
有段时间我对 Redis 的感情很复杂:喜它的速度,恨它的“被误用”。团队里常有人跟我说一句话:“加了 Redis,咋还不如没加呢?”我以前以为是 Redis 啥配置没弄好,后来多看了几次线上事故,才发现大部分问题根本不是 Redis,而是我们自己把缓存用成了拖油瓶。
讲几个我自己亲眼经历、真真实实发生在生产上的例子,都是痛到怀疑人生那种。希望你看完能减少一点线上背锅的概率。
我遇到过一个特别典型的:业务同学在一个用户详情查询接口上加了 Redis,理论上能把 15ms 的 DB 查询干到 1ms 左右。但上线后接口平均 RT 从 20ms 直接飙到 80ms,大家都懵了,以为 Redis 配置坏了。我接过活看日志才发现:每次请求查 Redis 的 key 根本就不存在,然后业务又去查 MySQL,把结果序列化成 JSON 后再写回 Redis,还顺便设置了个高达 3600 秒的 TTL。这些都不算致命,问题是:每个 key 的值都是一个 200KB 的超大 JSON。写入 Redis 的时候网络直接吞不下。
你要知道 Redis 不是魔法,它是内存数据库,瓶颈往往不在 CPU,而是在网络。你塞一个 200KB 的 JSON,上行网络延迟能直接干到几十毫秒。
BigKey 是我心目中排名前三的 Redis 杀手。很多人以为 Redis 很快,就随便往里塞东西。一次我排查一个线上 QPS 2000 的接口一直抖动,发现 Redis 客户端的 timeout 次数特别多。抓包一看,每次 GET 一个 key 都要拉 300KB 数据。你想想,一个 GET 操作返回一个 300KB 的大 JSON,解压、反序列化、传输,RT 肯定不可能稳定在 1ms。
我后来把 key value 拆成几个子 key,每个 key 大概 10KB,RT 从原来的 20ms 稳定到 2ms。BigKey 最大的问题不是内存,而是:
你以为是 MySQL 慢,其实你 Redis 早在默默崩溃了。
另一个大坑就是热 Key。我们一个活动系统的首页展示数据(全平台都要访问)被放到 Redis 一个固定 key 里,结果促销那天 QPS 从 5k 突破到 20k,Redis CPU 直接干到 95%,整个集群读延迟暴涨,甚至出现了 cluster failover。
热 Key 为什么危险?因为 Redis 单线程处理命令,某个 key 太热就等于你把所有请求都打到单个 CPU 上。
后来我们改成两招:
瞬间从单点压力变成分摊压力。
很多人用 Redis 当分布式锁,不是用 set nx px,就是 lua 脚本也懒得写,释放锁时直接 del。然后就出现“误删别人的锁”“锁提前释放”等问题,甚至线上出现过一条接口被锁 5 秒,整个接口 RT 飙到 5.xxx 秒那种诡异现象。
还有人用 incr 做计数,却把它和大量读写混在一起,结果 Redis pipeline 也不用,直接几十次 incr 循环调用,网络往返就能消耗绝大部分时间。
你以为你缺 CPU,其实你缺的是常识。
举个实际的计算:某次我查日志看到某个接口一次要从 Redis 获取 30 个 key,但代码里竟然是这样:
for (String k : keys) {
redis.get(k);
}一次网络往返大概 0.5ms,30 次就是 15ms,这还是在延迟很低的同机房环境。如果跨机房,直接翻倍。后来我改成 pipeline,大部分请求一下子打包发出去:
Pipeline p = jedis.pipelined();
for (String k : keys) {
p.get(k);
}
List<Object> res = p.syncAndReturnAll();RT 直接从 20ms 掉到 2ms。用 Redis,round-trip 是非常关键的点。你以为是 CPU 累了,其实是客户端在跑马拉松。
缓存穿透:查不存在的数据,每次都直接把数据库打爆。我见过一个手机号查询接口,因为垃圾爬虫疯狂撞手机号,Redis 那边全是 miss,数据库 QPS 从 100 飙到 20000,扛不住。
解决思路不是背出来的,是线上逼出来的:
缓存击穿:单 Key TTL 正常到期,突然海量请求打进来,打爆数据库。一个 Key 热度高的时候 TTL 不应该随便设置。
缓存雪崩:大量 key 在同一时间过期,机器直接抖成 PPT。解决方式就是加随机 TTL,别偷懒。
最常见的方式就是双写:写数据库,还写 Redis。但这个问题不是“写不写”,而是“顺序不能乱”。
我见过有人先写 Redis,再写数据库,结果写数据库失败,缓存里存的是错的。应该这样:
延迟删除也是常见方案,业务更新时先删缓存,再更新数据库,延迟一点时间再异步删除一遍,保证 eventual consistency。
有一次我们线上 Redis CPU 异常高,查下来是因为大家用的是 JSON 序列化,大对象 decode 超级慢。后来改成了 Protostuff,value 大概缩小了 40%,decode 时间减少了 50% 左右。
序列化的开销不只是 CPU,还有网络。
没必要盲目追求最好,用你能接受的最轻的就对了。
集群扩容的时候,把 slot 从一个节点迁移到另一个节点。很多业务方不知道迁移期间访问 slot 会发生 MOVED、ASK 重定向,客户端要做重试。
我见过一次迁移时,某个客户端不支持 ASK 指令,结果访问新 slot 的时候直接报错,整个服务 RT 飙升,所有人都在骂 Redis,其实是客户端不兼容。当然,最惨一次是 BigKey 迁移,迁移一条数据就卡 100ms,看着都想哭。
如果你用 cluster,那你必须:
latency 和 hit ratio。
latency 可以用 redis-cli 的 latency doctor,看一下是否因为慢命令、AOF、磁盘、fork 导致抖动。
hit ratio(缓存命中率)可以告诉你是否只是 Redis 存在但没起作用。有次我查线上命中率只有 40%,原因是 key 拼错了五次,有不同版本的 key。
监控 Redis 不只是看 CPU 和内存,它最重要的是看请求行为。
Redis 五种基础结构你都知道,但你可能不知道它们的性能差异巨大。比如:
你以为 Redis 很快,是因为你没用坏它。
一次线上问题,对象存的是 hash,业务要查十个字段。那同学为了方便,搞了一个 “取整个 hash 再从 Map 里拿字段”。结果每次要从 Redis 取一个 value 有七十多个 field,value 体积大概 50KB,一个接口三次读取,RT 每次 10ms 往上。后来我说:“你用 HMGET 只取用到的字段不行吗?”改完后 RT 降到 2ms 以下。
Redis 快,是建立在你不作死的前提下的。
很多人觉得 Redis 是万能加速器,但在我经历的几十次线上排查中,大部分“Redis 慢”其实都是:
Redis 没问题,是我们自己把问题引出来的。
如果你也觉得 Redis“没那么快”,我建议从:数据结构、网络延迟、key 大小、客户端配置、缓存策略五个角度逐个排查。往往问题不是出在你想的地方。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。