先说明一下,我不是来科普 Redis 命令的,那是文档干的事。这篇文章想聊的是 Redis 在真实业务系统里怎么流转、怎么支撑起高并发链路,又是怎么一步步成为整个架构中不可或缺的一环。围绕“Redis 核心业务流程”这个题目,我会把缓存读写、持久化、高可用、分布式锁、缓存治理这几个主流程全部过一遍,每一步都结合线上环境里的实际取舍来解读,不是背八股文。
我自己这些年接触过的项目中,几乎没有哪个高并发系统不在用 Redis。订单、库存、用户会话、接口幂等、热点榜单、消息队列削峰,Redis 的身影无处不在。但用的多的同时,真正把 Redis 业务流程梳理清楚的人却不算多。很多人只知道 set get 一把梭,真到了缓存穿透、主从切换丢数据、分布式锁失效这些场景,就抓瞎了。这篇文章就是想把 Redis 的业务主流程彻底讲透,让新手能建立完整认知,也让有经验的人查漏补缺。
1. 先从业务视角理解 Redis 的五大核心流程
1.1 Redis 在业务链路中到底扮演什么角色
很多新手入门 Redis 时,第一反应是把它当成一个“快一点的数据库”。这个理解不算错,但偏差很大。单看数据读写速度,Redis 确实碾压传统关系型数据库,能到十万级 QPS。但它真正的价值不在于“快”,而在于它在业务链路里承担的角色是数据库的前置屏障。
拿一个典型的下单流程来看。用户点击“立即购买”,请求到达后端服务后,第一件要查的事是商品信息,第二件是用户信息,第三件是库存信息。如果没有 Redis,这三类数据全部打到 MySQL 上。商品表几百行可能还好,用户表千万级也扛得住,库存表一旦被热点商品集中访问,数据库就很容易被慢查询拖垮。引入 Redis 之后,商品信息、用户摘要、库存余量都可以在 Redis 里缓存一份,读写请求走内存,穿透到数据库的请求量瞬间少几个数量级。
这就是 Redis 在业务链路中最核心的角色:数据访问的加速层,也是数据库的流量屏障。它承载的是业务系统里读多写少的那部分数据,是热点数据的临时落脚点,而不是最终的数据归属地。千万不要把 Redis 当成真正的数据仓库来用,它丢失数据的风险天然存在,所以业务流程上必须明确区分“缓存数据”和“源数据”。
1.2 五大核心流程的整体串联关系
把 Redis 放进业务系统里看,核心业务流程可以分成五条线,它们之间不是孤立存在的,而是层层嵌套的关系:
- 读写缓存流程:业务请求先查 Redis,命中的直接返回,没命中的回源数据库并回填 Redis。
- 数据持久化流程:写进 Redis 的数据,通过 RDB 快照或 AOF 日志落盘,防止进程重启后数据全部蒸发。
- 高可用架构流程:主节点负责写,从节点负责读和备份,主节点挂了由哨兵把从节点提拔成新主。
- 分布式锁业务流程:多个服务实例并发操作同一份资源时,用 Redis 的原子指令保证互斥。
- 缓存治理流程:围绕缓存穿透、击穿、雪崩三兄弟,做的日常防护和故障恢复策略。
这五条线在真实链路中是怎么咬合的,我用一个具体的库存系统来讲。商品详情接口读缓存,这是读写缓存流程;后台修改商品价格后主动删除缓存,这是缓存治理流程;库存扣减为了防止超卖,需要分布式锁,这是分布式锁流程;扣减成功的流水要异步写 MySQL,同时主从节点之间同步数据,这是持久化和高可用流程。五条线全部围绕同一条业务主线运转。
所以说,孤立地学 Redis 命令没有意义,要从业务流程的角度理解每个机制的存在原因。清楚了整体框架,后面每一条线都能对号入座。
2. 读写缓存流程:从请求进来,到数据回填
2.1 一次标准缓存读流程的完整步骤
把一次缓存读请求从头到尾拆开,可以看到一个非常明确的分支过程:
- 客户端发起读请求,比如查询商品详情。
- 后端服务先查 Redis,key 设计为
product:detail:1001这种格式。 - 如果 Redis 里存在数据,直接反序列化后返回给前端,整个过程结束,数据库不参与。
- 如果 Redis 里不存在,说明缓存未命中,则去 MySQL 查询商品详情。
- 数据库查询成功后,将结果写入 Redis,同时设置合理的过期时间。
- 把数据返回给前端,下一次同样的请求直接命中 Redis。
这个流程对应到代码里,就是最常见的 Cache Aside 模式。逻辑很简单,但有几个细节值得展开。
第一个细节是缓存 null 值的问题。如果数据库里查不到这个商品,流程走到第 4 步时返回空,这个时候如果不做任何处理,下一次请求还是会穿透到数据库。高并发下这个 key 被频繁查询,数据库会承受无谓压力。解决方案是把 null 值也缓存起来,设置一个较短的过期时间,比如 30 秒,这样即便数据暂时不存在,也不会反复打到数据库。
第二个细节是过期时间的设计。缓存过期时间不是随便设的,要结合业务的容忍度。商品详情这种数据允许 5 分钟延迟更新,就设 300 秒;用户登录 token 要求 30 分钟失效,就设 1800 秒。注意过期时间要加一个随机偏移量,避免大量 key 同一秒集体过期,引发缓存雪崩。
第三个细节是序列化方式。Redis 存的值本质是字节数组,Java 里通常用 JSON 序列化、JDK 序列化或 Protobuf。我最推荐 JSON 序列化,因为可读性好,而且跨语言兼容。不要用 JDK 默认序列化,存进去是一堆乱码,排障时想看一眼数据内容都无从下手。
2.2 更新缓存的两种常见策略:先删还是先更新
Cache Aside 模式里最经典的问题就是缓存更新策略:先更新数据库再删除缓存,还是先删缓存再更新数据库。
业内共识是优先选择“先更新数据库,再删除缓存”。这里很多人不理解,觉得更新完数据库之后缓存里还是旧数据,为什么不是先删缓存再更新数据库。原因在于并发窗口的差异。如果先删缓存,在缓存删除成功、数据库更新完成之前的这个时间窗口里,任何读请求都会把数据库的旧数据回填到缓存里,造成数据长期不一致。而先更新数据库再删缓存,虽然更新到删除之间缓存还是旧值,但这个窗口极短,且下一次读请求必然触发缓存重建,最终一致性能够得到保证。
这里有一个特殊情况必须说明:更新数据库成功了,但删除缓存失败了怎么办。这是缓存和数据库不一致最常见的故障来源。常规解法是引入重试机制,比如删除失败后把 key 丢进消息队列,由消费者异步重试删除。更进阶的做法是订阅 MySQL 的 binlog,通过 Canal 这类中间件感知数据变更,然后自动删除对应缓存。这个方案能彻底解耦业务代码,但引入额外的组件,适合数据一致性要求很高的核心链路。
我实际项目中就碰到过一个问题:订单状态更新后缓存里的数据一直是旧的,排查了很久发现是删除缓存的那行代码被 try-catch 吞了异常,删除失败没有任何日志。所以自那以后我有个习惯,所有缓存删除操作必须记录日志,删除失败要打到 error 级别,至少要能看到失败的 key。
2.3 缓存流程中的 Key 设计规范
key 设计看起来是小事,实际上对业务流程的流畅性影响很大。我见过项目里 key 命名五花八门,有的用业务名简称,有的直接塞对象 toString,出了问题连排查都没法做。
规范的 key 设计应该遵循三段式结构:业务域 + 业务对象 + 唯一标识,比如order:detail:10293847、user:info:283746、product:stock:1001。这样的好处是可以通过前缀快速定位一类 key,也可以通过 Redis 的 scan 命令按模式匹配找到目标 key。
冒号分隔是社区通用惯例,不是强制标准,但建议团队统一。统一之后,Redis Desktop Manager、Another Redis Desktop Manager 这些可视化工具里浏览 key 就非常直观,一组相关的 key 会自然归到一个分组下。
另外,key 不要在业务代码里硬编码拼接,最好通过统一的 KeyBuilder 工具类生成。这样可以避免不同开发人员写出的 key 格式不一致,也方便后续治理。比如需要批量删除某个前缀的缓存时,只要 KeyBuilder 统一,就能用scan 0 match user:info:* count 1000的方式分批清理。
3. 数据持久化流程:进程挂了,数据怎么找回来
3.1 RDB 与 AOF 的各自职责和工作链路
很多人会有个困惑:Redis 是内存数据库,数据都在内存里,还需要什么持久化。答案是如果 Redis 进程异常退出或服务器断电,内存数据会瞬间蒸发,没有持久化就意味着所有缓存数据和临时数据全部丢失。对于缓存场景可能还能忍受,但对于那些把 Redis 当临时存储的业务,比如分布式锁、限流计数器,丢失数据就可能引发业务事故。
Redis 持久化有两条链路:RDB 和 AOF。
RDB 的工作方式是定期把内存中的全量数据生成快照写入磁盘,生成的文件是二进制的dump.rdb。它的优点是恢复速度快,文件紧凑,适合备份和灾备。缺点是快照生成之间的窗口期数据会丢,因为它是定时触发的,比如默认 900 秒内如果有 1 次写操作就保存一次,这期间的数据丢失无法避免。另一个缺点是生成快照时 fork 子进程,在数据量极大的情况下会占用额外内存,可能导致短暂的卡顿。
AOF 的工作方式是把每一次写命令追加到日志文件末尾,类似 MySQL 的 binlog。它的优点是数据安全性更高,可以配置每次写操作都 fsync 到磁盘,最多丢 1 秒数据。缺点也很明显,日志文件会不断膨胀,恢复速度比 RDB 慢,需要定期做 AOF 重写压缩。
生产环境的标准组合是 RDB + AOF 同时开启,RDB 负责快速恢复和备份,AOF 负责补足 RDB 最后一次快照之后的数据。Redis 重启时加载数据的优先级是 AOF 优先于 RDB,因为 AOF 里的数据更完整。
3.2 持久化参数怎么设置最稳妥
RDB 的触发条件由save参数控制,默认配置大概是 900 秒 1 次修改、300 秒 10 次修改、60 秒 10000 次修改。这个配置适合写入不频繁的场景,但高写入量下 per second 触发会很频繁。我的建议是核心业务线不要依赖默认配置,而是结合数据重要性调整。如果业务能接受最多丢 5 分钟数据,那就用默认配置并配合定时备份;如果只能接受秒级丢失,主要靠 AOF。
AOF 的 fsync 策略有三个档位:always、everysec、no。
always:每次写操作都刷盘,最安全,但性能损耗最大,QPS 会明显下降。everysec:每秒批量刷一次盘,最多丢 1 秒数据,性能和安全的均衡点。no:交给操作系统决定什么时候刷盘,数据丢失风险最大。
生产环境我推荐everysec。有些团队为了极致性能开了no,这在缓存场景可能没事,但凡是把 Redis 用于分布式锁或库存扣减,no就等于埋雷。想想看,锁还没同步到磁盘,进程就崩了,重启后锁的状态全丢了,另一个进程就能拿到同一把锁,互斥逻辑形同虚设。
另外提醒一个点,AOF 文件膨胀之后必须配合重写机制。Redis 默认的auto-aof-rewrite-percentage是 100,auto-aof-rewrite-min-size是 64mb,意思是 AOF 文件比上次重写时大一倍,且文件超过 64MB,就自动触发重写。这个配置在绝大多数场景够用,但如果你用 Redis 做消息队列,写入量极大,建议把触发阈值调小一点,避免 AOF 体积失控。
3.3 数据恢复流程的实操验证
持久化配置完成后,不能只在文档里写着“已开启”,一定要做故障演练。我的标准操作流程是这样的:
- 往 Redis 写入一批测试 key,比如
test:persist:1到test:persist:1000。 - 执行
SHUTDOWN命令模拟正常关闭,再启动 Redis,检查 key 是否都在。 - 执行
kill -9模拟异常宕机,再启动 Redis,观察恢复的数据量。 - 关闭 AOF 只留 RDB,重复上述操作,对比数据丢失情况。
- 观察 Redis 启动日志中的
DB loaded from disk信息,记录加载耗时。
这个过程非常值得做。有一次我把一个 Redis 节点配错了持久化路径,数据写到磁盘了,但启动时加载的是另一个路径下的空 rdb 文件,业务方反馈缓存数据大面积丢失。后来发现是配置文件里dir参数和dbfilename参数拼接后指向了错误目录。这种问题只有通过故障演练才能暴露出来。
4. 高可用架构流程:主从复制与哨兵切换
4.1 主从复制链路是怎么工作的
单机 Redis 的瓶颈不只是容量,还有单点故障。一台机器挂了,整个缓存的流量全部打到数据库,后果可想而知。所以生产环境的 Redis 一定是多节点架构,最基本的是主从模式。
主从复制的完整链路可以这样描述:
- 从节点启动时,向主节点发送
PSYNC命令,请求同步数据。 - 主节点收到命令后,如果是一次全新同步,会执行
BGSAVE生成 RDB 快照,同时把生成快照期间的写命令缓存到缓冲区。 - 主节点把 RDB 文件发给从节点,从节点清空自身数据后加载 RDB。
- RDB 加载完成后,主节点把缓冲区里的增量写命令发给从节点,从节点执行这些命令。
- 之后的日常复制就变成增量复制,主节点每执行一条写命令,就把命令转发给从节点。
这个过程的几个关键点值得注意。第一,主从复制是异步的,主节点不等待从节点确认就返回客户端,所以从节点数据天然存在延迟。如果在业务上要求强一致读,就不能读从节点。第二,运行中如果主从之间的网络断连,重连后会使用复制积压缓冲区做增量同步,但如果断连时间过长导致缓冲区被覆盖,就得退化为全量同步,代价比较大。repl-backlog-size参数默认是 1MB,如果网络不稳定,建议调大到 64MB 以上,减少全量同步频率。
主从架构最常见的部署是“一主两从”或者“一主一从”,配合哨兵使用。从节点除了承担数据备份之外,还可以分担读流量。但要注意,从节点的读延迟可能比主节点高几毫秒到几十毫秒不等,在一致性要求高的场景,不要把读请求分到从节点。
4.2 哨兵如何完成故障自动切换
有了主从复制,主节点挂了,从节点数据还在,但业务不会自动把请求切到从节点。这就需要哨兵。
哨兵是一个独立进程,它的核心职责是监控所有 Redis 节点的健康状态,并在主节点故障时自动执行故障转移。典型流程是:
- 哨兵每隔 1 秒向所有主从节点发送
PING命令,检查是否存活。 - 如果主节点超过
down-after-milliseconds设定的时间没有响应,哨兵标记它为“主观下线”。 - 多个哨兵节点同时判定主节点主观下线,达到
quorum数量后,升级为“客观下线”,确认主节点真的挂了。 - 哨兵们开始选举 Leader,由 Leader 负责执行故障转移。
- Leader 从所有从节点里选择一个数据最完整、优先级最高的从节点,执行
SLAVEOF NO ONE把它提升为新的主节点。 - 其他从节点重新指向新主节点,继续复制。
- 客户端通过哨兵感知到新的主节点地址,完成重新连接。
这个过程中最容易被忽视的是客户端侧的配置。很多人只部署了哨兵进程,但客户端连接 Redis 时依然用的是主节点直连地址,主节点切换后客户端不会自动感知。正确做法是在客户端配置哨兵地址,比如 Java 的 Lettuce 或 Jedis 都支持sentinel模式,客户端会主动从哨兵查询当前主节点地址,切换后自动重连。
关于哨兵数量的设计,生产环境必须部署至少 3 个哨兵。原因是哨兵判断故障要满足 quorum,两个哨兵节点可以完成任务,但两个哨兵恰好挂了一个,就无法形成多数派,故障转移没法执行。三个哨兵允许挂一个,整体还能工作。
4.3 Docker 部署主从和哨兵的注意点
现在很多人用 Docker 搭 Redis 主从,网上类似的教程很多,但有些细节容易踩坑。
第一个坑是端口映射。容器里的 Redis 默认监听 6379,如果用了主从复制,从节点连主节点时如果填的是容器 IP,可能导致连接失败。关键是配置replica-announce-ip和replica-announce-port,让从节点知道用宿主机的 IP 和映射端口来连接主节点。我见过有人搭好主从,日志里一直报Unable to connect,找了半天发现就是没设置 announce 参数。
第二个坑是网络模式。建议直接用 Docker 自定义网络(docker network create),容器之间用容器名互相访问,这样比--link方式更稳定。用 Compose 文件管理时,主从配置可以写在一个 yml 文件里,启动也方便。
第三个坑是数据目录的挂载。生产环境务必把 Redis 的持久化文件挂载到宿主机的磁盘,不然容器一删,数据全没。挂载时还要注意目录权限,Redis 容器进程可能以低权限用户运行,目录权限不够会导致写入失败。
5. 分布式锁主线:互斥流程与防误删细节
5.1 分布式锁的核心业务流程拆解
多实例部署是微服务架构的常态。多个服务实例同时对一个共享资源做写操作时,比如扣减库存,单机 JVM 的 synchronized 锁只能锁住本实例,锁不住其他实例。这时候需要跨进程的分布式锁,Redis 就是最常用的锁容器。
一个标准的 Redis 分布式锁业务流程是:
- 线程 A 尝试加锁,执行
SET lock:product:1001 unique-token NX PX 30000。 - 如果返回 OK,说明加锁成功,线程 A 开始执行库存扣减业务。
- 线程 A 执行完业务后,执行
DEL lock:product:1001释放锁。 - 线程 B 尝试加锁时发现 key 已存在,加锁失败,进入重试或直接返回失败。
其中NX参数保证了只有 key 不存在时才能设置成功,PX设置了锁的自动过期时间,防止线程 A 崩溃后锁永不释放。unique-token是每个线程生成的唯一标识,释放锁时先比对 token 再删除,防止线程 A 的锁被线程 B 误删。
这个流程看起来简单,但展开后有几个必须注意的细节。第一,加锁和设置过期时间必须用SET一条命令原子完成,不能用SETNX和EXPIRE两条命令分开执行,否则一旦设置完 key 后进程崩溃,锁就会永久存在。第二,释放锁的比对和删除也要原子操作,不能先 GET 再 DEL,因为 GET 和 DEL 之间可能被其他线程插入执行。
5.2 锁续期和 Redisson 的 Watchdog 机制
业务执行时间超过了锁的过期时间怎么办。比如锁设置 30 秒过期,但业务逻辑执行了 40 秒,第 30 秒的时候锁自动过期了,此时另一个线程拿到了锁,两个线程同时执行,互斥失效。
解决方案就是锁续期。最简单的续期思路是起一个守护线程,每过锁过期时间的 1/3 就检查 lock 是否还是自己持有,如果是就延长过期时间。Redisson 把这个机制封装成了 Watchdog 自动续期功能,默认锁的 leaseTime 为 30 秒,Watchdog 每 10 秒续期一次。当业务执行完释放锁或 JVM 崩溃时,守护线程也跟着停止,锁最终会自然过期。
这里有一个进阶问题值得讨论:Redis 分布式锁在极端场景下不够绝对安全,比如主节点加锁成功后还没同步到从节点,主节点就宕机了,从节点升主后锁数据丢失,另一线程就能加锁成功。Redisson 的 RedLock 方案就是针对这个问题提出的,通过向多个独立 Redis 节点加锁,超过半数成功才视为加锁成功。但 RedLock 本身的争议很大,我个人的建议是:绝大多数业务对极端情况下的锁失效是可以接受的,分布式锁保证的是 99.9% 情况下的互斥,想要 100% 的强一致,应该引入 ZooKeeper 这类 CP 系统,而不是在 Redis 上死磕。
5.3 锁粒度与业务隔离的实战建议
分布式锁设计里最容易被忽略的是锁粒度。锁的 key 粒度越细,并发度越高,但实现复杂度也越高;粒度越粗,实现越简单,但吞吐量会被锁阻塞。
拿库存扣减这个经典场景来说,锁 key 设计为lock:stock:1001就是商品维度的细粒度锁,同一商品只允许一个线程操作,但不同商品之间互不影响。如果设计成lock:stock这种全局粒度,所有商品的扣减都串行化,吞吐量直接降一个数量级。
另一个建议是锁一定要设置合理的超时时间。超时时间是根据业务执行时间估算出来的,一般设为预估最大执行时间的 3 到 5 倍比较合理。太短容易在业务未完成时提前释放锁,太长则会让其他线程等待过久。配合 Watchdog 自动续期,则超时时间可以设短一些,由续期机制兜底。
还有一点是关于锁失败后的处理方式。加锁失败后直接抛异常返回用户“系统繁忙”,体验很差。更友好的方式是利用 Redis 的发布订阅机制,在锁释放时通知等待线程重新抢锁,或者简单点直接自旋重试几百毫秒再抢。这个取舍要根据具体业务来定。
6. 缓存治理流程:穿透、击穿、雪崩的防线
6.1 三类缓存故障的业务触发场景
缓存治理可以说是 Redis 业务流程中最接地气、最常见的环节。没有治理经验的人,系统一上线被流量一冲就出问题,而且问题往往不是 Redis 本身,而是数据访问链路设计不合理。
先说缓存穿透。业务请求查询一个必然不存在的数据,比如根据不存在的 ID 查询商品,缓存里没有,数据库里也没有。如果这个接口被恶意刷或大量请求同时带上随机 ID,Redis 起不到拦截作用,每个请求都打到数据库,数据库压力暴增。解决手段有两个:一是缓存空值,把 null 也存进 Redis,设置短过期时间;二是布隆过滤器,启动时把所有合法 ID 全量加载到布隆过滤器里,查询前先判断 ID 是否存在,不存在直接返回,数据库连去都不用去。
再说缓存击穿。某个热点 key 刚好在某一刻过期,此时大量请求同时来查,缓存未命中,所有请求同时回源数据库,瞬间打爆。解决手段是对热点 key 做逻辑过期:不直接设 Redis 过期时间,而是把过期时间作为 value 的一部分存进去,每次读取时判断是否过期,过期后先返回旧值,同时只允许一个线程去刷新缓存,其他线程复用旧值。这个方案在秒杀场景下非常好用。
最后说缓存雪崩。大量 key 在同一时间段集体过期,比如缓存初始化时给所有 key 设置了同样的过期时间,到点后大量缓存同时失效,请求全部穿透到数据库。解决手段就是过期时间加随机抖动,比如 300 秒基础过期时间上加 0 到 60 秒随机值。另一个手段是热点数据设置永不过期,配合后台任务主动刷新。
6.2 缓存预热与降级的业务规则
缓存治理不只是出了问题再修,更多是提前布局。
缓存预热是其中一个重要流程。比如每日榜单、首页推荐这类数据,在凌晨低峰期由定时任务提前把热点数据写入 Redis,并设置过期时间为凌晨之后,保证白天高峰期的请求全部命中缓存。预热脚本的执行顺序也有讲究,先清空旧的 key,再批量写入新数据,中间要加开关,防止预热期间用户读到半新半旧的数据。
降级则是另一个方向的治理思路。当 Redis 出现大面积故障或响应变慢时,不能因此拖垮整个业务流程。合理做法是在缓存访问层加上熔断逻辑:如果 Redis 连续 N 次超时,直接放行到数据库,同时记录日志告警。这个降级策略要配合 超时时间 设置,比如 Redis 命令执行超过 200 毫秒就放弃,不阻塞业务线程。
我踩过一个坑是缓存降级的阈值设得太低,导致 Redis 只是稍微抖动,就大面积降级到数据库,数据库反而被拖垮。后来把降级必要的条件改成连续 5 次超时且错误率超过 20% 才触发,同时加了半开恢复机制,每隔 10 秒放少量流量探测 Redis 是否恢复,这个方案稳定运行了很久。
6.3 缓存一致性维护的实用方案对比
缓存和数据库之间的一致性维护,是一个永恒话题。强一致在分布式环境下几乎不可能做到,所以目标是通过合理流程把不一致的时间窗口压缩到业务可接受的范围内。
实用方案有三个,按实现成本和最终效果排序:
- 纯 Cache Aside + 删除重试:业务代码负责更新数据库后删除缓存,删除失败则发送到消息队列重试。成本低,能解决 90% 的问题。
- binlog 订阅 + 异步删除:通过 Canal 监听 MySQL binlog,数据变更后自动删除对应缓存。对业务代码零侵入,适合已有大量调用方、不方便全量改造的老系统。
- 版本号/时间戳兜底:缓存 value 里存数据版本号或更新时间,每次读取时和配置中心拿最新版本号对比,不一致则主动失效。适合对数据新鲜度有高要求的配置类数据。
这三个方案可以叠加使用,核心是双保险。我从第一套方案过渡到第二套方案花了很长时间,切换过程中发现,清理缓存靠业务代码删除总是有不彻底的地方,比如事务回滚了但缓存删除已经执行,数据一致性反而被破坏。用 binlog 方案之后,这种问题天然规避掉了。
7. 高频故障排查流程与实操笔记
7.1 Redis 连接异常的排查步骤
线上 Redis 连接异常是最常见的问题,报错通常表现为Redis connection refused、Redis timeout等。排查思路要从外到内逐层定位。
第一步确认网络连通性。用telnet或nc测试目标 IP 的 6379 端口是否可达;不可达就检查防火墙、安全组、网络策略。这一步能排除大部分容器和跨机房访问问题。
第二步确认 Redis 进程状态。登录服务器执行redis-cli ping,如果返回PONG表示正常;如果返回LOADING说明 Redis 正在加载持久化数据,暂不对外服务;如果返回MISCONF说明开启了保护模式且没有配置密码或绑定地址,需要修改配置。
第三步检查连接数是否打满。INFO clients命令里的connected_clients如果接近maxclients上限,新连接就无法建立。这种情况通常是有连接池泄漏,排查代码里的连接获取和释放是否配对。
第四步看慢查询日志。SLOWLOG GET可以拉取慢命令,慢命令会占用 Redis 单线程,导致后续请求排队超时。常见慢命令有大 key 的 DEL、复杂的 KEYS、高耗时的 RANGE 操作,这些要结合业务代码优化。
7.2 日志分析与监控的体系搭建
排查故障依赖的现场就是日志和监控。我的监控体系分三层:
- 基础层:用 Prometheus + redis_exporter 采集 Redis 运行指标,包括内存使用率、连接数、命中率、QPS。磁盘和 CPU 用 node_exporter 采集。
- 容量层:重点监控
used_memory和maxmemory的比值。超过 80% 就要警惕内存淘汰,超过 90% 就要扩容或清理数据。Redis 内存淘汰策略默认是noeviction,内存满了直接拒绝写操作,业务侧表现为写入报错。线上建议设置allkeys-lru,允许缓存数据按 LRU 策略淘汰。 - 业务层:在客户端埋点监控命令的平均耗时和错误数,并用日志记录缓存未命中的 key 分布,一旦某个 key 的未命中率异常升高,主动告警。
日志方面,Redis 自身的日志默认只输出到 stdout,容器部署时要重定向到宿主机文件,再接入 ELK。重点日志包括主从切换的记录、持久化失败记录、慢查询记录。它们能帮助你在故障发生后还原整个执行链路。
7.3 几个容易忽视但影响深远的实操禁忌
最后分享几个我在实战中踩过的和看到别人踩过的坑,这些细节不在 Redis 文档里,但每一条都值得记在小本本上。
第一,生产环境禁用KEYS *命令。它在 key 数量多时会阻塞 Redis 主线程,导致所有请求超时。务必用SCAN命令替代,虽然慢一点但不会阻塞。如果只是想知道总 key 数,用DBSIZE。
第二,注意大 key 的清理方式。一个 value 达到几百 MB,删除它时 Redis 会阻塞一段时间,期间所有命令排队。建议用UNLINK命令替代DEL,UNLINK 是异步删除,不会阻塞主线程。
第三,Redis 镜像版本选择要保守。不要一看到新版就升级,我遇到过从 Redis 6.2 升到 7.0 后,某些客户端连接参数不兼容,导致频繁断连。生产环境优先使用官方稳定版,升级前先做完整的回归测试。
第四,密码配置不要只依赖requirepass。开启后客户端每次连接都要认证,连接池中的每个连接都需要配置密码。多环境部署时,要确保配置文件中的密码和实际redis.conf一致,凡是密码不一致的故障基本都是这个原因。
第五,容器化部署时,时刻记得maxmemory的设置。Docker 默认没有设置内存上限,Redis 进程消费完宿主机内存,触发操作系统 OOM,反而把整个宿主机拖死。建议容器内限 2GB,宿主机留 20% 余量。
8. 最后再聊点实在的踩坑体感
文章写到这儿,Redis 核心业务流程的几条主线基本都过了一遍。从读写缓存到持久化,从主从复制到哨兵切换,从分布式锁到缓存治理,再到高频故障排查,这些环节几乎覆盖了日常开发和运维会碰到的全部核心场景。
我个人在实际操作中的体会是,Redis 系统真正考验人的不是单个知识点,而是知识点的串联能力。很多人会配置主从,但没想过主从切换的窗口期数据丢失怎么兜底;会用分布式锁,但没想过锁续期和 token 校验;会设置缓存过期时间,但没想过随机抖动防止雪崩。这些问题单独拿出来都不难,但组合在一起就是架构能力的分水岭。
根据个人经验,建议每个团队把 Redis 的核心流程整理成一张运维清单,至少包含:缓存 key 规范和序列化方式、持久化参数和备份策略、主从和哨兵的部署架构、分布式锁的使用规范、缓存故障的应对预案、以及监控告警的阈值。有了这份清单,新员工入职能快速上手,老员工踩过的坑也不会反复踩。
最后再分享一个小技巧。排查 Redis 问题时,别急着上工具,先想清楚数据在每一条流程里的流动路径。比如缓存数据不一致,先想缓存更新流程在哪里断开了;主从切换出问题,先想哨兵选举流程的哪个环节失败了;锁失效,先想加锁和续期流程哪里没兜住。想清楚流程,再去看日志和命令输出,问题往往迎刃而解。