Redis排队延迟问题:造成的原因及解决方案
slowlog 无法监控排队延时,他只能监控每条命令的执行时间,并将执行时间大于设置的阈值的命令保存起来。
持久化阻塞
原因
对于开启了持久化功能的 Redis 节点,需要排查是否是持久化导致的阻 塞。持久化引起主线程阻塞的操作主要有:fork 阻塞、AOF 刷盘阻塞。
fork 操作发生在 RDB 和 AOF 重写时,Redis 主线程调用 fork 操作产生共享内存的子进程,由子进程完成对应的持久化工作。如果 fork 操作本身耗时过长,必然会导致主线程的阻塞。
解决
可以执行 info stats 命令获取到 latestforkusec 指标,表示 Redis 最近一次 fork 操作耗时,如果耗时很大,比如超过 1 秒,则需要做出优化调整。
redis-cli -c -p 7000 info | grep -w latest_fork_usec
latest_fork_usec:315
当我们开启 AOF 持久化功能时,文件刷盘的方式一般采用每秒一次,后 台线程每秒对 AOF 文件做 fsync 操作。当硬盘压力过大时,fsync 操作==需要等待==,直到写入完成。如果主线程发现距离上一次的 fsync 成功超过 2 秒,为了数据安全性它会==阻塞命令的执行==直到后台线程执行 fsync 操作完成。这种阻塞行为主要是硬盘压力引起,可以查看 Redis 日志识别出这种情况,当发生这种阻塞行为时,会打印如下日志:
Asynchronous AOF fsync is taking too long (disk is busy). \
Writing the AOF buffer without waiting for fsync to complete, \
this may slow down Redis.
也可以查看 info persistence 统计中的 aofdelayedfsync 指标,每次发生 fdatasync 阻塞主线程时会累加。
>info persistence
loading:0
aof_pending_bio_fsync:0
aof_delayed_fsync:0
不合理的命令或数据结构
原因
一般来说 Redis 执行命令速度都非常快,但是当数据量达到一定级别时,某些命令的执行就会花费大量时间,比如对一个包含上万个元素的 hash 结构执行 hgetall 操作,由于数据量比较大且命令算法复杂度是 O(n),这条命令执行速度必然很慢。
这个问题就是典型的不合理使用命令和数据结构。对于高并发的场景我们应该尽量避免在大对象上执行算法复杂度大于等于 O(n) 的命令。对于键值较多的 hash 结构可以使用 scan 系列命令来逐步遍历,而不是直接使用 hgetall 来全部获取。
解决
Redis 本身提供发现大对象的工具,对应命令:redis-cli-h {ip} -p {port} bigkeys。这条命令会使用 scan 从指定的 Redis DB 中持续采样,实时输出当时得到的 value 占用空间最大的 key 值,并在最后给出各种数据结构的 biggest key 的总结报告。
内存交换
原因
内存交换(swap)对于 Redis 来说是非常致命的,Redis 保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把 Redis 使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的 Redis 性能急剧下降。识别 Redis 内存交换的检查方法如下:
>redis-cli -p 6383 info server | grep process_id # 查询 redis 进程号
>cat /proc/4476/smaps | grep Swap # 查询内存交换大小
Swap: 0 kB
Swap: 4 kB
Swap: 0 kB
Swap: 0 kB
如果交换量都是 0KB 或者个别的是 4KB,则是正常现象,说明 Redis 进程内存没有被交换。
解决
有很多方法可以避免内存交换的发生。比如说:
- 保证机器充足的可用内存;
- 确保所有 Redis 实例设置最大可用内存(maxmemory),防止极端情况下 Redis 内存不可控的增长;
- 降低系统使用 swap 优先级,如 echo10>/proc/sys/vm/swappiness。可以参考使用 Swap 增加内存进行设置;
参考: