Skip to main content

6.缓存相关内容

Redis缓存预热

缓存预热是指在系统启动或者某个时刻,通过提前加载缓存数据,将常用的数据预先存储到缓存中,以提高系统的性能和响应速度。在Redis中,缓存预热通常指的是在启动或运行时,将一部分或全部的热门数据加载到缓存中。

目的

  • 提高响应速度: 预热可以确保系统启动后,大部分请求都能够从缓存中获取数据,提高响应速度。
  • 降低数据库压力: 预热后,数据库的压力会减小,因为部分请求可以直接从缓存中获取数据。
  • 减少缓存穿透: 预热可以避免缓存穿透问题,即大量请求直接穿透缓存访问数据库。

实现方法

脚本或工具

可以编写脚本或使用专门的工具,从数据库中读取热门数据,然后通过Redis提供的API将数据写入到缓存中。

后台任务

可以通过后台任务或定时任务,周期性地从数据库中读取热门数据,然后更新到Redis缓存中。

步骤

识别热门数据

首先,需要通过日志分析、统计工具等方式识别出系统中的热门数据,通常是那些频繁被访问的数据。

数据加载到缓存

将识别出的热门数据加载到缓存中。这可以在系统启动时完成,也可以通过定时任务进行。

处理并发问题

在进行缓存预热时,需要考虑并发访问可能导致的问题。可以使用Redis的事务或者加锁机制来避免并发问题。

注意事项

  • 定时更新: 缓存预热是一个动态的过程,系统的数据访问模式可能会发生变化,因此需要定期更新缓存。
  • 内存管理: 预热的数据需要占用一定的内存空间,需要根据系统的内存情况进行合理规划。
  • 异常处理: 在预热过程中,可能会发生一些异常,如数据库连接异常、数据加载异常等,需要有相应的异常处理机制。
  • 性能测试: 在进行缓存预热之后,需要进行性能测试,以确保系统在高并发情况下能够正常运行。

脚本示例

chatgpt给的脚本示例,没做过测试,仅供参考:

import redis
import pymysql

def load_data_from_database():
# 从数据库中加载热门数据的逻辑
# 这里假设使用MySQL数据库,根据实际情况修改连接参数和SQL语句
connection = pymysql.connect(
host='your_mysql_host',
user='your_mysql_user',
password='your_mysql_password',
database='your_database_name',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)

try:
with connection.cursor() as cursor:
# 从数据库中查询热门数据
sql = 'SELECT id, name FROM your_table WHERE condition'
cursor.execute(sql)
result = cursor.fetchall()
return result
finally:
connection.close()

def preload_redis_cache(redis_host, redis_port, data):
# 将数据预热到Redis缓存中
redis_client = redis.StrictRedis(host=redis_host, port=redis_port, decode_responses=True)

for item in data:
key = f'your_prefix:{item["id"]}' # 根据实际情况定义键名前缀
value = item['name'] # 根据实际情况定义值
redis_client.set(key, value)

if __name__ == "__main__":
# 配置Redis连接信息和其他参数
redis_host = 'your_redis_host'
redis_port = 6379

# 加载热门数据
hot_data = load_data_from_database()

# 预热Redis缓存
preload_redis_cache(redis_host, redis_port, hot_data)

print("Cache preloading completed.")
  • 根据实际情况修改脚本中的连接参数、SQL语句、键名前缀、值等。
  • 确保在运行之前备份好数据库和Redis数据,以免因为错误操作导致数据丢失。

Redis缓存雪崩

Redis缓存雪崩是指在某个时间点,大量缓存数据同时失效或者被清除,导致大量的请求直接落到数据库上,压力骤增,引起系统性能急剧下降,类似于雪崩效应。这种情况通常是由于缓存中的大量数据在同一时间失效引起的。

缓存雪崩通常出现在以下情况:

  • 相同的过期时间: 大量的缓存数据设置了相同的过期时间,导致它们在同一时刻失效。
  • 缓存数据量庞大: 当缓存中的数据量很大时,失效的概率也就相应增大。
  • 热点数据集中: 如果系统中某些数据是热点数据,它们的缓存失效可能会对系统产生较大影响。

缓解和预防缓存雪崩的策略

  • 设置随机的过期时间: 避免所有缓存数据同时失效,可以为每个缓存数据设置一个稍微不同的过期时间,使得它们在时间上分散失效。
  • 使用互斥锁(Mutex): 在缓存失效时,通过互斥锁来保护数据库查询,防止大量请求同时落到数据库上。例如,在缓存失效的时候,只允许一个请求去查询数据库,其他请求等待。
  • 采用缓存预热策略: 在系统启动时或者低峰期,通过预热的方式将热门数据加载到缓存中,避免在高峰期缓存大量失效。
  • 使用分布式锁: 在缓存失效的时候,通过分布式锁来保护数据库查询,避免多个服务实例同时查询数据库。
  • 限流和降级: 在缓存失效时,可以通过限流或降级等手段来减缓请求压力,保证系统的稳定性。
  • 合理设置缓存过期时间: 针对不同的数据,可以设置不同的过期时间,避免在同一时刻大量数据同时失效。
  • 使用备份机制: 在缓存失效时,可以使用备份机制,先从备份数据中获取数据,然后异步更新缓存,减轻数据库的压力。

脚本示例

一个简单的Python脚本,演示一种缓解缓存雪崩的方式,即在缓存失效时,使用互斥锁来防止并发请求同时触发数据库查询。

import redis
import time
import threading

def get_data_from_database():
# 模拟从数据库中获取数据的逻辑
# 这里可以根据实际情况修改为真实的数据库查询
time.sleep(2) # 模拟查询数据库的耗时操作
return "Data from database"

def get_data_with_mutex(redis_client, key, mutex_key):
# 尝试获取互斥锁,防止并发请求同时触发数据库查询
with redis_client.lock(mutex_key, timeout=3):
# 先检查缓存中是否有数据
cached_data = redis_client.get(key)
if cached_data:
return cached_data

# 缓存中没有数据,从数据库获取数据
data = get_data_from_database()

# 将获取的数据存入缓存
redis_client.set(key, data, ex=60) # 设置缓存过期时间为60秒

return data

if __name__ == "__main__":
redis_host = 'your_redis_host'
redis_port = 6379

# 创建Redis连接
redis_client = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)

# 模拟多个并发请求
def simulate_concurrent_requests():
key = 'your_cache_key'
mutex_key = f'{key}:mutex'

data = get_data_with_mutex(redis_client, key, mutex_key)
print(f"Data: {data}")

# 启动多个线程模拟并发请求
threads = []
for _ in range(5):
thread = threading.Thread(target=simulate_concurrent_requests)
threads.append(thread)

for thread in threads:
thread.start()

for thread in threads:
thread.join()

print("Script completed.")

在上述脚本中,通过使用 redis-py 库提供的 lock 函数实现互斥锁。这样,当多个线程同时触发缓存失效时,只有一个线程能够获取到互斥锁,其他线程会等待锁释放。这样可以有效地避免缓存雪崩问题。

Redis缓存击穿

Redis缓存击穿是指在某个时间点,对某一热点数据的请求非常集中,导致缓存失效,大量请求直接落到数据库上,引起系统性能急剧下降,类似于缓存雪崩,但缓存击穿通常是由于某个特定的热点数据的缓存失效引起的。

主要原因

  1. 缓存过期: 当某个热点数据的缓存过期时,如果此时有大量请求同时访问这个数据,会导致缓存失效,这些请求直接访问数据库,引起数据库压力激增。
  2. 首次访问或低频访问: 对于一些很少被访问的数据,当它们的缓存失效时,可能会有大量请求同时访问。

缓解和预防缓存击穿的策略

  1. 使用互斥锁: 在缓存失效时,使用互斥锁来防止多个请求同时触发数据库查询。这样只有一个请求能够执行数据库查询,其他请求等待。
  2. 提前主动更新缓存: 在缓存过期之前,提前主动更新缓存。例如,可以在缓存即将过期时异步更新缓存,避免在大量请求同时到来时触发数据库查询。
  3. 设置短暂的缓存过期时间: 对于热点数据,可以设置相对较短的缓存过期时间,确保数据能够及时得到更新。
  4. 使用缓存穿透保护策略: 在查询数据库之前,可以先进行缓存穿透保护,例如使用布隆过滤器等手段,防止对不存在的数据不断触发数据库查询。
  5. 使用备份机制: 在缓存失效时,可以从备份数据中获取数据,然后异步更新缓存,减轻数据库的压力。
  6. 限制并发访问: 在系统层面,可以限制某一资源的并发访问数,防止大量请求同时访问。

缓解缓存击穿的脚本示例

以下是一个简单的Python脚本示例,演示了如何通过互斥锁来缓解缓存击穿问题。

import redis
import threading
import time

def get_data_from_database():
# 模拟从数据库中获取数据的逻辑
# 这里可以根据实际情况修改为真实的数据库查询
time.sleep(2) # 模拟查询数据库的耗时操作
return "Data from database"

def get_data_with_mutex(redis_client, key, mutex_key):
# 尝试获取互斥锁,防止并发请求同时触发数据库查询
with redis_client.lock(mutex_key, timeout=3):
# 先检查缓存中是否有数据
cached_data = redis_client.get(key)
if cached_data:
return cached_data

# 缓存中没有数据,从数据库获取数据
data = get_data_from_database()

# 将获取的数据存入缓存
redis_client.set(key, data, ex=60) # 设置缓存过期时间为60秒

return data

if __name__ == "__main__":
redis_host = 'your_redis_host'
redis_port = 6379

# 创建Redis连接
redis_client = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)

# 模拟多个并发请求
def simulate_concurrent_requests():
key = 'your_cache_key'
mutex_key = f'{key}:mutex'

data = get_data_with_mutex(redis_client, key, mutex_key)
print(f"Data: {data}")

# 启动多个线程模拟并发请求
threads = []
for _ in range(5):
thread = threading.Thread(target=simulate_concurrent_requests)
threads.append(thread)

for thread in threads:
thread.start()

for thread in threads:
thread.join()

print("Script completed.")

缓存穿透

Redis缓存穿透是指恶意请求或者无效请求穿过缓存层直接访问数据库,导致数据库压力过大,严重影响系统性能。缓存穿透通常是由于请求的键在缓存中不存在,每次请求都会直接访问数据库,即使数据库中也不存在这个键对应的数据。

主要原因

  1. 请求的数据不存在: 恶意请求或者无效请求查询的数据在数据库和缓存中都不存在,导致每次请求都会落到数据库上。
  2. 查询条件非法: 查询条件不合法或者恶意构造的查询条件导致数据库中不可能存在这样的数据。

缓解和预防缓存穿透的策略

  1. 布隆过滤器: 使用布隆过滤器对请求的键进行过滤,将可能存在的键加入布隆过滤器,不在布隆过滤器中的请求可以直接拒绝,避免直接查询数据库。
  2. 缓存空对象: 对于数据库中不存在的键,也可以将其缓存,但是设置一个较短的过期时间,避免长时间缓存无效数据。
  3. 合法性校验: 在业务层面对请求的查询条件进行合法性校验,如果不合法直接拒绝请求。
  4. 限制请求频率: 对请求频率进行限制,防止大量无效请求对数据库造成压力。
  5. 使用互斥锁: 在缓存失效时,使用互斥锁进行保护,防止多个请求同时触发数据库查询。

缓解缓存穿透的脚本示例

以下是一个简单的Python脚本示例,演示了如何通过布隆过滤器进行缓解缓存穿透问题。

import redis
import threading
import time

def get_data_from_database():
# 模拟从数据库中获取数据的逻辑
# 这里可以根据实际情况修改为真实的数据库查询
time.sleep(2) # 模拟查询数据库的耗时操作
return "Data from database"

def get_data_with_mutex(redis_client, key, mutex_key):
# 尝试获取互斥锁,防止并发请求同时触发数据库查询
with redis_client.lock(mutex_key, timeout=3):
# 先检查缓存中是否有数据
cached_data = redis_client.get(key)
if cached_data:
return cached_data

# 缓存中没有数据,从数据库获取数据
data = get_data_from_database()

# 将获取的数据存入缓存
redis_client.set(key, data, ex=60) # 设置缓存过期时间为60秒

return data

if __name__ == "__main__":
redis_host = 'your_redis_host'
redis_port = 6379

# 创建Redis连接
redis_client = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)

# 模拟多个并发请求
def simulate_concurrent_requests():
key = 'your_cache_key'
mutex_key = f'{key}:mutex'

data = get_data_with_mutex(redis_client, key, mutex_key)
print(f"Data: {data}")

# 启动多个线程模拟并发请求
threads = []
for _ in range(5):
thread = threading.Thread(target=simulate_concurrent_requests)
threads.append(thread)

for thread in threads:
thread.start()

for thread in threads:
thread.join()

print("Script completed.")

Redis性能监控指标

当进行Redis性能监控时,可以关注以下性能指标

  1. 内存使用情况:

    • used_memory: 当前使用的内存总量。
    • used_memory_peak: 使用的最大内存量。
  2. 命令统计:

    • total_commands_processed: Redis处理的命令总数。
    • instantaneous_ops_per_sec: 每秒执行的命令数。
    • keyspace_hitskeyspace_misses: 缓存命中和未命中的次数,用于计算缓存命中率。
  3. 连接统计:

    • connected_clients: 当前连接到Redis实例的客户端数量。
    • blocked_clients: 被阻塞的客户端数量。
  4. 持久化和复制统计:

    • rdb_changes_since_last_save: 上次RDB持久化之后的修改次数。
    • rdb_last_save_time: 上次成功执行RDB持久化的时间戳。
    • aof_current_size: AOF文件当前大小。
    • aof_rewrite_in_progress: AOF重写是否正在进行。
  5. 性能指标:

    • instantaneous_input_kbpsinstantaneous_output_kbps: 输入和输出的瞬时速率。
  6. 慢查询统计:

    • slowlog_len: 慢查询日志中的条目数量。
    • slowlog_last_id: 慢查询日志中最后一条查询的ID。
    • slowlog_log_slower_than: 记录慢查询的阈值(以微秒为单位)。
    • slowlog_max_len: 慢查询日志的最大长度。
  7. 事件和连接统计:

    • total_connections_received: 收到的连接总数。
    • total_commands_processed: 处理的命令总数。
  8. Lua脚本和缓存统计:

    • lua_scripts: 加载的Lua脚本数量。
    • lua_reloads: 重新加载的Lua脚本数量。
    • lua_cached_scripts: 缓存的Lua脚本数量。
    • lua_max_cache_size: Lua脚本缓存的最大大小。
  9. 网络和客户端缓冲区使用情况:

    • total_net_input_bytestotal_net_output_bytes: 从启动以来的总输入和输出字节数。
    • rejected_connections: 被拒绝的连接数量。
    • used_cpu_sysused_cpu_user: 用于内核和用户操作的CPU时间。
    • used_cpu_sys_childrenused_cpu_user_children: 子进程用于内核和用户操作的CPU时间。
  10. 主从同步和Replica相关指标(在主从复制和集群模式下):

    • role: Redis实例的角色(主服务器、从服务器)。
    • connected_slaves: 连接到主服务器的从服务器数量。
    • master_sync_in_progress: 主从同步是否正在进行。
    • master_link_down_since_seconds: 如果主从同步断开,距离断开的时间。
    • master_last_io_seconds_ago: 距离上次与主服务器通信的时间。
  11. 集群信息(在集群模式下):

    • cluster_enabled: Redis是否运行在集群模式下。
    • cluster_size: 集群中节点的数量。

相关命令

SLOWLOG

SLOWLOG 是一个慢查询日志,用于记录执行时间超过阈值的命令。慢查询日志可以帮助开发者识别系统中的性能瓶颈和慢查询问题。

查看慢查询日志

SLOWLOG GET [count]
  • count: 可选参数,用于指定获取最近慢查询日志的条数。

配置慢查询阈值

可以通过配置 slowlog-log-slower-than 参数来设置慢查询的时间阈值。这个值表示,当一个命令执行时间超过该阈值时,会被记录到慢查询日志中。

在配置文件中添加如下配置:

slowlog-log-slower-than 10000

当一个命令的执行时间超过 10 毫秒时,会被记录到慢查询日志中。

慢查询日志的记录格式

慢查询日志的记录格式包含以下信息:

  • id: 日志条目的唯一标识符。
  • timestamp: 记录时间戳。
  • execution_time: 命令执行时间(以微秒为单位)。
  • command: 执行的命令及参数。

示例

1) (integer) 1
2) (integer) 1637851499
3) (integer) 10442
4) 1) "GET"
2) "example_key"

1) 表示第一条日志,2) 表示时间戳,3) 表示执行时间,4) 是一个数组,包含执行的命令和参数。

示例

查看最近的5条慢查询日志:

SLOWLOG GET 5

注意事项

  • 慢查询日志会占用一定的内存,因此在生产环境中需要谨慎使用。
  • 在Redis命令行客户端中,也可以使用 CONFIG GET slowlog-log-slower-than 查看当前慢查询阈值。

相关工具

redis-benchmark

redis-benchmark是Redis自带的性能测试工具,用于对Redis服务器进行基准测试,评估其性能和吞吐量。通过模拟多个并发客户端同时执行操作,可以测试Redis在不同负载下的表现。

语法:

redis-benchmark [options]

常用选项:

  • -c:并发连接数。指定并发连接数,即模拟的客户端数量。
  • -n:请求数。指定总的请求数。
  • -P:管道数量。指定每个客户端的请求在发送到服务器之前积累的数量。
  • -t:测试类型。指定要执行的测试类型,如 PINGSETGET等。
  • -d:数据大小。指定数据大小,以字节为单位。
  • -a:密码。指定连接Redis时使用的密码。
  • -v:输出详细信息。输出详细信息,包括每个命令的响应时间和其他统计信息。
  • -n:选择数据库。指定连接的数据库编号。
redis-benchmark -c 50		#
redis-benchmark -n 10000 #
redis-benchmark -P 16
redis-benchmark -t set,get
redis-benchmark -d 100
redis-benchmark -v

示例:

模拟50个并发连接,执行10000个SET操作

redis-benchmark -c 50 -n 10000 -t set