前言
在实际使用过程中, 经常会碰到Redis
响应延迟高的问题, 故将可能出现的问题及优化思路整理一下, 以防不时之需. 这里罗列的问题是目前能够想到的, 后续如果遇到新的问题会再回来更新的.
业务修改
主要从Redis
的业务使用方面处理, 不需要动到Redis
实例.
慢查询
是否存在处理时间过久的操作.
分析慢查询可通过如下方式:
- 查看线上延迟是否较高
redis-cli --intrinsic-latency 30
, 通过命令可分析30s 内的延迟, 作为比较的基线
- 查看
slowlog
redis-cli slowlog get 10
, 获取最新的10条慢日志- 相关配置:
slowlog-log-slower-than 10000
: 执行时间大于10000微妙, 会进行慢查询记录slowlog-max-len
: 在内存中记录多少条慢查询
- 峰值监控
redis-cli LATENCY LATEST
, 获取超过阈值的命令最新和最大的延迟- 相关配置:
latency-monitor-threshold 1000
: 当命令执行超过1000微妙, 就会被监控
对于慢查询, 解决办法就是使用时间复杂度低的命令. 具体分析下业务
并发高
当并发量过高的时候, 也会导致请求的延迟下降.
可以先想办法降低并发量, 或者水平扩容.
可能用到的分析工具:
redis-faina
:facebook
开源的分析脚本, 可查看哪些key
的访问频率高等redis-cli -p 6379 MONITOR | head -n 100 | ./redis-faina.py
大量 key 同时过期
因为redis
的过期策略是, 随机取样, 回收其中已过期的元素, 循环此步骤, 知道取样数据中已过期元素比例小于25%. 而在回收的过程中, 是会阻塞主线程的.
解决办法就是将过期时间添加随机数, 防止同时过期.
同步删除
在执行同步删除操作(如del
)时, 需要释放内存空间, 同时若删除的数据很大, 就会导致主线程阻塞.
分析大 key 可能用到的工具:
redis-cli --bigkeys -i 0.1
: 查找大 key. 每扫描100次, 停0.1s. 避免影响业务redis-clie memory usage key
: 查询某个key
的内存占用
解决方案, 可将同步删除改为异步删除:
del
->unlink
flushdb
->flushdb async
flushall
->flushall async
系统层面
同时, 在系统层面也存在影响其运行的因素, 包括: 运行配置、内存溢出、IO 阻塞等等. 这里整理一些可能会有影响的因素, 一些是我遇到的, 一些是我想到的, 还有是我在网上找到的.
数据量过大
当数据量较大, 占用内存较高时, 会导致进程的页表变大. 无论是生成rdb
还是AOF
重写, 都会fork
子进程, 而子进程fork
时是需要复制页表的, 会导致fork
操作变慢. 而fork
操作会阻塞主线程.
AOF日志重写
在将数据写入磁盘的过程中, 会存在如下两个步骤:
write
: 将数据先存放的缓冲区, 此时还在内存中fsync
: 将数据落盘, 此操作会进行阻塞
Redis
的 AOF 日志有两种, 一个是增量日志, 记录每条操作, 一个是全量日志, 用于减少日志体积. AOF增量日志的配置项为appendfsync
, 有如下3中策略:
no
: 关闭 AOF 日志always
: 同步写. 既每条操作日志在主线程写入后立即调用fsync
落盘, 落盘后返回客户端everysec
: 每秒调用fsync
进行落盘
在如下场景会造成阻塞:
- AOF重写会发生大量磁盘 IO
- 此时
fsync
会发生阻塞. 即使使用子线程执行fsync
, 若主线程发现上一次fsync
还未完成, 仍会阻塞等待.
解决方案:
- 更换固态硬盘, 提高IO 速度
- 修改配置
no-appendfsync-on-rewrite yes
- 表示在 AOF 重写的时候, 不执行
fsync
操作, 等到重写完毕, 主线程再执行fsync
- 但这样可能会导致, 若重写过程中宕机, 这期间的操作丢失
- 表示在 AOF 重写的时候, 不执行
内存交换
若物理内存不够用了, 操作系统会将部分内存交换到磁盘上来, 此时磁盘的读取就变慢了.
判断是否发生了交换:
# 获取进程 ID
ps -ef | grep redis
# 查看内存占用及有多少被 swap
# 其结果为进程的所有内存页, 及每页中有多少在磁盘上
cat /proc/22111/smaps | egrep '^(Swap|Size)'
解决方法就是垂直扩容或水平扩容咯.
主从同步阻塞
在主从同步的开始, 从库获取rdb
文件进行全量同步. 此时会先执行flushdb
操作, 也会造成阻塞. 原因请参考同步删除
多核CPU
若物理机存在多核 CPU, 那么进程从物理核A 切换到物理核B 时, 不光需要进行上下位切换. 包括L1/L2
缓存也要重新加载, 存在一定耗时.
可将进程绑定在单个物理核上运行, 避免切换:
# 查看当前cpu 核心架构
lscpu
# 绑定到同一物理核上的多个逻辑核
# 最好多绑定几个逻辑核, 因为redis 本身是多线程的
taskset -c 0,1 ./redis-server
内存大页
操作系统默认分页为4kb, 若启用大页, 则每页为4mb
在执行备份的时候, 会复制子进程, 此时发生内存更新, 会触发写时复制, 将整个页的数据全部复制.
通过命令cat /sys/kernel/mm/transparent_hugepage/enabled
查看是否启用大页. 改为never
关闭即可
内存碎片
Redis
在运行的过程中, 是否内存并不会将内存还给操作系统, 而是会留下来等待下次使用. 此时就会导致Redis
使用的内存较少, 但占用的物理内存较大.
查看内存碎片:
# used_memory: 使用内存
# used_memory_rss: 系统分配的内存
# mem_fragmentation_ratio: 内存碎片率. 等于: used_memory_rss/used_memory
redis-cli info memory
解决方案:
# 修改配置文件启动自动碎片清理
activedefrag yes
# 当碎片字节数超过100mb, 进行清理
active-defrag-ignore-bytes 100mb
# 当内存空间占分配空间的10%, 进行清理
active-defrag-threshold-lower 10
# 清理过程中, CPU使用比例不低于1%, 保证清理正常进行
active-defrag-cycle-min 1
# 清理过程中, CPU使用比例不高于25%, 保证服务正常
active-defrag-cycle-max 25
缓冲区溢出
Redis
会将客户端的请求和响应分别放入缓冲区中, 若缓冲区不够用了, 可能会导致溢出.
redis 默认提供最多1G 的输入缓冲区大小, 不可配置.
查看缓冲区占用:
# 查询所有客户端的情况, 其中部分
# cmd: 最新执行的命令
# qbuf: 缓冲区已经使用的大小
# qbuf-free: 缓冲区尚未使用的大小
redis-cli CLIENT LIST
解决:
- 判断请求和响应中是否存在大 key 导致缓冲区溢出
- 修改配置
client-output-buffer-limit
, 可更改输出缓冲区的大小client-output-buffer-limit normal 0 0 0
: 普通客户端不做限制. 后面的三个0分别表示: 缓冲区大小限制, 持续写入量限制, 持续写入时间限制client-output-buffer-limit pubsub 8mb 2mb 60
: 修改订阅客户端的限制. 若60s 内写入超过2mb 或者 总实际占用 量超过8mb, 则会关闭连接client-output-buffer-limit slave 8mb 2mb 60
: 针对主从中主节点的复制缓冲区设置. 在全量复制期间暂存的增量数据
- 修改配置
repl-backlog-size
. 此配置为主从节点进行同步的数据存放, 主节点将新增命令记录, 从节点拉取, 是一个环形列表- 注意, 此值若设置的太小, 从节点要拉取的数据已经被覆盖了, 就会触发全量复制
运维工具
整理一些在Redis
运行过程中可能用到的运维工具.
- INFO 命令:
Redis
的info命令本身就能够显示查询一些信息server
: 实例本身的信息client
: 客户端统计信息stat
: 常规的统计信息keyspace
: 数据库相关commandstats
: 命令统计memory
: 内存信息cpu
:persistence
: RDB和AOF信息replication
: 主从复制cluster
: 集群信息
redis-faina
:facebook
开源的分析脚本, 可查看哪些key
的访问频率高等prometheus
: 一款可视化的监控报警系统. 配合插件Redis-exporter
cachecloud
: 搜狐开源的Redis
管理平台. 支持集群、主从、哨兵等的自动化部署和管理redis-shake
: 阿里开源的数据迁移工具.
先写这么多, 后面遇到问题再回来加吧.