
去年帮一家电商团队排查线上故障现象很经典Redis 内存告警、慢查询刷屏、数据库连接被打满。我打开他们的 Redis 一看——大 key、永不过期的 key、还有大量用 String 存的复杂结构遍地都是雷。这几乎是每个没定过使用规范的团队在 Redis 上的共同结局。这篇博客把我这些年整理、验证过的一套 Redis 最佳实践完整放出来。以 7 个维度、43 条使用规范为主线覆盖键值设计、数据类型选型、内存治理、持久化与高可用、缓存三大经典问题、分布式锁、监控运维最后附带一份可直接抄走的实践清单。适合正在维护生产 Redis 的工程师也适合准备给团队定规范的技术负责人。以下每条都是我在真实环境里踩过坑、验证过有效才留下的不是从网上抄来的八股文但面试时能把这 43 条讲透基本也够用了。1. 键值设计从源头避免滥用与冲突1.1 六条硬规范先把键名和值管住规范 1-1键名统一前缀用业务域 冒号分层。这是最基础也最容易被忽视的一条。没有前缀的键名在集群和多个业务共用一套 Redis 时就是一场灾难你不知道user:1001是订单服务的还是会员服务的也不敢随便清理。我这边要求所有键名必须是业务域:模块:实体:ID:属性的结构比如order:user:1001:info。这样无论用SCAN维护、做数据迁移还是定位问题都能靠前缀快速过滤。另外冒号分层不是给 Redis 看的是给人看的格式统一了后续所有脚本和文档都能省一大半精力。规范 1-2键名带时间或版本维度给冷数据留退路。很多团队把缓存键写成固定名字上线后数据格式一变旧缓存全部失效或者新老数据混在一起排查起来极难。现在我会在关键业务的键名里加上版本号或日期比如promo:20250601:config。这样做的好处很实在灰度发布时新老版本可以共存出问题能快速回退每天的统计类缓存也能按天自动过期不用写定时清理任务。规范 1-3键名长度控制在可读与性能之间。Redis 的键是二进制安全的理论上多长都行但键本身是要占内存的超长键名在千万级 key 量下浪费的内存非常可观。我通常要求键名不超过 128 字节能表达清楚业务含义即可。反例是有人把整个 URL 当 key动辄几百字节一个 key 比 value 还大完全没有必要。正确做法是对 URL 做 MD5 或 SHA1 哈希后用前 16 位做键原始 URL 放 value 里。规范 1-4严禁大 key这是内存和延迟的头号杀手。大 key 没有一个绝对标准但业界普遍接受的阈值是单个 String 值超过 10KB或者集合类型元素数量超过 5000 个、总大小超过 10MB都要列入大 key 治理清单。大 key 的危害有几个层面读写阻塞Redis 单线程操作大 key 耗时上升、网络带宽压力一次 get 拉几十 MB、复制延迟主从同步一个大 key 要传很久、迁移困难集群扩容时一个 key 没法拆 slot。我见过一个 800 万元素的 Hash每次 hgetall 直接让 Redis 卡顿 2 秒以上下游服务全部超时。规范 1-5所有 key 必须设置过期时间除非你明确知道它要永久存在。永不过期的 key 是最隐蔽的内存泄漏。很多团队上线时图省事set 完就忘结果 Redis 内存一天天涨最后 maxmemory 触发淘汰把那些还在用的热 key 全给淘汰了引发缓存雪崩。我的建议是所有缓存类 key 必须带 TTL业务上确实需要长期保留的数据比如分布式锁的 key 本身也要单独评审。TTL 也不是越长越好要根据数据更新频率来一个订单状态缓存设 24 小时和一个商品详情缓存设 5 分钟背后的合理性完全不同。规范 1-6统一序列化方案禁止多种序列化混用。这个坑在 Spring 项目中尤其常见。有人用 JDK 序列化有人用 Jackson有人用 Fastjson结果同一个 key 写入和读取的序列化方式不一致反序列化直接报错。更隐蔽的是序列化方式一变老缓存全部变成看不懂的字节读不到就回源数据库Redis 瞬间退化成一个昂贵的摆设。我们团队定死了一条规矩缓存 value 统一 JSON 字符串复杂对象用 ProtoBuf 或 Hessian 可以但必须在配置中心里明确标注禁止在代码里各自为政。另外还要注意String 类型的 value 如果是数字用整数存不要用带引号的字符串否则想用 INCR 做计数器的时候会直接报错。1.2 大 key 扫描与拆分实战规范定了怎么落地先做一次体检。Redis 自带的redis-cli --bigkeys可以扫描全库把最大的 key 列出来。这个命令本质上是在跑 SCAN不会阻塞线上可以放心用。但要注意它统计的是运行期间的瞬时值对于持续增长的 key最好在低峰期跑连续跑几天把趋势拉出来。redis-cli -h 127.0.0.1 -p 6379 --bigkeys扫描出来之后按规范 1-4 处理。以 Hash 大 key 为例最常用的方案是哈希分片把一个大 Hash 按业务 ID 或时间切分成多个小 Hash。比如用户行为数据原来是user:behavior:{uid}一个 Hash 存所有操作记录可以拆成user:behavior:{uid}:20250601、user:behavior:{uid}:20250602每个 Hash 天然控制在一个月的量级。ZSet 大 key 则考虑按分数段拆分或者用多个 ZSet 存不同时间窗口的数据。List 大 key 尽量直接裁剪超过长度限制的用LTRIM截断。如果一时半会改不动代码可以先在客户端做逻辑拆分读的时候拆成多次请求写的时候也拆至少把单请求的阻塞降下来。2. 数据类型选型用对的姿势操作 Redis2.1 七种结构怎么选场景对照表先收藏规范 2-1String 只放简单值和计数器。String 是 Redis 最基础的结构适合缓存一个对象的 JSON、简单的值、Session。它最大的优势是原子自增自减INCR、DECR、INCRBY是实现计数器、限流、库存扣减的天然工具。但我不建议把复杂对象塞进 String 后靠客户端去 parse比如一个用户的多个属性拆成十几个 String key性能和内存都很差这种场景应该用 Hash。规范 2-2Hash 是对象存储的首选。一个用户的昵称、头像、等级、积分用user:1001:info这个 Hash 的多个 field 来存比 4 个 String key 省内存而且可以只更新某一个 field不需要整个对象序列化反序列化。Hash 的 field 数量控制在 1000 以内性能最好。如果 field 超过这个量级说明你需要的是分片 Hash 或其他结构。这里有个容易被小看的好处Hash 在做字段级 TTL 时虽然不支持但可以通过整体过期加存储冗余解决比 String 拆 key 优雅得多。规范 2-3List 用于消息队列和时间线要有限度。List 的 LPUSH BRPOP 组合是一个简单可靠的队列。但注意List 作为队列一旦消费者挂了消息会无限堆积变成大 key。时间线场景也一样用户发帖后把帖子 ID LPUSH 到 List 里如果用户发帖频繁List 会膨胀。我在规范里强制要求List 必须设置长度上限用 LTRIM 裁剪或者消费者必须监控队列深度。如果对消息可靠性要求高别在这里硬扛直接上 Stream 或专业消息队列。规范 2-4Set 擅长去重但要注意运算成本。Set 天然去重适合标签系统、好友关系、已读列表。SINTER、SUNION 这些集合运算很强大但时间复杂度是 O(N)N 是集合大小。两个上百万元素的 Set 做交集一次操作就能把 Redis 卡住。建议大集合做交集放到离线任务里算好再写入或者提前用位图、HyperLogLog 估算规模。另外Set 的每个成员也有内存开销几千万个短字符串的 Set内存会远超你的想象。规范 2-5ZSet 是排序场景的万能选手不只是排行榜。排行榜只是 ZSet 最基础的用法。拿分数做时间戳、成员做业务 IDZSet 就能变成延迟队列、滑动窗口限流、在线用户列表。比如延迟队列任务到期时间戳作为 score消费者ZRANGEBYSCORE取当前时间之前的成员。这个用法我用了很久比定时扫描数据库靠谱得多。但要注意ZSet 的 score 是双精度浮点别拿它存超过精度的整数 ID否则会出现精度丢失导致排序错乱。规范 2-6Bitmap 和 HyperLogLog 是内存极客的最爱。统计日活用户、用户签到这类布尔型数据用 Bitmap 一个用户只占 1 bit一亿用户也只要 12.5MB。HyperLogLog 做 UV 统计每个 key 固定 12KB 左右误差 0.81%大多数场景完全够用。这两类结构的问题是它们只能提供有没有大概多少个这种信息拿不到明细所以用之前要确认产品不需要下钻。规范 2-7Stream 才是真正面向消息队列的结构。很多人还在用 List 当队列但 Redis 5.0 之后推出的 Stream 才是正解。它支持消费组、消息确认、未读消息追踪、消息持久化语义完整。我在实时数据管道里用它做轻量级消息中转比 List 方案省掉了大量自研的 ack 逻辑。2.2 反直觉的选型案例ZSet 也能当延迟队列用我实际做过一个订单超时关单的需求最初方案是每笔订单一个 TTL keyTTL 到了触发 key 过期事件。但 Redis 的过期事件推送并不可靠尤其在 key 量大的时候会丢。后来改用 ZSet 延迟队列订单创建时间 超时时长作为 score订单 ID 作为 member消费者每 5 秒拉一次到期订单执行关单。这个方案跑了一年多没有丢过一笔订单而且实现只有几十行代码。这个案例说明选型不能只盯着教科书上的ZSet 就是排行榜数据结构是死的组合思路是活的。3. 内存治理让有限的内存花在刀刃上3.1 淘汰策略与内存控制六条规范一次说清规范 3-1必须设置 maxmemory并明确淘汰策略。很多 Redis 不设 maxmemory内存满了直接拒绝写入业务表现为Redis 怎么突然写不进去了。缓存场景我推荐allkeys-lru让 Redis 自动淘汰最久没用的 key如果只是某个业务域用 Redis且所有 key 都有明确的 TTL可以用volatile-ttl优先淘汰即将过期的 key。禁止在需要 Redis 做持久存储的场景使用noeviction更不要在使用纯缓存场景选择noeviction导致写入失败。这里有张决策表可以参考业务场景推荐策略原因纯缓存key 可丢失allkeys-lru自动淘汰冷数据缓存为主部分 key 持久化volatile-lru只淘汰带 TTL 的缓存 key预占内存严格按 TTL 淘汰volatile-ttl优先移除最先过期的 key数据稀缺不能丢noeviction内存满后拒绝写入保护已有数据需配合告警规范 3-2禁用危险命令用 rename 锁死。KEYS、FLUSHALL、FLUSHDB、MONITOR这些命令在生产环境就是定时炸弹。一个手抖的FLUSHALL能让整个缓存全灭一次不当的KEYS *能让 Redis 阻塞几秒。标准做法是在配置里把这些命令改名rename-command KEYS rename-command FLUSHALL rename-command FLUSHDB rename-command MONITOR rename-command SHUTDOWN 注意rename-command在 Redis 7.0 之后改成了rename-command配 ACL但思路一样。如果业务确实需要KEYS用SCAN替代一次性拿完和分批拿完对单线程的影响天差地别。规范 3-3合理利用小对象编码。Redis 的 Hash、List、ZSet 在小规模时用的是紧凑编码比如 listpack、intset内存可以省 80% 以上。默认参数一般不用动但如果你的业务 Hash 总是恰好超过hash-max-listpack-entries默认 128可以按需调大。不要盲目把阈值调到几十万那会让一次操作变成大 key 操作性能适得其反。判断是否生效用OBJECT ENCODING key查看listpack/intset开头说明是紧凑编码hashtable开头说明已经转成常规结构了。规范 3-4管理内存碎片关键指标是 mem_fragmentation_ratio。Redis 内存碎片率 used_memory_rss / used_memory。正常情况下在 1.0 到 1.5 之间。如果持续大于 1.5说明碎片过多常见原因是大量 key 频繁创建删除。可以重启节点让 Redis 重新整理内存或者用MEMORY PURGE手动整理 jemalloc 的空闲页。如果碎片率小于 1.0说明发生了内存交换swap赶紧检查系统内存是不是不够了Redis 一旦 swap性能会惨不忍睹。规范 3-5建立内存监控和容量预警。最实用的三个指标maxmemory使用率、mem_fragmentation_ratio、单节点 key 数量。我要求监控系统每小时记录一次INFO memory的used_memory使用率超过 80% 触发告警。为什么是 80%因为做内存扩容、迁移、rolling restart 都需要余量等到 95% 才处理业务早就开始抖了。容量规划上也别抠一个核心业务 Redis 节点预留 30% 的缓冲空间是底线因为你永远不知道运营活动什么时候来。规范 3-6大 value 分拆才谈后续优化。内存治理的第一步永远是去掉大 value。一个 5MB 的 String 存在 Redis 里压缩比再高也压不出太多空间。先定位再拆分然后才是考虑换编码、换结构这些细节。顺序不能反否则你优化半天一个大 key 就把收益清零了。3.2 内存碎片率到底怎么读有一次我们一个节点内存使用率只有 50%但INFO memory显示 RSS 已经是物理内存的 90%敏感一点的人立刻能定位到碎片问题上。当时排查发现是短信验证码这种短生命周期 key 产生的大量分配释放jemalloc 没有及时把内存归还给操作系统。处理办法是先调低maxmemory触发淘汰再滚动重启节点碎片率就从 1.8 降到了 1.1。如果不想重启调jemalloc的MALLOC_CONFdirty_decay_ms参数也能缓解但要重启进程才生效所以多数时候滚动重启反而更省事。4. 持久化与高可用数据安全从配置抓起4.1 六条规范把 RDB、AOF、主从、集群一次理清规范 4-1RDB 与 AOF 的双写策略要分场景。默认配置里 RDB 是开启的AOF 默认关闭。缓存场景可以只开 RDB丢一点数据无所谓但如果是订单状态、账户余额这类数据必须开 AOF。我的推荐配置是appendonly yesappendfsync everysec每秒刷一次盘极端情况丢 1 秒数据大部分业务都能接受。fflush 频率太高always会让写性能大幅下降除非你对数据安全零妥协否则不要用。save 900 1 save 300 10 save 60 10000 appendonly yes appendfsync everysec规范 4-2AOF 重写机制必须理解否则会被卡一下坑到。AOF 文件持续增长到一定阈值会触发 BGREWRITEAOFRedis fork 子进程做重写。fork 期间主进程要拷贝页表内存越大 fork 越慢期间会有短暂的阻塞。我见过一个 30GB 的节点fork 花了将近 2 秒所有写入请求一起超时。规避手段调低auto-aof-rewrite-percentage从默认 100 调整到 200让重写不那么频繁重写尽量放在低峰期也可以手动在主从切换时做滚动重写。规范 4-3主从复制要做但要有主从不一致的预案。主从同步是异步的这意味着从节点数据一定比主节点旧只是差异多少的问题。如果业务容忍度极高可以配置min-replicas-to-write和min-replicas-max-lag来保证写入时有足够健康的从节点否则主节点拒绝写入。这不是降低可用性是防止主节点数据异步复制到从节点之前主节点挂了导致数据丢失。读写分离场景建议从节点只承担可容忍延迟的读请求强一致读必须走主节点。规范 4-4哨兵模式至少三个节点且部署在不同物理机。哨兵的定位是监控和自动故障切换。两个节点会存在脑裂问题主节点假死时一个哨兵认为主挂了另一个不认为无法达成 quorum无法触发切换。三个哨兵允许一个哨兵失联场景更稳。quorum 设置 2 比较常见两个哨兵同意主节点不可用才发起切换。另外哨兵所在机器不能全和 Redis 实例在一台物理机上否则宕机就是全军覆没。规范 4-5集群模式用官方 Cluster 或 Proxy 方案按业务场景选。Redis Cluster 的优点是去中心化、支持自动故障转移默认 16384 个 slot。它的缺点是不支持多 key 操作跨 slotMGET 多个 key 要确保 hash tag 在同一个 slot批量 pipeline 也会受槽位限制。对业务侵入小的方案是 Codis 或代理层对运维要求高的团队直接用原生 Cluster。我的经验是key 量百万级以下、单节点内存能扛住的场景哨兵模式已经够了单节点内存超过 32GB 或者写入吞吐成为瓶颈再考虑 Cluster别为了用集群而上集群。规范 4-6容器和 K8s 中部署 Redis持久化与网络是关键。在 Kubernetes 里部署 Redis 集群很多人第一次就栽在Pod 重启后数据全没。因为默认容器是无状态的必须在 StatefulSet 里挂 PVPersistentVolume并且 AOF 和 RDB 的路径必须指向持久化目录。另一个常见坑是Pod 重启后 IP 变了Redis Cluster 节点互相注册的还是旧 IP。Redis 6.2 之后有cluster-announce-ip和cluster-announce-port配置配合 Headless Service 的稳定 DNS才能保证集群拓扑不变。我也是折腾过两轮才把 K8s 里的 Redis 集群跑稳强烈建议先小规模验证再全量迁移。4.2 一次 AOF 重写引发的阻塞复盘那次故障被我记到现在。现象是主节点 CPU 和延迟指标每 10 分钟跳一次尖峰客户端时不时报READ TIMEOUT。一开始以为是流量波动查完才发现INFO persistence里aof_last_bgrewrite_status:ok但latest_fork_usec高达 190 万微秒也就是 1.9 秒。整个节点 28GB 内存每次 fork 都要拷贝页表主进程完全卡住。当时 AOF 文件已经 25GB触发了自动重写阈值。处理办法是把auto-aof-rewrite-percentage抬到 200给 AOF 重写设置了业务低峰窗口同时把这些大内存节点的 RDB 备份改到从节点上执行。从那之后我把所有持久化相关的 fork 耗时就加入了监控告警latest_fork_usec超过 500 毫秒就开始人工关注。5. 缓存三大经典问题穿透、击穿、雪崩的治理套路5.1 六条规范把三座大山挨个拆掉规范 5-1缓存穿透——布隆过滤器 空值缓存双保险。穿透的意思是查询一个必然不存在的数据每次请求都打到数据库。典型的攻击手段就是遍历不存在的 ID。布隆过滤器能快速判断肯定不存在成本极低。但你得接受它会误判也就是说可能存在不代表真的存在最终还是要查一次库。误判率可以靠error_rate参数控制通常设到 1% 以内。另一个兜底方案是空值缓存查不到的数据也写一个空值到 RedisTTL 设短一点比如 2~5 分钟防止恶意 ID 批量穿透。注意空值缓存要设置 TTL否则会堆积成内存垃圾。规范 5-2缓存击穿——热点 key 加互斥锁。击穿和穿透不一样击穿是热点 key 在过期瞬间大量请求同时打到数据库。最经典的解法是互斥锁缓存不存在时先抢锁抢到锁的线程去查库、回填缓存其他线程等待重试。用 Redis 实现就是SET lock:hotkey 1 NX PX 30000设置 30 秒过期避免持锁线程崩溃导致死锁。但要注意回填缓存的操作时间必须小于锁过期时间否则先回填的线程还在跑锁已经过期又有线程进来了。如果业务查询特别慢考虑用逻辑过期方案缓存里存一个过期时间字段读到过期数据时去抢锁刷新这样不阻塞读请求。规范 5-3缓存雪崩——过期时间加随机值 多级缓存。雪崩是大批 key 同时过期或者 Redis 节点宕机所有请求一起落到数据库。加随机值是最便宜的防御TTL 不只是固定时间而是在基础值上加一个随机偏移量比如 300 到 600 秒之间随机让过期时间错开。代码里实现就是TTL 300 random.nextInt(300)。多级缓存在 Redis 之上再加一层本地缓存即使 Redis 整体不可用本地缓存也能挡住一部分请求给故障恢复争取时间。规范 5-4缓存一致性——Cache Aside 模式先更新库再删缓存。这是最常见的缓存更新模式读的时候先读缓存没有就读库回填写的时候先更新数据库再删缓存。为什么不直接更新缓存因为并发写和并发读交错时更新缓存容易出现旧值覆盖新值。删缓存虽然会产生一次 cache miss但代价比一致性错误低得多。这里有个细节删缓存失败怎么办我的方案是延迟双删先删缓存等几百毫秒再删一次把第一次删除后可能回填的旧值清掉。如果对一致性要求再高用消息队列给缓存删除加一个重试通道。规范 5-5缓存粒度要按需而定。很多团队习惯把整个用户对象塞进缓存只改了一个积分字段也要刷新整个对象。这导致缓存失效的代价很大、更新很频繁。正确做法是按读取频率拆分缓存粒度。高频读取且只需要部分字段的数据拆成多个小缓存低频全量读取的数据才考虑整对象缓存。一条经验值如果一个缓存字段一天更新超过 100 次就不要缓存它否则每次更新都会引发一次缓存刷新成本已经超过了收益。规范 5-6热点 key 要有治理预案不能等问题爆发。热点 key 不像穿透那些是设计问题而是流量问题。比如大促时一个商品瞬间来了几十万请求单 key 的 QPS 上限会卡住。预案有三层第一层是给热 key 加二级本地缓存挡住大部分流量第二层是给热 key 做分片把数据复制到多个 key 上比如hotkey:01到hotkey:20请求随机打散第三层是限流降级超出阈值直接返回降级数据。我用得最多的是本地缓存 分片因为成本低、见效快。5.2 缓存一致性为什么这么难很多人问我先更新缓存再更新库行不行答案是在并发下几乎必出问题。A 线程先更新了缓存为新值B 线程随后用旧值覆盖了缓存然后 A 再更新数据库为新值数据库和缓存就永远不一致了。Cache Aside 的删缓存之所以能成立是因为删比写冲突概率低删掉后读请求会回源查询数据库拿到的就是最新值旧值没有机会留在缓存里。这也是先更新库再删缓存和延迟双删的核心逻辑。我们团队因为这个坑付出过一次线上事故之后我直接把规范 5-4 写进了 Code Review 检查项谁写缓存更新逻辑都要走一遍这个流程。6. 分布式锁与并发控制锁不好写但可以写得对6.1 六条规范把分布式锁写成生产级规范 6-1分布式锁的前提是锁必须最终可释放。最简单的锁实现是SETNX但只 SETNX 不设过期时间会出现持锁进程崩了锁永远不释放的问题。所以第一步必须是SET key value NX PX 30000一个命令原子地完成不存在才设置设置过期时间。不要分两步执行 SETNX 然后 EXPIRE因为两步之间可能出事故这属于分布式锁最基础的常识。规范 6-2锁的 value 必须是唯一的防止误删。很多人写释放锁的代码是这样的DEL lock:xxx这是隐患。如果线程 A 的锁已经过期线程 B 拿到了锁然后线程 A 的业务才跑完执行 DEL 就把 B 的锁删了。正确做法是 value 用唯一 IDUUID 或业务流水号释放前先比对再删必须用 Lua 保证原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这段 Lua 脚本就是用自旋方式释放锁的唯一正解网上你能看到的正确版本基本都是它。规范 6-3锁的过期时间要拍脑袋但要拍得有依据。锁过期时间太短业务没跑完锁没了别的线程进来了太长一旦持锁线程卡住后面的请求全得等。我给团队的经验是先估算业务最坏耗时乘以 3 到 5 倍作为锁的过期时间。如果业务时间波动很大用 Redisson 的看门狗自动续期机制默认每 10 秒续期一次直到业务结束。规范 6-4可重入锁必须自己实现或用 Redisson。同一个线程多次进入同一个锁的业务场景很常见比如一个方法加了锁它内部又调用另一个加了同一个锁的方法。原生 SETNX 不支持可重入会直接死锁。Redisson 的RLock支持可重入而且底层用 Hash 记录重入次数。RLock lock redissonClient.getLock(order:pay: orderId); boolean locked lock.tryLock(3, 30, TimeUnit.SECONDS);还有一点Redisson 的看门狗默认 30 秒续期一次业务结束不要忘了unlock()否则会一直续期到超时上限。规范 6-5锁粒度能小则小别锁整个业务域。我看到过很夸张的写法做订单支付时锁的 key 是整个用户 ID结果这个用户同时下两笔订单时第二笔的加锁直接失败。锁粒度应该对应到具体资源支付锁加在订单号上库存锁加在 SKU ID 上用户积分锁才加在用户 ID 上。锁粒度铺太大并发能力骤降锁粒度太小锁的数量爆炸、管理复杂度上升。一条经验值如果锁数量大概率超过每秒一万个你要反思是不是把多个资源拆得太细了。规范 6-6RedLock 的争论有个务实结论大多数业务用不上。RedLock 是 Redis 作者提的红锁方案要求锁在多个独立 Redis 节点上同时获取成功才算拿到。理论上它防止了单点故障但工程上争议很大它依然依赖时钟、网络而且性能开销很大。我的建议是普通业务场景用单节点 Redis 锁 合理的过期时间 手动续期方案完全够用只有你确实需要跨多个 Redis 实例的强一致锁再考虑 RedLock 或换成 ZooKeeper / etcd。别让团队为了一个看起来更高级的锁方案把复杂度抬上去。6.2 一个典型的锁失效案例有一次我们做秒杀代码逻辑是抢锁成功 → 查库存 → 扣减数据库 → 回写 Redis → 释放锁。结果上线没多久库存超卖了。排查发现锁过期时间是 10 秒但查库加扣减逻辑因为慢查询跑了 15 秒锁早就没了导致多个线程同时进入。修复方案是把锁过期时间从静态改成动态根据最近一次慢查询的 P99 耗时动态计算最低 30 秒超时后自动续期。同时把查库存的 SQL 索引加上从根上消除慢查询。这个案例告诉我们分布式锁不是加上了就完事它的过期时间、续期机制和业务执行时长必须联动考虑。7. 监控、容量与故障排查线上问题从防开始7.1 六条监控运维规范覆盖日常和应急规范 7-1慢查询日志必须开而且要主动分析。Redis 的慢查询日志不是传统 SQL 那种慢日志它记录的是超过阈值的命令执行时间。配置两个参数slowlog-log-slower-than 10000 slowlog-max-len 128这个10000单位是微秒也就是命令执行超过 10 毫秒就记录。查询用SLOWLOG GET默认返回最近 10 条。我要求开发环境的阈值设到 5 毫秒生产设 10 毫秒每天拉一次慢日志看有没有新命令上榜。慢日志的意义不只是性能问题它往往暴露了数据结构选型错误——频繁出现的HGETALL和ZRANGE大范围操作就是该做拆分的信号。规范 7-2可视化工具别乱连生产库MONITOR 命令尤其危险。RedisInsight 和 Another Redis Desktop Manager 是目前用得最多的可视化管理工具前者功能多、适合分析后者轻便、适合快速查询。但无论是哪个工具直接连生产 Redis 都要谨慎。特别是带 MONITOR 功能的工具它会让 Redis 把所有命令实时打印出来高流量时直接把 CPU 打满。我们规定生产环境只能通过只读账号连接可视化工具禁止使用管理员权限执行任何写操作。DBA 操作走命令行审计操作记录留档。规范 7-3Redis 访问要做到客户端连接可控。客户端连接数突增是 Redis 挂掉的又一隐形原因。timeout参数如果为 0空闲连接永不释放连接数会持续累积到maxclients上限然后新的客户端直接连接失败。建议设置timeout 300让空闲 5 分钟以上的连接被服务端主动断开。客户端侧也要配置合理的连接池大小和超时时间。我特别想提醒的是 Java 里 Lettuce、Jedis 的默认配置Lettuce 默认超时 60 秒如果 Redis 卡顿客户端线程就傻等 60 秒任务堆积成雪崩。把超时时间调到 300~1000 毫秒配合连接池的maxTotal和maxIdle设置让失败快速暴露而不是无限等待。规范 7-4集群运维从在线迁移到自动化检查都要有预案。Redis Cluster 在线扩缩容靠redis-cli --cluster reshard但要严格控制批量迁移量一次迁移的 slot 数不要超过 512 个否则节点间数据复制占用大量网络带宽。迁移前必须检查每个节点的 key 数量分布尽量均匀。迁移完成以后用redis-cli --cluster check验证集群状态看到All 16384 slots covered才算完成。规范 7-5备份策略要测试恢复而不是只在文档里写着。备份 RDB 文件定时拷贝 定期演练恢复。很多团队的备份文件一堆但没有一个人实际验证过能恢复。我的做法是每周做一次恢复演练随机挑一个备份文件在测试环境启动 Redis用INFO keyspace校验 key 数量用抽查 key 校验数据正确性。别等真出事了才发现备份文件损坏或者版本不兼容。规范 7-6版本升级走先小后大的路径。Redis 小版本升级一般兼容性较好但从 5.x 到 6.x、6.x 到 7.x 要注意配置和行为变化。比如 Redis 7.0 对rename-command和 ACLE 的改动就不少redis.conf里很多参数弃用。升级顺序先在测试环境跑一遍回归再在从节点上升级验证逐步切换主节点。不要跳大版本不要在生产直接升级后重启这是最基础的红线。7.2 一次 Lettuce 超时问题排查记录有次下游反馈某个服务接口偶发抖动日志里报RedisCommandTimeoutException错误信息是典型的redis command timed out; nested exception is io.lettuce.core.rediscommandtimeoutexception。一开始以为是 Redis 性能问题看监控发现 Redis CPU 和延迟都不高问题在客户端侧。Lettuce 是 Netty 实现的异步客户端如果事件循环线程阻塞所有共享连接的请求都会超时。当时排查出的原因连接池太小高峰期线程竞争连接加上一个慢查询把连接占住后续所有请求排队超时。修复方案调大maxTotal、调小maxIdle和minIdle并给 Lettuce 设置了合理的读超时比如 500ms和连接池等待超时比如 200ms。从那以后我把这类客户端侧超时配置加入了上线前 Checklist比出了事故再定位高效得多。8. 落地执行43 条规范如何转化为团队实践清单8.1 建立可执行的检查清单别让它停留在博客里43 条规范读完容易落地难。我的做法是把它们做成一张Redis 使用规范检查表按照 7 个维度逐条列出来每条包含规范编号、具体说明、检查方式、责任人。比如编号规范项检查方式责任人1-4严禁大 keyredis-cli --bigkeys 定期扫描服务负责人3-2禁用危险命令检查 redis.conf rename-commandDBA6-2锁 value 唯一Code Review 检查 DEL 前校验开发负责人7-1慢日志分析每日拉取 slowlog 并归档DBA这张表不用一次性全量执行可以先挑最容易出事的 10 条重点治理比如大 key、危险命令、无 TTL、分布式锁 value 唯一性这四条控制住以后再把范围扩大到全部 43 条。实践清单本身要放在团队 Wiki 或代码仓库里持续更新我发现过了半年后大家慢慢都会忘掉细节所以最好半年做一次全员复盘把新踩的坑补进规范里。检查方式也要尽可能自动化。我分享一下我们目前用的巡检脚本思路用redis-cli和SCAN TYPE OBJECT ENCODING组合扫描 key 分布统计每个业务域的 key 数量和总大小用SLOWLOG GET拉慢查询用INFO memory记录内存使用率和碎片率。整个巡检每天定时跑一次输出 JSON 格式报告到监控平台。这套自动化巡检的价值在于它把 43 条规范里能机器检查的部分全部转化为持续监控而不是靠人肉翻命令。8.2 从规范到习惯Code Review 和上线评审要把 Redis 列为必查项规范写进文档很容易真正难的是让每个开发同学写代码时按规范来。我们在 Code Review 模板里增加了 Redis 使用自查清单包括是否设置 TTL、是否避开大 key、序列化是否统一、分布式锁释放前是否校验 value、缓存更新是否按 Cache Aside 模式。另一个卡点在上线评审凡是涉及 Redis 操作的需求都必须说明用了哪些 key、什么数据类型、TTL 多久、风险点是什么评审现场过一遍。这听起来很重但实际坚持半年后团队里 Redis 相关的线上问题明显下降而且新同学上手时多了一份直接参考的工程手册。最后再分享一个我在多个团队验证过的小技巧不要强推所有人都精通 Redis而是要有一个 Redis 规范负责人他负责维护实践清单、巡检脚本、事故复盘。别人踩坑他把坑写进规范其他人就不用再踩一遍。这套机制比任何一场培训都管用。