☰
Redis变慢怎么办?从慢查询到大Key的完整排查指南
2026/10/3 3:38:32 网站建设 项目流程

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-len

slowlog-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 和热 Key:Redis 变慢的头号嫌疑人

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_key

UNLINK 是异步删除,它的原理是先把 key 从键空间中移除,真正释放内存的操作放到后台线程去做,主线程不会被卡住。我在线上清理过不少几个 GB 的 hash,用 DEL 的时候 Redis 直接红了,换成 UNLINK 之后就完全没有波动。

另一个衍生场景是:大 Key 过期也是大坑。过期策略里 lazyfree-lazy-expire 如果没开,过期删除也是同步的,一样会卡住。所以建议在配置里加上:

lazyfree-lazy-expire yes lazyfree-lazy-eviction yes lazyfree-lazy-server-del yes

3.3 热 Key 的排查与处理

热 Key 是另一个高频杀手,指某个 key 在短时间内被超高并发访问,比如某个爆款商品、热门新闻的缓存。热 Key 的问题在于它把压力全部压在一个 Redis 节点上,单线程的模式下,一个热 Key 就能把 CPU 打满。

排查热 Key 的方式有几个。Redis 4.0 以上可以用redis-cli --hotkeys,它依赖 LFU 淘汰策略,如果 maxmemory-policy 不是 LFU 相关的,这个命令会直接报错。不满足条件的,可以用monitor命令抓一段时间内高频的 key:

timeout 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-policy

maxmemory如果不设置,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 的日志级别是 debug,fork 期间产生的日志更多,进一步延长阻塞时间。

排查方式:看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默认 100,auto-aof-rewrite-min-size默认 64MB。如果你的写入量大,AOF 文件膨胀得很快,重写会被频繁触发,每次重写都要 fork 子进程和写临时文件,带来一波新的阻塞。线上把 min-size 调大,比如 4GB,能明显减少重写频率。

5. 系统层面的较量:网络、CPU 与 Swap

5.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 子进程生成 RDB,fork 期间主线程阻塞,此时所有请求都堆积了。阻塞结束后,堆积的命令同时涌入,又触发新的慢查询,形成了恶性循环。

这就是典型的多因素叠加事故:大 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 > 100000(100ms)fork 阻塞直接导致请求排队,必须关注
连接数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 2000,slowlog-max-len 1024。Log 和监控这两个东西,就是线上事故的逃生通道,投入产出比极高。

在最后再分享一个小技巧:排查完之后,把当时的INFO everything输出完整保存一份。Redis 的 troubleshooting 很多时候不是当下找不到原因,而是事后要对照之前的数据才能发现趋势。有了快照,下次再出类似问题,你几分钟就能定位,而不是从零开始。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询