Redis性能优化

1.是否使用复杂度过高的命令

首先,第一步,你需要去查看一下 Redis 的慢日志(slowlog)。

Redis 提供了慢日志命令的统计功能,它记录了有哪些命令在执行时耗时比较久。

查看 Redis 慢日志之前,你需要设置慢日志的阈值。例如,设置慢日志的阈值为 5 毫秒,并且保留最近 500 条慢日志记录:

Lua 复制代码
redis-cli -h 127.0.0.1 -p 6379
# 命令执行耗时超过 5 微秒,记录慢日志
CONFIG SET slowlog-log-slower-than 5
# 只保留最近 500 条慢日志
CONFIG SET slowlog-max-len 500
#获取最近的 10 条慢查询命令
redis-cli SLOWLOG GET 10

(1)查看日志中是否使用 O(N) 以上复杂度的命令,例如 SORT、SUNION、ZUNIONSTORE 聚合类命令

(2)Redis 一次需要返回给客户端的数据过多,更多时间花费在数据协议的组装和网络传输过程中。

优化:

(1)对于数据的聚合操作,放在客户端做

(2)每次获取尽量少的数据,让 Redis 可以及时处理返回

2. 是否操作 bigkey

如果你查询慢日志发现,并不是复杂度过高的命令导致的,而都是 SET / DEL 这种简单命令出现在慢日志中,那么需要判断实例是否写入了 bigkey

bash 复制代码
redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 1
-------- summary -------
Sampled 829675 keys in the keyspace!
Total key length in bytes is 10059825 (avg len 12.13)
Biggest string found 'key:291880' has 10 bytes
Biggest   list found 'mylist:004' has 40 items
Biggest    set found 'myset:2386' has 38 members
Biggest   hash found 'myhash:3574' has 37 fields
Biggest   zset found 'myzset:2704' has 42 members
36313 strings with 363130 bytes (04.38% of keys, avg size 10.00)
787393 lists with 896540 items (94.90% of keys, avg size 1.14)
1994 sets with 40052 members (00.24% of keys, avg size 20.09)
1990 hashs with 39632 fields (00.24% of keys, avg size 19.92)
1985 zsets with 39750 members (00.24% of keys, avg size 20.03)

3. 数据是否集中过期

当redis中大量数据集中过期时,请求穿透到mysql等关系型数据库,会导致查询速度变慢,从而影响redis响应变慢

在某个时间点突然出现一波延时,其现象表现为:变慢的时间点很有规律,例如某个整点,或者每间隔多久就会发生一波延迟。

优化:

(1)集中过期 key 增加一个随机过期时间,把集中过期的时间打散,降低 Redis 清理过期 key 的压力

# 在过期时间点之后的 5 分钟内随机过期掉
redis.expireat(key, expire_time + random(300))

(2)如果使用的 Redis 是 4.0 以上版本,可以开启 lazy-free 机制,当删除过期 key 时,把释放内存的操作放到后台线程中执行,避免阻塞主线程。

# 释放过期 key 的内存,放到后台线程执行
lazyfree-lazy-expire yes

4. 实例内存是否达到上限

把 Redis 当做纯缓存使用时,通常会给这个实例设置一个内存上限 maxmemory,然后设置一个数据淘汰策略。

当 Redis 内存达到 maxmemory 后,每次写入新的数据之前,Redis 必须先从实例中踢出一部分数据,让整个实例的内存维持在 maxmemory 之下,然后才能把新数据写进来。

这个踢出旧数据的逻辑也是需要消耗时间的,而具体耗时的长短,要取决于你配置的淘汰策略:

  • allkeys-lru:不管 key 是否设置了过期,淘汰最近最少访问的 key
  • volatile-lru:只淘汰最近最少访问、并设置了过期时间的 key
  • allkeys-random:不管 key 是否设置了过期,随机淘汰 key
  • volatile-random:只随机淘汰设置了过期时间的 key
  • allkeys-ttl:不管 key 是否设置了过期,淘汰即将过期的 key
  • noeviction:不淘汰任何 key,实例内存达到 maxmeory 后,再写入新数据直接返回错误
  • allkeys-lfu:不管 key 是否设置了过期,淘汰访问频率最低的 key(4.0 + 版本支持)
  • volatile-lfu:只淘汰访问频率最低、并设置了过期时间 key(4.0 + 版本支持)

一般最常使用的是 allkeys-lru / volatile-lru 淘汰策略,它们的处理逻辑是,每次从实例中随机取出一批 key(这个数量可配置),然后淘汰一个最少访问的 key,之后把剩下的 key 暂存到一个池子中,继续随机取一批 key,并与之前池子中的 key 比较,再淘汰一个最少访问的 key。以此往复,直到实例内存降到 maxmemory 之下。

需要注意的是,Redis 的淘汰数据的逻辑与删除过期 key 的一样,也是在命令真正执行之前执行的,也就是说它也会增加我们操作 Redis 的延迟,而且,写 OPS 越高,延迟也会越明显。

优化:

(1)淘汰策略改为随机淘汰,随机淘汰比 LRU 要快很多(视业务情况调整)

(2)拆分实例,把淘汰 key 的压力分摊到多个实例上

5. 是否开启了redis持久化

当 Redis 开启了后台 RDB 和 AOF后,在执行时,它们都需要主进程fork出一个子进程进行数据的持久化。主进程在 fork 子进程期间,整个实例阻塞无法处理客户端请求的时间。因此如果此时磁盘的 IO 负载很高,那这个后台线程在执行刷盘操作(fsync 系统调用)时就会被阻塞住。此时的主线程依旧会接收写请求,紧接着,主线程又需要把数据写到文件内存中(write 系统调用),当主线程使用后台子线程执行了一次 fsync,需要再次把新接收的操作记录写回磁盘时,如果主线程发现上一次的 fsync 还没有执行完,那么它就会阻塞。

你可以在 Redis 上执行 INFO 命令,查看 latest_fork_usec 项,单位微秒。

复制代码
##上一次 fork 耗时,单位微秒`
latest_fork_usec:59477`

对于AOP,还存在着 AOF rewrite操作,这个过程也会占用大量的磁盘 IO 资源。 此外fork 的耗时也与系统也有关,虚拟机比物理机耗时更久。

优化:

(1)当子进程在 AOF rewrite 期间,可以让后台子线程不执行刷盘

# AOF rewrite 期间,AOF 后台子线程不进行刷盘操作
# 相当于在这期间,临时把 appendfsync 设置为了 none
no-appendfsync-on-rewrite yes

(2)把redis创建到真实物理机上,而不是虚拟机

(3)如果只是把redis作为缓存使用,可以关闭RDB 和 AOF持久化功能

(4)尽量不要把redis 和 其他 i/o 使用率高的创建在同一台机器上,让 Redis 运行在独立的机器上。

(5)SSD磁盘要比机械硬盘读写效率高出许多

6. 是否开启了内存大页

如果采用了内存大页,那么,即使客户端请求只修改 100B 的数据,Redis 也需要拷贝 2MB 的大页。相反,如果是常规内存页机制,只用拷贝 4KB。两者相比,你可以看到,当客户端请求修改或新写入数据较多时,内存大页机制将导致大量的拷贝,这就会影响 Redis 正常的访存操作,最终导致性能变慢。

首先,我们要先排查下内存大页。方法是:在 Redis 实例运行的机器上执行如下命令:

复制代码
`$ `cat` /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
`

如果执行结果是 always,就表明内存大页机制被启动了;如果是 never,就表示,内存大页机制被禁止。

在实际生产环境中部署时,我建议你不要使用内存大页机制,操作也很简单,只需要执行下面的命令就可以了:

复制代码
echo` never /sys/kernel/mm/transparent_hugepage/enabled
`

其实,操作系统提供的内存大页机制,其优势是,可以在一定程序上降低应用程序申请内存的次数。

但是对于 Redis 这种对性能和延迟极其敏感的数据库来说,我们希望 Redis 在每次申请内存时,耗时尽量短,所以我不建议你在 Redis 机器上开启这个机制。

7. 最后还要考虑网络及机器配置对 Redis 性能的影响

相关推荐
NiNg_1_2345 小时前
Redis - 全局ID生成器 RedisIdWorker
数据库·redis·缓存
如风暖阳7 小时前
Redis单线程架构
数据库·redis·架构
记得开心一点嘛8 小时前
Redis --- 秒杀优化方案(阻塞队列+基于Stream流的消息队列)
数据库·redis·分布式·缓存
AI大模型训练家19 小时前
2025Java面试题超详细整理《微服务篇》
android·数据库·redis·sql·微服务·架构·wpf
ChinaRainbowSea1 天前
五. Redis 配置内容(详细配置说明)
java·数据库·redis·缓存·bootstrap·nosql
阿芯爱编程1 天前
redis教程
数据库·redis·缓存
Pafey1 天前
实现一个 LRU 风格的缓存类
c++·缓存
ChinaRainbowSea1 天前
十. Redis 事务和 “锁机制”——> 并发秒杀处理的详细说明
android·java·数据库·redis·后端·bootstrap·nosql
茂桑1 天前
Redis缓存穿透、击穿、雪崩介绍以及解决方案
数据库·redis·缓存