半夜两点多,某线上项目X的Redis出现了一波诡异的延迟尖刺,P99从平时的1毫秒左右直接飙到800毫秒以上,持续两三秒后又恢复如常。业务方截图过来的时候我还不太慌,结果打开监控一看,CPU不高、内存没满、带宽也不紧张,慢查询日志里干干净净,数据库连接数甚至没有明显变化。这种“什么指标都正常,但就是卡”的情况,基本可以断定是Redis阻塞问题。
如果你也遇到过类似的场景,你应该能懂那种感觉:性能瓶颈排查了半天,最后发现根本不是网络问题,也不是硬件问题,而是Redis内部或系统层面的某个瞬间“堵住了”。在单线程模型下,任何一点阻塞都会被放大成全局事故,而阻塞的来源又五花八门——命令、持久化、内存、操作系统、主从复制、慢客户端,每一样都能给你来上一刀。这篇文章我就把Redis阻塞的几大类原因和整套排查链路掰开揉碎讲清楚,适合正在处理线上延迟问题的运维和开发者参考。
1. 先搞清楚Redis阻塞的“堵车逻辑”:单线程模型为什么让任何慢操作都被放大
1.1 Redis事件循环与命令处理的串行本质
Redis核心的工作模式是单线程事件循环。主线程在aeMain循环里,依次处理连接应答、命令读取、命令执行、命令回复,以及时间事件(过期清理、AOF刷盘、主从心跳这些后台任务)。这意味着同一时刻只能干一件事:当前命令没处理完,后续所有命令只能排队等待。
我习惯用一个“单车道收费站”的类比来解释这个问题。Redis就像一条单车道,每个客户端请求都是一辆车。正常情况下一辆车几微秒就过去了,但如果前面有一辆超长卡车在慢悠悠地翻仓库(比如执行KEYS *),或者收费站工作人员被叫去搬砖(比如主进程在fork生成RDB快照),那么后面所有的车都被堵在路上一动不动。车道上没有任何超车道,也没有红绿灯,只有一根车道在运转。
阻塞的本质就是:主线程在某一个瞬间被某件事占用,而这个占用时间明显超出了正常范围。占用期间,新到达的连接可能要排队,已连接的请求得不到响应,于是客户端那侧看到的就是延迟飙升、超时、甚至连接被重置。搞清楚这个底层逻辑,后面所有阻塞原因都能归到同一条主线上:什么情况下主线程会被莫名占用?
1.2 阻塞的全景地图:从命令、持久化到系统层
Redis官方文档其实把延迟和阻塞问题拆得很细,但网上资料比较零散,我这里把常见阻塞源整理成一张表,方便你后面对照排查。
| 阻塞类别 | 常见源头 | 典型表现 | 首选排查工具 |
|---|---|---|---|
| 命令执行 | 慢命令、大Key操作、集中过期 | 延迟尖刺、慢日志有记录 | SLOWLOG GET、--bigkeys |
| 持久化 | fork生成RDB、AOF重写、AOF刷盘 | 全实例停顿、fork耗时日志 | INFO persistence |
| 内存与系统 | swap、内存淘汰、碎片整理、THP | CPU不高但延迟高 | vmstat、INFO memory |
| 主从与集群 | 全量复制、主从切换、槽位迁移 | 延迟高峰与同步动作同步出现 | INFO replication |
| 网络与客户端 | 慢客户端、输出缓冲堆积、连接风暴 | 连接异常、缓冲区告警 | client list、tcpdump |
| 时间事件 | 过期键扫描、定时任务堆积 | 周期性延迟尖刺 | LATENCY LATEST |
很多新手排查阻塞只会看慢查询日志,这是一个很大的误区。慢查询日志只能捕获到“命令执行本身耗时长”的那类阻塞,而像fork阻塞、swap阻塞、网络缓冲区堆积这类问题,慢日志里往往一条记录都没有。这也是为什么上面那种“指标都正常”的谜之卡顿,经常把很多人卡在原地。
2. 命令执行引发的阻塞:大Key、慢命令与集中过期
2.1 慢查询日志与耗时命令的定位
命令执行阻塞是最容易被理解的一类,也是最常见的一类。Redis的慢查询日志(SLOWLOG)就是为这类问题准备的,它能记录执行耗时超过阈值的命令,但前提是你提前配置了恰当的阈值。
你可以先看下当前配置:
redis-cli CONFIG GET slowlog-log-slower-than这个值的单位是微秒,默认通常是10000,也就是10毫秒。如果你线上业务对延迟很敏感,建议把这个阈值调低到5000甚至2000微秒,否则很多“小毛刺”可能根本不会被记录下来。然后使用:
redis-cli SLOWLOG GET 100去拉取最近的100条慢命令,重点看command字段、execution_time字段和执行时间戳。我见过多次线上问题,最终定位结果都是一条命令直接干了几秒,比如在某实例上执行HGETALL一个成员数过万的Hash、执行SMEMBERS一个几十万成员的Set、或者对一个大列表做LRANGE 0 -1。这些命令的复杂度是O(N),当N足够大时,单线程就必然被卡住。
另外一个容易被忽略的就是KEYS命令。很多人知道它慢,但不知道它到底有多慢。在一个键数量达到几百万的实例上,一个KEYS * 足以让整个实例不可用好几秒,期间所有业务请求全部排队。虽然很多公司安全规范里早就禁止了KEYS,但代码里借用KEYS做模糊匹配的情况非常普遍,尤其是老项目里,这种代码就像埋着的雷。
2.2 大Key为什么是阻塞的头号杀手
慢命令往往和大Key是绑定在一起的。判断一个Key算不算大Key,业界没有统一标准,但根据我的实战经验可以参考这几个阈值:
- String类型:单个value超过10KB就算需要关注了。
- Hash、Set、ZSet、List:元素数量超过1万个,或整个对象占用内存超过10MB,就要纳入监控和治理。
- 单个大Key占用内存超过50MB以上,属于危险级别,处理时要格外小心。
大Key的阻塞风险不只是读取慢,删除同样致命。在老版本Redis里,删除一个大Key(比如一个包含几十万个元素的列表)意味着主线程要一次性释放这块内存,释放过程中所有请求都被挂起。很多团队第一次踩坑就是因为清理垃圾数据时,执行了一条DEL,结果整个Redis卡了几百毫秒。
解决删除大Key的阻塞,最简单的方式是使用Redis 4.0引入的UNLINK命令:
redis-cli UNLINK big_key_nameUNLINK会尝试把释放操作丢给后台线程异步处理,主线程只需要做一个引用计数递减,不会长时间卡住。但要注意,UNLINK并不是绝对无阻塞的:如果对象的引用计数不是1,它还是会同步释放;而且释放一个特别庞大的聚合对象时,后台线程同样会占CPU,极端情况下也会拖累主调度。所以根本的解决思路还是治理大Key,而不是等它出现了再想怎么删。
查找大Key可以直接用内置的扫描工具:
redis-cli --bigkeys它会以SCAN方式扫描全库并统计各种类型里最大的Key,不会污染主线程太多。新版本还可以用--memkeys来从内存维度统计,识别那些元素不多但单个元素巨长的Key。建议在低峰期定期跑一次,把扫描结果存下来做对比。
2.3 集中过期:藏在expire背后的CPU尖刺
还有一个很隐蔽的命令层阻塞来源:大量Key在同一时间点集中过期。Redis的过期键清理是周期性的,默认每秒执行10次(由hz配置控制),每次会抽样一部分过期键并删除。如果某个瞬间有过期任务堆积,主线程要在过期扫描和命令处理之间来回切换,CPU开销陡增,表现就是延迟在某个固定时间点出现规律性尖刺。
这类问题的经典场景是业务里把缓存过期时间设成整点或同一个相对时间,比如“凌晨0点所有用户标签缓存同时过期”,然后到了那个点Redis就卡顿几秒到几十秒。排查时你会发现慢日志里没有几条像样的命令,但系统CPU可能突然飙升。
解决方案也很朴素:在设置过期时间时加入随机扰动。比如统一过期时间原本是3600秒,就改成3600 + random(0, 300),让过期分布错开。同时可以把hz适当调高,比如从10调到20,让过期任务更加分散地执行,但这个参数会带来一点CPU开销,需要权衡。
3. 持久化机制是阻塞的重灾区:fork、RDB快照与AOF刷盘
3.1 fork时页表复制带来的全实例停顿
如果命令层排查了一圈没发现问题,我建议你立刻把目光转向持久化,这是线上Redis隐性阻塞的第二大源头。
Redis在生成RDB快照或执行AOF重写前,需要先fork一个子进程。fork这个系统调用本身在Linux里算是轻量级的,但如果父进程内存很大,fork复制页表的时间就不可忽略了。这里面的原理是:fork需要给子进程复制父进程的页表项,内存越大,页表就越大,复制时间越长,期间父进程完全停止响应。
一个常见参考数值:4GB左右内存的实例,fork可能需要几十毫秒;十几GB的实例,fork耗时可能到几百毫秒甚至超过1秒。如果你Redis里全是几GB级别的数据,那每次RDB快照就等于给线上请求来了一次“人为断电”。这类问题通过慢日志是看不到的,因为阻塞发生在命令执行之外。
如何确认?看日志和INFO输出。在Redis启动日志里如果出现类似:
Background saving started by pid ... fork operation may be slow就说明fork已经对主线程产生了明显影响。更精确地,可以通过INFO persistence查看rdb_last_fork_usec字段:
redis-cli INFO persistence | grep rdb_last_fork_usec这个字段的单位是微秒,代表上一次fork的耗时。如果这个数值持续大于几万到几十万(也就是几十到几百毫秒),整个实例的阻塞风险就非常高了。
3.2 AOF刷盘策略和磁盘性能的真实影响
AOF持久化模式下,刷盘策略是个老生常谈但依然坑多的话题。appendfsync有三个取值:always、everysec、no。三者的差异我在下面列出来:
| 刷盘策略 | 行为 | 阻塞风险 | 数据丢失窗口 |
|---|---|---|---|
| always | 每次写命令执行后都调用fsync | 最高,磁盘慢时每次写都变慢 | 最多丢失1条命令 |
| everysec | 每秒后台刷盘一次 | 较低,但刷盘瞬间磁盘慢也会拖累 | 最多丢1秒数据 |
| no | 完全交给操作系统 | 最低,内存中积压后统一落盘 | 可能丢几十秒到上百秒数据 |
很多团队为了数据安全会把appendfsync设为always,但如果底层的磁盘是普通的机械盘或者虚拟化环境里的共享存储,每次fsync的耗时可能达到几十毫秒。这种情况下主线程写入越频繁,延迟就越夸张,而且这类问题同样不会出现在慢查询日志里。当你排除掉命令层问题又看到“写入延迟和磁盘IO读写量成正相关”时,基本上就是always模式在作祟了。
我的建议是:如果数据库本身已经能保证状态可重建,appendfsync设为everysec已经能满足绝大多数场景的数据安全需求;如果一定要用always,请务必保证底层磁盘是SSD,并且单独监控fsync耗时。
3.3 大内存页与内核参数的坑
除了fork本身,Linux内核的某些默认配置也会放大持久化阻塞。最常见的就是透明大页(Transparent Huge Pages,THP)。THP本来是内核用来减少TLB miss的优化,但在Redis场景下它是个彻头彻尾的坑。
原因在于:当Redis fork子进程后,主进程仍然在继续写入,触发写时复制(copy-on-write)时,如果启用了THP,内核默认按2MB的大页粒度复制内存页,而不是常规的4KB小页。这就意味着只需要修改一个4KB的数据,系统可能也要复制整块2MB的内存页,COW的开销被放大了几百倍,内存占用和CPU都会飙升。
线上比较稳妥的做法是直接关闭THP:
echo never > /sys/kernel/mm/transparent_hugepage/enabled同时为了确保重启后依然生效,要把它写进系统启动脚本或crontab里。另外一个重要内核参数是vm.overcommit_memory,如果它是0或者2,Redis在内存偏紧时fork可能直接失败,日志里会出现:
Can't save in background: fork: Cannot allocate memory这会导致RDB备份失败,同时主线程反复尝试fork反而加重阻塞。建议将vm.overcommit_memory设置为1,允许进程申请超过物理内存的虚拟内存空间,让fork更大概率成功,因为fork只是复制页表,不会立刻分配全部物理内存。
4. 内存紧缺与操作系统层面的隐形阻塞:swap、淘汰和碎片
4.1 swap的恐怖延迟:为什么CPU不高却卡顿
这是我认为Redis所有阻塞里最阴险的一种:swap。当Linux物理内存不足时,会把一部分内存页交换到磁盘上,Redis的数据是全部驻留在内存的,一旦被swap出去,访问那块数据时就会触发磁盘IO,延迟从微秒级变成毫秒甚至秒级。
为什么说它阴险?因为CPU可能完全不高,内存used_memory看起来也没满,业务却时不时卡顿。其实数据已经有一部分被换到磁盘上了,Redis自己并不知道,它只知道访问那些数据特别慢。我遇到过一次真实情况:某项目实例的物理内存是8GB,Redis maxmemory设成7GB,又跑了一些其他进程,最后内存不够开始swap,结果P99延迟从1ms涨到300ms,查了三天没头绪,后来用vmstat看到si和so两个字段持续非零,才锁定swap。
排查swap的命令组合:
# 查看整体swap情况 free -h # 查看具体进程swap占用(Redis主进程PID在这里替换) grep VmSwap /proc/<redis_pid>/status # 动态观察换入换出 vmstat 1vmstat输出里的si(swap in)和so(swap out)只要持续非零,就是明确的swap信号。严格来说,线上Redis环境应该禁用swap或者至少设置swappiness极低。svm.swappiness建议设置成0或1,让系统尽量不主动使用swap。另外,把maxmemory设置得比物理内存余量小得多一些,给系统预留足够空间,才是根本方案。
4.2 内存淘汰策略在极端场景下的卡顿
Redis在达到maxmemory后会根据淘汰策略清理键,常用的有allkeys-lru、allkeys-lfu、volatile-lru等。理论上淘汰一个键的耗时很低,但有两个场景会让它演变成阻塞事故。
第一个场景是淘汰大Key。如果所有键都比较大,淘汰时需要释放大块内存,释放操作本身会占用主线程时间。我曾经见过一个实例里全是大String(单个1MB以上),内存打满后Redis开始频繁淘汰,每次淘汰都能看到几十毫秒的延迟尖刺。
第二个场景是淘汰和过期同时发生。当实例到达内存上限,又恰好有一批过期键集中到期,Redis既要执行过期删除又要执行内存淘汰,两件事叠加在单线程里处理,瞬间的CPU占用会非常高。
排查内存淘汰问题,可以在INFO stats里看evicted_keys字段的增长情况:
redis-cli INFO stats | grep evicted_keys如果这个值在延迟期间快速增长,基本就能确认淘汰策略是主因。优化手段包括:把大Key拆小,避免淘汰大对象的开销;合理设置maxmemory,不要把实例压到临界点运行;根据业务读取特征选择更合适的淘汰策略,比如allkeys-lfu在冷热分明的场景下淘汰更聪明,可以减少频繁淘汰。
4.3 从INFO memory判断内存健康度
我每次做Redis体检都会重点看INFO memory里的几个指标:
- used_memory:Redis实际使用的内存。
- used_memory_rss:进程在操作系统里实际占用的物理内存。
- mem_fragmentation_ratio:碎片率,等于used_memory_rss / used_memory。
- maxmemory:实例内存上限。
碎片率超过1.5说明内存碎片明显偏多,这会导致实际物理内存占用远超实际数据大小,间接引发swap风险。碎片整理的参数activedefrag可以在系统空闲时开启,但开启后本身会增加CPU开销,建议只在碎片率持续偏高且业务低峰时启用。
另一个值得关注的问题是长时间运行后内存分配器(默认是jemalloc)内部碎片和资源池膨胀,导致used_memory明明不高,但RSS却一直上涨。这种情况最直接的解法就是主动规划一次主从切换或滚动重启,让进程重新获得干净的内存布局。
5. 主从复制与集群动作引发的“连锁阻塞”
5.1 全量复制时的fork与消耗
主从复制过程中的全量同步,是Redis持久化之外另一个常见的fork触发点。当从库第一次连接或者复制偏移量落后太多时,主库会触发bgsave生成RDB快照,然后通过网络全量传给从库。这个bgsave的fork阶段就会造成主线程暂停,前面讲的fork耗时问题在这里完全适用。
如果主库本身内存已经很大,比如几十GB,那全量同步基本等于主动制造一次秒级阻塞。更糟的情况是多从库同时触发全量同步,主库要连续fork多次,或者多个fork进程同时存在,CPU和磁盘IO都会被拉满,主库的写延迟会变得惨不忍睹。
我处理过一个具体案例:某个实例有3个从库,某天网络抖动让两个从库同时断连,接着同时开始全量复制,主库在几分钟内连续fork两次,每次耗时都在400毫秒以上,业务侧在那一分钟里出现了明显的超时告警。后来我们限制了同一时间内只允许一个从库重连,并且把repl-backlog-size调大,减少了断连后落到全量复制的概率。
5.2 主从切换和复制风暴对主库的冲击
主从切换本身不是阻塞,但切换后往往会引发复制风暴。哨兵或集群发起failover后,新的主库需要和它下面的从库同步数据;如果从库数量很多,且没有良好的复制拓扑,一个主库带着多个从库同时开始全量同步,压力全部压在新主库上。这种情况下你看到的表现可能不是某一条慢命令,而是整个实例延迟整体抬升。
集群场景下的槽位迁移则是另一个阻塞源。Redis Cluster迁移数据时,会多次执行MIGRATE命令,这个命令是同步的,主线程需要把一个Key序列化、发送给目标节点、等待目标节点返回后再处理下一个请求。如果待迁移的Key很大,或者需要迁移的Key数量很多,每个MIGRATE操作都要占用主线程,业务请求在这些间隙里被拖延。
我的经验是,做集群扩容和槽位迁移务必选择业务低峰期,并且把迁移的批量参数调小,不要一次搬太多数据。同时在迁移窗口期密切观察主库的LATENCY和CPU,一旦延迟尖刺明显就暂停迁移,让实例喘口气。
6. 网络与慢客户端:阻塞不只在Redis内部
6.1 慢客户端如何吃掉服务端缓冲区
很多时候Redis阻塞的根源不在服务端的处理逻辑,而在客户端的一侧。这里的“慢客户端”不是指慢查询命令,而是指那些连接了Redis却迟迟不读取返回数据的客户端连接。
Redis为每个客户端维护出站缓冲区,如果某个客户端一直不读取数据,服务端就必须在缓冲区里不断堆积返回结果。堆积越来越多,占用的内存越来越大,同时主线程在拷贝和发送数据时花费的时间也越来越长。极端情况下,这些慢客户端能吞掉大量内存,间接触发swap和淘汰,甚至让Redis直接进入不可服务状态。
用以下命令可以查看每个客户端的输出缓冲区状态:
redis-cli CLIENT LIST在输出里关注obl、oll、omem字段,omem表示输出缓冲区占用的内存。如果某些连接的omem一直居高不下,或者某个客户端连接数异常膨胀,很可能就是它在拖慢整个Redis。适当调整client-output-buffer-limit参数可以控制风险,但这只是止损,更要从业务侧排查为什么客户端消费消息这么慢,比如订阅者有阻塞的消费逻辑。
6.2 Redis 6 IO多线程的真实定位
很多人以为Redis 6引入的多线程IO能彻底解决阻塞问题,这是一个很大的误解。Redis 6.0的io-threads只是把socket上的网络读写分散到多个IO线程,但命令执行的核心逻辑仍然在单线程主线程中串行执行。也就是说,它能改善高并发连接下网络读写带来的开销,但如果你有一个大Key慢命令,照样会把主线程卡死。
所以不要把多线程IO当成万能药。它主要解决的是那种“连接数非常多但单条命令都很快”的场景,比如某些网关服务建立了几万个连接,网络事件的处理占用了主线程大量时间。配置时也不要盲目开太多IO线程,一般2到4个就够,开太多反而会因为锁竞争和上下文切换带来额外开销。
7. 线上阻塞的实战排查链路:从现象到根因
7.1 第一步:用redis-cli和内置指标确认阻塞范围
我现在接到Redis延迟异常的第一反应,不是翻代码,而是先用工具确认阻塞范围。第一步永远是用redis-cli直接测到实例的端到端延迟。
redis-cli -h <host> -p <port> --latency -i 1这个命令会每秒发一个PING,持续统计延迟分布。如果你执行后看到avg正常但偶尔出现几百毫秒的毛刺,说明问题就在Redis或它的宿主机上;如果avg本身就很高,那先检查你自己这台客户端和目标服务器之间的网络链路。另外还可以用--intrinsic-latency测一下这台机器本身的内核延迟基线:
redis-cli --intrinsic-latency 100如果主机本身的延迟就很高,那问题可能出在宿主机CPU争抢或虚拟化层,这时候再排查Redis意义也不大。
7.2 第二步:慢日志、命令统计与耗时分布分析
确认阻塞在Redis侧之后,我习惯按“命令层→持久化层→系统层”的顺序逐层排除。
命令层先看慢日志:
redis-cli SLOWLOG GET 50如果慢日志里有大量的耗时命令,那直接顺着命令去代码里找来源。如果慢日志空荡荡,不要慌,这很可能说明阻塞不在命令执行本身。接着看命令统计和状态计数:
redis-cli INFO commandstats redis-cli INFO stats重点看两个方向:一是有没有哪个命令的调用次数和耗时异常高,二是expired_keys和evicted_keys在延迟尖刺时间段内是否出现突增。如果expired_keys突增,那就是集中过期问题;如果evicted_keys突增,那就是内存淘汰问题。
7.3 第三步:持久化与系统层的交叉验证
命令层没发现问题时,立刻看持久化和系统层。先用INFO persistence确认fork耗时:
redis-cli INFO persistence如果rdb_last_fork_usec耗时很大,再把焦点放到cron里是不是正好有RDB定时保存,或者从库是不是正在发起全量复制。如果需要同时评估系统层,我会开三个终端分别跑:
# 终端1:观察CPU和内存以及swap vmstat 1 # 终端2:观察磁盘IO iostat -x 1 # 终端3:定位Redis进程CPU和内核态占比 top -H -p <redis_pid>如果vmstat里si/so非零,说明swap正在发生;如果iostat里util接近100%,说明磁盘IO瓶颈;如果top里Redis的主线程CPU占用异常高且内核态占比大,可能是COW(写时复制)或网络发送在大量消耗CPU。
7.4 一套可复用的排查命令组合
为了降低下次排查的记忆成本,我把整套流程沉淀成一个可以直接照抄的排查序列,你按顺序执行基本能覆盖90%的阻塞场景:
# 1. 延迟基线 redis-cli -h <host> -p <port> --latency -i 1 # 2. 慢日志 redis-cli -h <host> -p <port> SLOWLOG GET 30 # 3. 命令统计和状态计数 redis-cli -h <host> -p <port> INFO commandstats redis-cli -h <host> -p <port> INFO stats # 4. 持久化耗时 redis-cli -h <host> -p <port> INFO persistence # 5. 系统和进程指标 vmstat 1 iostat -x 1 top -H -p <redis_pid> # 6. 连接与客户端状态 redis-cli -h <host> -p <port> CLIENT LIST redis-cli -h <host> -p <port> INFO clients再补充一个非常实用的技巧:如果要定位“间歇性毛刺”,可以将latency monitor打开,配置一个阈值,比如100毫秒:
redis-cli CONFIG SET latency-monitor-threshold 100 redis-cli LATENCY LATEST这样Redis会自己记录延迟事件的类型和耗时,包括fork、expire-cycle、aof-write这些内部事件。很多之前靠肉眼看日志根本定位不到的问题,用LATENCY LATEST一眼就能看到阻塞发生在哪个环节。
我曾经排查过一个案例就特别典型:某服务每天20:05左右准时卡两秒,慢日志空白,CPU不高。打开latency monitor后,发现事件类型是command,说明确实是命令执行导致,但慢日志为什么没记录?后来才找到原因,是某个客户端用MONITOR命令开启了全量命令流监控,MONITOR本身会显著增加服务端开销,导致大量命令的执行时间被拉长,但慢日志只记录“命令本身计算耗时”,没考虑监控带来的开销。这提醒我一件事:MONITOR是排查工具也是阻塞源,不要在线上长时间开,用的时候必须掐秒表。
8. 我的预防清单:踩过坑之后的配置与习惯
写到最后,把多年踩坑积攒的预防措施列一份清单。如果说前面的章节是“治病”,这一部分的思路就是“防病”。
第一,配置层面,我强烈建议每条线上实例都落实下面几项:
- 禁止危险命令:通过rename-command把KEYS、FLUSHALL、CONFIG等在业务侧禁掉,或者只允许运维通道使用。
- 设置maxmemory:必须设置,并留足系统余量,不要让Redis进程无限吃内存导致swap。
- 关闭THP:echo never > /sys/kernel/mm/transparent_hugepage/enabled,写进开机自启。
- 设定合理的slowlog阈值和latency monitor阈值:我一般用slowlog-log-slower-than 5000,latency-monitor-threshold 100。
- appendfsync推荐everysec,并确保磁盘为SSD。
- 如果启用了Redis 6的IO多线程,线程数保守一点,2到4个足够。
第二,开发协作层面,要把“禁止大Key、禁止O(N)命令、过期时间加随机扰动”写进代码规范和评审清单。很多时候线上出问题不是Redis不行,而是使用方式出了问题。比如某次事故的根源就是业务方为了取一个统计值,在一个几百万元的Hash上执行了HGETALL,这种代码在数据量小的时候毫无感知,数据涨起来就变成定时炸弹。
第三,监控层面,除了常规的CPU、内存、网络,还要专门盯几个Redis专属指标:延迟P99、慢日志条数、evicted_keys、expired_keys的速率、rdb_last_fork_usec、客户端输出缓冲区omem峰值。这些指标配合告警,可以让你在阻塞还没造成大规模故障的时候就先发现问题。
根据我个人的经验,90%的Redis阻塞都不是Redis本身坏了,而是使用方式或周边环境里那些不起眼的小问题被数据量放大成了事故。每次排查完阻塞问题,我都会习惯性地把“根因+修复方案+如何提前发现”记下来,下次再遇到类似延迟尖刺,就能直奔主题,少走很多弯路。希望这篇总结也能给你一些启发,让你下次面对Redis卡顿的时候,多一条快速定位的思路。