线上 Redis 突然“爆”了,怎么办?

凌晨三点,手机疯狂报警------Redis CPU 100%,内存快满了,业务接口一个个超时......

别慌,这篇文章不堆术语,我会像聊天一样,带你一步步排查问题、稳住现场、顺便把优化思路讲清楚。


一、先搞清楚"爆"是什么意思?

"机器爆了"通常指以下几种情况,不同情况处理方式不一样:

现象(你能看到什么) 最可能的原因
CPU 使用率 100% 请求太多、执行了慢命令(比如 KEYS *)、或者 Redis 在做持久化
内存快用完了 存了太多数据、很多 key 没设过期时间、或者内存碎片太多
连接数爆满 客户端程序没释放连接、连接池设置不合理
网络跑满 传输了大 key(比如一个 value 几 MB),或者一次取太多数据
响应变慢、卡顿 有慢查询、或者 Redis 在做 fork 操作(比如生成快照)

小解释

  • fork:Redis 保存数据到硬盘时,会复制自己一份(子进程),这个过程会短暂卡一下。
  • 大 key:指单个 key 对应的 value 非常大,比如一个 hash 里存了 10 万条数据。
  • 慢查询:执行时间超过预设阈值(比如 10 毫秒)的命令。

二、第一步:紧急"把脉"------查看当前状态

登录到出问题的机器,执行下面几条命令(不用记,收藏就行):

bash 复制代码
# 看 Redis 进程占用了多少 CPU 和内存
top -p $(pgrep redis-server)

# 连上 Redis 看关键指标
redis-cli INFO stats      # 每秒请求数、拒绝连接数等
redis-cli INFO memory     # 用了多少内存、碎片率
redis-cli INFO clients    # 当前有多少客户端连接
redis-cli SLOWLOG GET 10  # 抓出最近的慢命令

如果发现 SLOWLOG 里有一堆 KEYS *,那恭喜你,找到元凶了。下面我们专门讲它。


三、场景一:CPU 飙高 ------ 重点讲 KEYSSCAN

3.1 KEYS 命令:方便但危险

它有什么用?

KEYS pattern 可以查出所有匹配某个模式的 key。

比如你想找所有以 user: 开头的 key:

bash 复制代码
redis-cli KEYS "user:*"

它有什么问题?

它会一次性扫描整个数据库 ,把所有 key 都查一遍。如果库里有 1000 万个 key,这个命令可能要执行好几秒,期间 Redis 无法处理其他请求,CPU 瞬间爆满。生产环境禁止使用!

比喻:就像在一个巨大仓库里,不用索引,挨个箱子翻找所有带"用户"标签的箱子,而且翻完之前不能做任何其他事。

3.2 代替方案:SCAN 命令------温和的游标扫描

SCAN 像一条"可暂停的流水线":每次只取一小部分(比如 100 个),你需要不断重复调用,直到取完所有数据。虽然麻烦一点,但不会阻塞 Redis。

基本用法

bash 复制代码
# 第一次扫描:从游标 0 开始
redis-cli SCAN 0 MATCH "user:*" COUNT 100
# 返回:1) "下一轮的游标值"  2) ["user:1", "user:2", ...]

如果返回的游标是 0,说明扫描结束;否则用这个游标继续:

bash 复制代码
redis-cli SCAN <上一轮返回的游标> MATCH "user:*" COUNT 100

注意COUNT 只是建议每批返回的数量,不保证精确。通常设为 100~1000 即可。

代码示例(Python)

python 复制代码
cursor = 0
while True:
    cursor, keys = redis.scan(cursor, match="user:*", count=100)
    for key in keys:
        # 处理每个 key
        pass
    if cursor == 0:
        break

这样优化后 :原本一个 KEYS 造成的 5 秒阻塞,被拆成几十次毫秒级的扫描,CPU 平稳,用户几乎无感知。


3.3 其他 CPU 杀手及解决

问题命令 替代方案
HGETALL 大 hash HSCAN 分批取
SMEMBERS 大 set SSCAN 分批取
ZRANGE 取全量 加上 LIMIT 分页
SORT 操作 尽量在客户端或数据库做排序

另外如果整体 QPS(每秒请求数)太高,比如超过 5 万,考虑:

  • 加从库做读写分离(读请求走从库)
  • 使用 Redis Cluster 把数据分到多台机器

QPS:Queries Per Second,每秒查询次数。


四、场景二:内存快满了 ------ 先看谁吃掉了内存

4.1 快速找出"大 key"

Redis 自带了扫描大 key 的工具:

bash 复制代码
redis-cli --bigkeys

它会告诉你哪种类型的 key 占内存最多,比如:

  • 最大的 10 个 string
  • 元素最多的 hash / set / list

解决办法

  • 给 key 加上过期时间:EXPIRE keyname 3600(1 小时后自动删除)
  • 拆分大 key:比如把一个有 10 万字段的 hash,拆成 100 个小 hash,按 id 分段
  • 压缩 value:存 JSON 之前先用 gzip 压缩(会略微增加 CPU,但省内存)

4.2 内存碎片是什么?怎么清理?

解释 :你删掉了 10 个 key,但 Redis 并没有把内存立刻还给操作系统,留下很多"小空洞",导致实际占用的内存(used_memory_rss)远大于你真正使用的内存(used_memory)。

查看碎片率

bash 复制代码
redis-cli INFO memory | grep mem_fragmentation_ratio

如果大于 1.5,说明碎片比较严重。

处理办法

  • 重启 Redis(最简单粗暴,但会短暂停服)
  • 如果用的是 Redis 4.0 以上,可以执行 MEMORY PURGE 尝试整理
  • 调整内存分配器(通常用 jemalloc 会好一些)

4.3 设置内存上限和淘汰策略

一定要给 Redis 设置最大内存,否则它会一直吃直到机器死机。

bash 复制代码
# 最大用 8GB
CONFIG SET maxmemory 8gb
# 内存满了怎么办?按 LRU(最近最少使用)算法淘汰旧数据
CONFIG SET maxmemory-policy allkeys-lru

LRU :Least Recently Used,淘汰那些很久没被访问的 key。

其他策略:volatile-lru(只淘汰有过期时间的 key)、noeviction(满了就拒绝写入)等。


五、场景三:连接数爆了 ------ 客户端没"分手"

现象redis-cli INFO clients 显示 connected_clients 接近一万,并且出现 ERR max number of clients reached

原因:你的应用程序每次请求都新建连接,用完却不关闭;或者连接池配置了"永不超时"。

优化

  • 在 Redis 配置中设置空闲连接超时:timeout 300(300 秒无活动就断开)
  • 检查代码:使用连接池(如 JedisPool、Lettuce),并设置 maxTotalmaxIdle
  • 如果瞬间涌入大量客户端(比如抢购),可以在前面加一层代理(如 Twemproxy)来聚合连接

六、场景四:网络带宽打满 ------ 传了太多"大包裹"

现象:网卡流量跑满,Redis 吞吐量下降。

常见原因:有人一次性读了一个几 MB 的 key,并且频繁操作。

排查 :可以用 redis-cli --bigkeys 找到最大的那些 key,看是不是业务在反复读写。

优化

  • 避免一个 value 超过 1 MB,如果必须大 value,考虑压缩
  • 使用 MGET 时,一次不要超过 100 个 key
  • 启用 RESP3 协议(Redis 6 以上)可减少网络往返

七、事后要做的"长期优化"

救火之后,得治本。

7.1 架构级改造

  • 分片:用 Redis Cluster 把数据分散到多个节点,每个节点只存一部分。
  • 读写分离:主库写,从库读,分担压力。
  • 多级缓存:热点数据放到本地内存(比如 Caffeine),访问 Redis 的频率自然就降下来了。

7.2 监控告警(这次不能再裸奔了)

至少监控这些指标:

  • CPU 使用率
  • 内存使用率 + 碎片率
  • 慢查询数量
  • 命中率(keyspace_hits / (keyspace_hits+keyspace_misses),低于 0.8 说明缓存效果差)
  • 主从复制延迟

推荐工具组合:Prometheus + Grafana + redis_exporter,配置好告警规则。

7.3 定期巡检(每季度一次)

  • 执行 SLOWLOG GET 100 分析慢查询
  • 检查有没有 KEYS 被误用(通过监控慢日志)
  • 找出长期不用的 key,批量删除

八、如果 Redis 已经"爆"了,应急三招

  1. 临时牺牲数据一致性

    如果内存爆满,紧急执行 CONFIG SET maxmemory-policy allkeys-lru 让 Redis 自己淘汰旧数据,尽快恢复服务。

  2. 流量切换

    如果有备用 Redis 集群,立刻通过 DNS 或负载均衡切一部分流量过去。

  3. 应用层限流

    在代码里加个限流器:每秒最多向 Redis 发 5000 次请求,超过的直接返回缓存空值或降级数据。


最后说几句

Redis 优化没什么玄学,记住三句话:

  • 不给 Redis 添乱 :不用 KEYS、不存大 key、记得设过期时间。
  • 资源要设上限maxmemory 一定要配,淘汰策略要选对。
  • 监控要早:别等到半夜被报警吵醒才想起来。

如果你在实际中遇到过更奇葩的"爆机"案例,欢迎评论区分享,我们一起排雷。


附录:本文提到的专业词汇速查表

词汇 简单解释
KEYS 命令 遍历所有 key 的命令,会阻塞 Redis,生产环境禁用
SCAN 命令 游标式遍历,每次取一小批,不阻塞
慢查询 执行时间太长的命令,会被记录到 SLOWLOG 里
QPS 每秒请求数,衡量 Redis 的压力
内存碎片 内存被删得七零八落,实际占用比数据多
LRU 淘汰最近最少使用的 key
fork Redis 持久化时复制自身进程,可能短暂卡顿
RESP3 Redis 新版协议,更节约网络流量
相关推荐
三十..2 小时前
Redis 核心原理与高可用架构实践
运维·数据库·redis
叶小鸡4 小时前
Java 篇-项目实战-AI 天机学堂(从 0 到 1)-day5
数据库·redis·缓存
IT策士4 小时前
Redis 从入门到精通:Python 操作 Redis
redis·python·bootstrap
周杰伦的稻香5 小时前
Go + Redis:本地部署高性能图片主色调提取服务
开发语言·redis·golang
小二·6 小时前
Redis 7 分布式缓存架构实战
redis·分布式·缓存
lx188548698969 小时前
Redis大Key阻塞:单线程CPU100%的致命陷阱
数据库·redis·缓存
IT策士9 小时前
Redis 从入门到精通:位图、HyperLogLog、GEO
数据库·redis·缓存
IT策士9 小时前
Redis 从入门到精通:Python 操作 Redis 进阶
数据库·redis·python
布局呆星9 小时前
Spring Boot + Redis 缓存实战:@Cacheable、序列化踩坑、缓存一致性,一次讲透
spring boot·redis·缓存