ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis变慢怎么办?从慢查询到大Key的完整排查指南

Redis变慢怎么办?从慢查询到大Key的完整排查指南 Redis 突然变慢这事但凡在线上扛过事儿的都懂。最怕的不是慢是你打开监控一看什么都是绿的但接口就是卡在那里用户已经在群里开喷了。尤其像 Redis 这种平时快得跟闪电一样的东西一旦响应时间开始往上走往往不是某一个点出了问题而是一连串因素叠加。我经历过几次半夜被拉起来排查 Redis 的事故今天把完整的排查思路和实操命令整理出来照着一步步来能省下大把瞎猜的时间。本文面向的是线上环境出问题需要快速定位的人也适合准备面试想系统梳理 Redis 性能排查思路的同学。内容覆盖慢查询、大 Key、热 Key、持久化、内存淘汰、网络和系统层基本把 Redis 变慢的常见路径都走了一遍。1. 别急着怀疑 Redis先把现象界定清楚1.1 第一步永远是确认慢到底慢在哪很多人在 Redis 变慢时第一反应就是冲过去重启或者直接清缓存。这俩操作我都做过基本上都是白费劲甚至会把现场搞丢。正确做法是先把慢这个模糊的说法变成可量化的指标。你需要搞清楚三个时间客户端发出命令到收到响应的时间Redis 内部处理命令的时间以及网络传输的时间。有个很常见的误导场景应用自己所在机器的 CPU 被打满或者连接池被占满请求根本没发到 Redis 那边但你在客户端测到的耗时就是很高。再比如如果应用和 Redis 之间隔了 LB、代理或者 Docker 网络这些中间环节的抖动也会表现为 Redis 变慢。判断方法不复杂。直接在 Redis 服务器本机用 redis-cli 执行redis-cli -h 127.0.0.1 -p 6379 ping连续压几十次看延迟。如果在本机 ping 都是毫秒级但客户端侧延时很高那问题大概率不在 Redis 本身而在网络链路或者客户端代码。反过来如果本机 ping 本身就有几十毫秒甚至更高那 Redis 内部肯定有问题继续往深挖。1.2 先看的三个基础指标确定问题在 Redis 内部之后我会先拉三个东西INFO server、INFO clients、INFO stats。别用那种甩锅式的看一眼监控这三个命令的字段信息量很大。INFO clients里看 connected_clients 和 blocked_clients。connected_clients 如果突然比平时多出好几倍一般是有客户端没走连接池或者连接没释放。blocked_clients 大于 0 说明有客户端卡在阻塞命令上比如 BLPOP、BRPOP这也是排查方向。INFO stats里重点看 instantaneous_ops_per_sec 的波动和 total_commands_processed 的增长斜率。如果 ops 没涨但延迟涨了说明是 Redis 内部处理能力下降如果 ops 涨了延迟也涨了那是流量变大导致的正常压力重点查大 Key 和慢查询。另外一定要看INFO replication。主从全量同步的时候主节点要 fork 子进程生成 RDB这是非常经典的阻塞点。如果发现 master 上有正在进行的 SYNC同时延迟升高多半就是这里的问题后面的持久化章节会详细说。2. 慢查询和延迟监控Redis 自己的体检报告2.1 slowlog 是最直接的证据Redis 有个专门的慢查询日志机制用来记录执行时间超过阈值的命令。这个机制太实用了很多线上问题靠它一抓一个准。先看阈值的配置redis-cli config get slowlog-log-slower-than redis-cli config get slowlog-max-lenslowlog-log-slower-than单位是微秒默认 10000也就是 10 毫秒。对于 Redis 这种微秒级响应的东西10 毫秒的阈值其实已经很高了线上我一般会调到 2000 微秒也就是 2 毫秒这样才能捕捉到那些还行但不够快的命令。查看慢查询的命令redis-cli slowlog get 20输出的每条记录包含时间戳、执行耗时、命令参数。注意slowlog只记录命令的执行时间不包括网络传输和排队等待时间所以它反映的是 Redis 内部真正干活的时间。如果你发现某个 KEYS 命令耗时 800 毫秒那就是典型的全表扫描问题。有一点必须提醒slowlog默认只保存在内存里重启就没了而且slowlog-max-len默认 128 条量大的时候很快被覆盖。所以发现问题时第一时间把慢查询拉出来存到文件别干等。2.2 Latency Monitor 能测到更隐蔽的延迟慢查询只能抓到命令级别的耗时但有些延迟不是某个命令造成的而是 Redis 事件循环被整个卡住。比如 fork 子进程的时间过长、AOF 刷盘卡住、内存大页导致的阻塞等这时候命令本身可能不慢但整体延迟突然飙高。Redis 提供了LATENCY命令来监控这类事件。先开启监控然后查看redis-cli config set latency-monitor-threshold 100 redis-cli latency latest redis-cli latency history这里的 100 单位是毫秒意思是任何超过 100ms 的事件都会被记录下来。latency latest输出的事件类型包括 command、fork、aof-write、rdb-unlink-temp-file 等。我最关注的是 fork 和 aof-write 这两个因为在生产环境里它们导致的延迟最容易让人摸不着头脑。我之前排过一次诡异的问题所有命令的耗时都正常但客户端就是偶尔会卡几百毫秒。开了 latency monitor 之后发现 fork 事件耗时 400 多毫秒一查才发现是物理机的内存页太多fork 子进程时页面表复制时间过长。这个后面在持久化部分展开讲。3. 大 Key 和热 KeyRedis 变慢的头号嫌疑人3.1 怎么快速定位大 Key大 Key 指的就是单个 key 的 value 特别大。比如一个 hash 里有几百万个字段一个 list 里有几十万个元素或者一个字符串 value 达到了几十 MB。大 Key 是个慢动作杀手它不会让所有请求变慢但会拖垮访问它的那些线程。定位大 Key 的办法一个是 Redis 自带的--bigkeys参数redis-cli --bigkeys --i 0.01--i 0.01表示每扫描 100 个 key 停顿 0.01 秒再继续这是为了避免扫描本身成为新的性能问题。这个命令会遍历整个 Redis 键空间统计出每种数据类型里最大的几个 key。但它的问题是只统计每个类型里最大的那个如果你想找出所有超过某个大小的 key得自己写脚本。更精准的方式是MEMORY USAGE命令redis-cli memory usage your_key_name这个命令返回的是这个 key 在 Redis 内存中实际占用的字节数。可以在排查时用redis-cli --scan --pattern *遍历所有 key逐个判断大小。注意MEMORY USAGE对嵌套的数据结构算得比较准但也会消耗一点 CPU所以不要在生产高峰期大规模用。再补充一个思路从 RDB 文件离线分析大 Key。你可以用redis-rdb-tools这种工具在从节点上做。把从节点的 RDB 拿下来分析不打扰主节点这是大 Key 体检最安全的方式。3.2 大 Key 为什么会让 Redis 变慢大 Key 的问题通常体现在三个层面。第一操作大 Key 本身很耗时。比如对一个有几十万元素的 list 执行 LRANGE 全量查询Redis 是单线程的这个命令执行期间其他所有命令都得等着。只要这种慢命令的比例上来了整个实例的吞吐量就下来了。第二大 Key 会导致内存碎片化和持久化开销增加。RDB 持久化时要遍历所有 key大 Key 会拖慢整个 RDB 生成过程AOF 重写也会因为大 Key 而变得迟钝。更麻烦的是大 Key 占了大量内存一旦触达内存上限后面讲的内存淘汰策略会引发雪崩式的延迟。第三删除大 Key 本身就会造成阻塞。Redis 在删除超大集合时如果一次性 DEL这个操作要释放成百上千兆内存期间 Redis 直接卡死。正确操作是使用 UNLINK 命令redis-cli unlink your_big_keyUNLINK 是异步删除它的原理是先把 key 从键空间中移除真正释放内存的操作放到后台线程去做主线程不会被卡住。我在线上清理过不少几个 GB 的 hash用 DEL 的时候 Redis 直接红了换成 UNLINK 之后就完全没有波动。另一个衍生场景是大 Key 过期也是大坑。过期策略里 lazyfree-lazy-expire 如果没开过期删除也是同步的一样会卡住。所以建议在配置里加上lazyfree-lazy-expire yes lazyfree-lazy-eviction yes lazyfree-lazy-server-del yes3.3 热 Key 的排查与处理热 Key 是另一个高频杀手指某个 key 在短时间内被超高并发访问比如某个爆款商品、热门新闻的缓存。热 Key 的问题在于它把压力全部压在一个 Redis 节点上单线程的模式下一个热 Key 就能把 CPU 打满。排查热 Key 的方式有几个。Redis 4.0 以上可以用redis-cli --hotkeys它依赖 LFU 淘汰策略如果 maxmemory-policy 不是 LFU 相关的这个命令会直接报错。不满足条件的可以用monitor命令抓一段时间内高频的 keytimeout 30 redis-cli monitor hotkey.log然后统计这个日志里最常出现的 key。但注意monitor 这个命令在高流量下会对性能造成额外压力生产环境谨慎使用建议在低峰期或者直接抓 10 到 30 秒就停。处理热 Key 的常规手段包括在客户端做本地缓存也就是多级缓存把热 Key 加上随机后缀分散到多个节点对于读多写少的场景用读写分离架构把流量分散到从节点。我在工程上最常用的就是本地缓存比如缓存商品详情时在应用里放一层 Caffeine热 Key 失效了再回源到 Redis这样 Redis 的压力一下子就降下来了。4. 内存和持久化最容易忽略的系统性瓶颈4.1 内存淘汰策略引发的连锁反应先看当前配置redis-cli config get maxmemory redis-cli config get maxmemory-policymaxmemory如果不设置Redis 会用光系统内存最终被内核 OOM Kill。设置了之后当内存写满Redis 会根据淘汰策略开始干活。关键坑点是allkeys-lru和allkeys-random这类策略在内存快满的时候每次写入新 key 都要扫描采样尝试淘汰旧 key这个操作是同步的而且如果淘汰了一批设置了过期时间但还没真正到期的 key既不经济又引入延迟。更典型的坑是volatile-lfu这类策略配合访问模式的抖动可能导致key被频繁淘汰又被打回来形成缓存穿透和延迟抖动。另外一个是内存碎片的问题。当你大量删除和写入变化了大小的 value内存碎片率used_memory_rss / used_memory会升高。查看命令redis-cli info memory | grep mem_fragmentation碎片率超过 1.5 说明碎片很多内存分配器需要更多时间做分配管理增加 CPU 开销。最直接的处理方式是重启但重启有风险临时方案是用MEMORY PURGE命令手动整理或者干脆在低峰期做一次主从切换。4.2 RDB 持久化导致的主线程阻塞这个点我吃了大亏所以拿出来单独讲。RDB 快照的生成原理是 fork 一个子进程子进程复制父进程的页表后开始遍历内存写快照。fork 本身是个昂贵操作内存越大页表复制越慢。fork 期间主线程是被阻塞的虽然只持续几十到几百毫秒但在高 QPS 的场景下这段时间的请求全部会排队反映到客户端就是毛刺。还有一个隐藏点如果 Redis 的日志级别是 debugfork 期间产生的日志更多进一步延长阻塞时间。排查方式看INFO persistence里的latest_fork_usec它记录最近一次 fork 的微秒耗时。我见过 fork 耗时 300 多毫秒的实例因为那台机器内存被 Redis 占了三十多 GB页面表巨大。优化措施包括尽量使用物理机而不是小规格虚拟机避免内存页表过度膨胀。关闭 THP透明大页用echo never /sys/kernel/mm/transparent_hugepage/enabled临时关闭或者写入 /etc/rc.local 让重启后依然生效。THP 开启会导致 fork 后 copy-on-write 时按大页粒度复制内存延迟飙升。用repl-diskless-sync yes配合主从复制时的无盘同步减少磁盘 IO 压力。4.3 AOF 刷盘策略的坑AOF 持久化的刷盘策略有三种always、everysec、no。always是每个命令都刷到磁盘延迟最高吞吐量下降明显。no是交给操作系统决定何时刷盘数据安全性差。everysec是折中方案每秒刷一次盘。实测下来很多团队线上用的是默认的everysec但没注意它的一个副作用如果磁盘 IO 慢或者磁盘饱和AOF 的刷盘线程会把阻塞传导到主线程。体现为 Redis 延迟周期性飙升。检查 AOF 相关状态redis-cli info persistence | grep aof_last_write重点看aof_last_write_status是不是 ok以及aof_delayed_fsync是不是在增长。如果 fsync 延迟很高说明磁盘性能跟不上。处理方式确认磁盘类型不要用共享型云盘或网络磁盘必要时把 AOF 放到单独的磁盘如果业务对持久化要求不是极端苛刻可以调整no-appendfsync-on-rewrite yes避免在 AOF 重写期间反复刷盘。还有一种常见情况是 AOF 重写太频繁。auto-aof-rewrite-percentage默认 100auto-aof-rewrite-min-size默认 64MB。如果你的写入量大AOF 文件膨胀得很快重写会被频繁触发每次重写都要 fork 子进程和写临时文件带来一波新的阻塞。线上把 min-size 调大比如 4GB能明显减少重写频率。5. 系统层面的较量网络、CPU 与 Swap5.1 网络带宽被打满的外部表现应用连不上 Redis或者延迟很高有时根本原因不在 Redis 进程而是物理机网卡带宽被占满。尤其是 Redis 大 value 特别多的时候多几次全量查询就能把千兆网卡打爆。判断方式登录服务器用iftop或nload看实时带宽也可以用sar -n DEV 1 5看历史趋势。如果确认带宽打满检查是不是有主从全量同步、AOF 重写、或者数据迁移任务在同一时刻运行。另外大 Key 的响应本身就会占用几十 MB 的出口流量这时候你该做的不是换带宽而是先干掉大 Key。5.2 CPU 飙升和上下文切换Redis 是单线程模型但并不意味着它只用一个 CPU。如果 Redis 的 CPU 使用率被占满通常是命令执行效率太低或者热点太集中。而如果整体系统 CPU 高但 Redis 进程占用不高检查是不是有其他进程在抢资源尤其是同一台物理机上还跑了其他服务。查看进程级的线程上下文切换pidstat -w -p redis_pid 1上下文切换次数暴涨说明线程频繁让出和抢占 CPU很多时候跟 GC 线程、内存分配器的锁竞争有关。另外Redis 6.0 之后引入了多线程 IO在io-threads开启的情况下网络 IO 的读和写可以由多个线程分担但注意命令执行依然是单线程的。如果开启了这个特性监控的时候别被 CPU 多核占用迷惑了。5.3 swap 问题最容易忽略的隐性炸弹物理内存不够时内核会把部分内存数据交换到磁盘也就是 swap。Redis 进程如果发生了 swap它的性能会断崖式下跌。最坑的是这种问题用 top 都未必一眼看得出来的。排查 swap 用这个redis-cli info memory | grep used_memory # 对应的物理内存占用 ps -eo pid,rss,args | grep redis-server对比 Redis 的 used_memory逻辑内存占用和 RSS实际驻留物理内存如果 RSS 远小于 used_memory说明有一部分 Redis 内存被 swap 出去了。更严格的检查是cat /proc/redis_pid/smaps | grep -i swap如果 swap 数量不为零Redis 的某些内存页已经在磁盘上访问这些页时的速度比读 DSSD 还慢任何命令都可能突然变得龟速。此时要做的不是优化 Redis 配置而是调整实例内存上限给系统预留足够物理内存再关掉不必要的 swap 优先级。我重申一遍swap 对 Redis 是慢性毒药别让 Redis 的内存设置超过物理内存的一半除非你很清楚自己在做什么。6. 从现象到定位一次真实排查记录6.1 一个典型的乱局复盘有一次线上告警业务反馈所有读 Redis 的接口平均耗时从 2ms 涨到了 500ms部分请求直接超时。我先用了 1.2 和 2.1 的方法发现本机 ping 的延迟其实只有 1.5ms先排除了 Redis 自身卡死。然后拉 slowlog发现有一条SMEMBERS命令耗时 1.2 秒作用于一个几百万元素的 set。紧接着用redis-cli --bigkeys扫描确认这是最大的一个集合。但问题来了这个 set 并不是每个请求都访问为什么全局延迟被拖垮继续查INFO stats发现sync_full和sync_partial_ok很短的时间内快速增长说明主从复制出现了全量重传。全量同步要 fork 子进程生成 RDBfork 期间主线程阻塞此时所有请求都堆积了。阻塞结束后堆积的命令同时涌入又触发新的慢查询形成了恶性循环。这就是典型的多因素叠加事故大 Key 是基础病灶主从复制是引爆点。处理方式分三步先用 UNLINK 异步清掉那个巨型 set再在主节点上临时调低repl-backlog-size和配置客户端超时避免网络抖动反复触发全量重同步最后在从节点用redis-rdb-tools备份分析避免再往主节点施加压力。6.2 怎么快速定位是哪个客户端在捣乱很多时候 Redis 本身没病是客户端使用姿势有毛病。比如客户端把超时时间设得特别大慢命令的超时没有兜底连接池没有设置最大等待时间线程全部阻塞在获取连接上或者使用了类似KEYS *这种命令虽然用的人少一用就出事。定位方法可以结合INFO clients里的client_recent_max_input_buffer和client_recent_max_output_buffer这两个指标。如果最大输出缓冲区特别大说明有客户端一直在拉长列表或大集合的数据它会占住 Redis 的响应通道。再用CLIENT LIST命令看看连接来源 IP 分布redis-cli client list | awk {print $2} | sort | uniq -c | sort -nr | head -20如果某个 IP 的连接数异常多去查那个应用节点的代码。我碰到过一个生产事故是某个服务的连接池设置太大默认 300 连接多个实例加起来把 Redis 的连接数打到了上万光是调度这些连接的空闲检测就把 CPU 吃掉了不少。6.3 一套直接可用的监控告警配置排查做得再好不如提前预防。我结合多年经验整理一套告警配置基本覆盖了 Redis 变慢的主要诱因。监控指标告警阈值说明慢查询数每分钟慢查询数超过 20慢查询是延迟的直接信号建议采样周期 1 分钟内存碎片率mem_fragmentation_ratio 1.5碎片率过高会导致性能下降和内存浪费fork 耗时latest_fork_usec 100000100msfork 阻塞直接导致请求排队必须关注连接数connected_clients 超过告警基线 2 倍连接数暴涨往往是客户端异常或流量突增主从同步延迟master_repl_offset 与 slave 差大于 1000同步延迟会导致从节点数据过期业务读从机时出现旧数据实例内存占比used_memory / maxmemory 85%接近淘汰上限随时可能触发淘汰风暴告警工具可以用 Prometheus 加 redis_exporter这个 exporter 会把上面这些指标全部拉出来配合 Grafana 做可视化。告警规则用 PromQL 写很简单比如increase(redis_slowlog_last_id[1m]) 20如果你们没有现成的监控体系先用 redis-cli 写个定时检查脚本也可以但远不如 exporter 方便。重点是别等到凌晨两点被叫醒才去查线上压测前就要把监控基线建立起来。7. 我沉淀下来的几条实战经验聊了这么多命令和思路最后分享几条我踩过坑之后攒下的经验。第一排查 Redis 慢顺序太重要了。我现在的标准顺序是先看本机 ping 排网络再看 slowlog 和 latency再看 bigkeys 和 hotkeys最后看持久化和系统层。这个顺序能最快排除掉不是问题的问题直奔真正的元凶。第二线上禁用高危命令烈度远比你想象的高。KEYS、SMEMBERS、HGETALL、LRANGE 0 -1 这种命令一个都不能在流量高峰期执行。我见过多次生产事故就是开发顺手在 Redis 里跑了个 KEYS 然后全接口超时。如果非要用可以配置 rename-command 禁用这些高危命令rename-command KEYS rename-command FLUSHALL rename-command FLUSHDB 第三任何关于 Redis 的性能优化都要先在预发环境验证。别信这个命令应该不会慢这种鬼话Redis 的行为在数据量大了之后和测试环境完全是两个世界。我每次做线上变更哪怕是改一个配置项都会先在压测环境里跑一轮流量再上。第四日志一定要留。很多团队 Redis 都没开慢查询日志也没接监控出事了全靠猜。其实只需要改两个配置就能把最关键的证据留下来slowlog-log-slower-than 2000slowlog-max-len 1024。Log 和监控这两个东西就是线上事故的逃生通道投入产出比极高。在最后再分享一个小技巧排查完之后把当时的INFO everything输出完整保存一份。Redis 的 troubleshooting 很多时候不是当下找不到原因而是事后要对照之前的数据才能发现趋势。有了快照下次再出类似问题你几分钟就能定位而不是从零开始。
返回列表