ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis脑裂问题全解析:数据丢失原理、防护参数与排查思路

Redis脑裂问题全解析:数据丢失原理、防护参数与排查思路 复习这套Redis知识的时候我习惯把脑裂问题放在高可用这一节的最前面。原因是它太容易踩中了表面上看主从切换一切正常哨兵该选举选举、该通知通知但当你去比对数据时才发现切换期间写入旧主的几万条数据已经无声无息地没了。更麻烦的是Redis集群的脑裂不像ZooKeeper那样表现为“两个主节点同时对外服务”它的主角往往是“一个已经失联但进程还活着的旧主节点”。这个问题不搞清楚Redis的高可用架构就是纸糊的。这篇文章从脑裂的成因讲起把数据丢失的时间线、防脑裂参数配置、真实故障排查思路一次性聊透。适合正在复习Redis面试题的同学也适合线上Redis集群出过“灵异丢数据”事故、想弄明白根因的运维和开发。1. 脑裂到底是什么分布式系统的经典故障1.1 从“双领导”类比说起Redis里脑裂长什么样脑裂这个词最早是从分布式系统里“Split Brain”直译过来的。想象一个两千人的公司董事会因为通讯线路故障分成两派两派都联系不上对方于是各自都觉得“现在公司是我说了算”同时发布了两套完全不同的经营指令。等通讯恢复员工发现收到过两套互相矛盾的指令但实际执行下去的已经收不回来了。Redis里的脑裂很类似但有个容易被新手忽略的细节多数时候旧主节点并没有宕机它只是“失联”了。比如一台Redis的master和它的哨兵、从节点在网络上被切开了master这边进程照常运行还在正常接收客户端的写请求它根本不知道自己已经被其他节点“抛弃”了。与此同时哨兵那边通过多数派机制判断出master可能挂了于是从剩下可达的从节点里选出一个新master开始对外提供服务。于是尴尬的局面就出现了旧master还在傻乎乎地写数据新master也在正常接收写入。通信恢复后哨兵会把旧master降级成slave让它重新去同步新master的数据。降级的第一步是清空本地数据旧master在失联期间写入的数据就这么被当成“异物”直接抹掉了。这就是Redis版本脑裂最经典的面貌不是两个master一直同时活着而是一个“假死”的master被强制退休顺带把它假死期间的数据全部带走了。1.2 Redis三种高可用架构下的脑裂形态Redis的高可用方案看着多其实脑裂的底层逻辑是一致的分区 异步复制 自动切换三个条件凑齐丢数据就成了大概率事件。主从加哨兵Sentinel是最常见的形态。哨兵进程通过PING来探活当master在down-after-milliseconds配置的时间内没有响应单个哨兵会先标记它“主观下线”。当达到quorum数量的哨兵都认为master不可达时才升级为“客观下线”触发故障转移流程从候选从节点里选出一个晋升为新master。这里有个关键点从旧master失联到新master被选出来中间隔着一段时间窗口。假设哨兵判定耗时5秒选举加通知耗时3秒那这8秒里旧master如果还在接受写入这些写请求就全部处在“待删除”状态。Redis Cluster集群模式的脑裂思路也类似只是判定机制换成了Gossip协议和cluster-node-timeout参数。默认情况下一个master超过15000毫秒没被其他节点感知到就会被标记为pfail疑似下线随后被传播成fail确定下线并引发从节点选举。注意这里的15000毫秒是很大的如果网络分区持续了几分钟旧master累积的写入量会非常可观。还有一种容易被忽略的情况是纯主从架构没有哨兵。如果master和slave网络断开master继续写自己的slave继续同步旧的等网络恢复后slave自动重新全量同步期间slave的数据其实已经悬空了。这种情况严格说也是脑裂的一种变体因为没有自动切换不会出现“双主”但数据不一致问题一点没少。我在排查故障的时候总结了一句话只要存在“网络分区 主从数据异步复制 某个角色自动切换”这三个元素就必须把脑裂放进排查清单里。2. 为什么脑裂最怕的是“写丢失”2.1 一次故障的时间线推演把丢失数据算给你看光说理论没感觉我们推演一个生产场景把数字算出来就直观了。假设某个Redis集群峰值写入约2000 QPSmaster和slave部署在同一机房的不同机架三个哨兵监控。某天交换机的一个端口出现异常master所在的机器被网络隔离但进程没挂本机客户端连接正常。此时时间线是这样的第0秒网络分区发生master失去与所有哨兵、从节点的通信能力但还在正常接收客户端写入。第5秒3个哨兵中有2个判定master主观下线达到quorum升级为客观下线开始走故障转移流程。第8秒哨兵选出优先级最高、复制偏移量最靠前的slave晋升为新master客户端通过哨兵感知到新地址开始切流。第40秒网络恢复旧master重新连上集群。哨兵通过slaveof指令强制它降级为从节点旧master清空本地数据尝试向新master做全量同步。把写入量算上2000 QPS乘以40秒等于80000条写请求。这8万条数据在旧master上被写进去了也返回客户端成功了但最后被清空。客户端那边看到的是“写入成功”实际上数据消失了。更阴险的是如果网络抖动反复发生这种时间窗口会反复出现。每次窗口可能只有几秒但架不住积少成多最终给业务方造成“Redis偶尔丢数据”的模糊印象。2.2 除了丢失还有“脏数据覆盖”和“锁失效”两个隐藏问题写丢失是脑裂最直接的危害但还有两个不怎么被提起的副作用同样是生产事故的种子。第一个是脏数据覆盖。旧master降级后不会保留自己的写数据但它的角色变成了slave会接收新master同步过来的数据。如果接下来的写入操作命中了相同的主键就会出现一种情况旧master之前写的那个值已经被清掉新master同步过来的值可能是完全不同的另一个版本。如果业务没有做幂等或版本校验旧数据覆盖新数据、新数据覆盖旧数据互相覆盖的混乱状态很难追查。第二个问题在分布式锁场景下尤其危险。用Redis SETNX实现分布式锁时脑裂会导致两个客户端同时持有同一把锁因为两个master都对外提供了写入服务。等分区恢复旧master被降级并清空数据但它发出的“我拿到锁了”这个结果已经返回给客户端了。锁的互斥性在脑裂窗口内被彻底击穿。这也是为什么很多严苛的业务不会把分布式锁作为唯一方案要么配合数据库唯一约束兜底要么设计fencing token机制做二次校验。2.3 哪些生产环境最容易触发脑裂排除掉纯粹的人为误操作之后生产环境触发脑裂的原因其实高度集中网络抖动和交换机故障排在第一位。机房里的网卡、光模块、交换机固件bug都可能让一台机器“网断了但活得好好的”。长时间的GC停顿也见过。JVM应用和Redis混部在同一台宿主机上时如果宿主机内存压力大GC长暂停几十秒Redis进程虽然活着但心跳响应已经停滞哨兵照样会判定下线。云环境的宿主机热迁移、网络策略变更也会造成类似效果。很多云厂商的“维护事件”里都会提示网络瞬断风险但很多团队没有把这个事件和Redis脑裂关联起来。自己误操作也不少见比如防火墙规则误改、交换机端口配置错误、运维在做网络割接时忘了通知业务方。我印象很深的一次事故发生在Kubernetes环境里节点之间因为NetworkPolicy配置不当互相ping不通但Redis进程本身完全没有报错。等发现时业务已经静默丢了几分钟的数据。所以排查脑裂时别只盯Redis日志底层网络事件和节点事件往往能提供更关键的线索。3. 防脑裂的核心配置min-replicas 系列参数3.1 参数原理主节点也要“安全确认”Redis官方的建议是用min-replicas-to-write和min-replicas-max-lag这两个参数把“写入开关”和“主节点对从节点的感知能力”绑在一起。核心思路是master不应该无条件接受写入。它需要定期收到从节点的复制确认ACKACK会带上从节点当前同步到的复制偏移量。如果master在一段时间内连一个从节点的ACK都没收到或者收到的ACK显示从节点落后太多master就主动拒绝写请求直接给客户端返回错误。这两个参数配合起来效果就是“只有当至少N个从节点在M秒内正常和master保持复制心跳时master才允许写入”。这样一来一旦发生网络分区旧master迅速丢失所有从节点的ACK写请求会在很短的时间内被拒掉。等哨兵完成新master的选举旧master那边已经不再接收新写入数据丢失窗口就被大幅压缩。这两个参数的默认值都是0也就是说默认情况下master不会做任何限制。这也是很多线上事故的原因集群是搭起来了哨兵也配了但没人把这两个参数改掉。3.2 实战配置从节点数1个、滞后10秒的推荐起点最稳妥的起步配置是min-replicas-to-write 1 min-replicas-max-lag 10含义是master至少要能感知到1个从节点且这个从节点的复制延迟在10秒以内才接受写请求。不满足就直接拒绝写客户端会收到类似MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to no或者-MISCONF Redis is configured to save RDB snapshots...这类错误提示。如果是老版本Redis 5.0以前参数名是min-slaves-to-write和min-slaves-max-lag。Redis 5.0加入了min-replicas系列命名但为了兼容旧参数名仍然可用。集群模式的配置文件写法一样可以直接写在redis.conf里也可以运行时执行CONFIG SET min-replicas-to-write 1 CONFIG SET min-replicas-max-lag 10这里有个细节值得多说一句为什么推荐从节点数配1、lag配10而不是配3个从节点、lag配2秒min-replicas-to-write配成1前提是你的集群里每个master至少带1个从节点。如果你配成2但实际只挂了1个从节点那master会一直拒绝写入等于自断后路。所以这个值要实事求是根据架构里的真实副本数来定。min-replicas-max-lag配10是平衡“丢失窗口”和“误伤概率”的结果。lag配太小比如2秒那么从节点只要稍微抖动一下、主从复制慢了一点master就开始拒绝写入正常业务会频繁看到写入失败。lag配太大比如60秒网络分区后旧master还能继续写将近一分钟丢失的数据量又涨上去了。10秒是一个比较稳妥的中间值大多数正常情况下的复制延迟都远低于这个值而脑裂场景下旧master通常几秒内就会和所有从节点失去ACK联系。3.3 这个方案能保证不丢吗认识它的边界必须说清楚min-replicas系列参数只能尽量缩小丢失窗口做不到零丢失。它保护的是“旧master和它的从节点之间失去联系”的场景。如果网络分区比较特殊比如旧master仍然能和某一个从节点保持复制心跳但和哨兵以及其他所有节点都隔离了那旧master会一直认为自己有健康的从节点继续接受写入。这种情况下min-replicas参数不会触发写丢失照样会发生。所以这个方案的真实定位是在大多数常见分区场景下把“整个分区期间都在写”变成“分区发生后几十秒内停止写入”。丢失窗口从分钟级压缩到秒级这个效果已经足够让绝大多数业务恢复元气。如果业务对数据一致性要求极高需要进一步压降丢失风险可以在客户端配合Redis的WAIT命令。WAIT numreplicas timeout可以阻塞等待指定数量的从节点确认收到某次写入相当于把异步复制临时变成半同步复制但代价是写延迟显著上升吞吐量下降。再往上的方案比如写本地消息队列做补偿、跨机房双写、直接用强一致性的存储产品就是架构层面的取舍了不是Redis配置能解决的问题。3.4 集群模式还要看cluster-node-timeout用Redis Cluster时除了min-replicas系列参数cluster-node-timeout也是脑裂相关的关键参数。默认值是15000毫秒也就是15秒。节点之间超过这个时间没收到彼此的心跳才会开始标记疑似下线。这个值直接决定了“从网络断开到集群开始响应”的间隔。调小一点比如5000毫秒故障切换的响应速度会变快但调太小也有风险瞬时网络抖动就可能触发节点标记引发不必要的选举切换。这个参数要和min-replicas-max-lag配合起来看整体原则是让master拒绝写入的速度快于集群完成切换的速度这样即使发生切换旧master那边也已经没什么新数据可丢了。4. 真实复盘排查脑裂的完整思路4.1 一次典型故障复盘的五个步骤我自己经历过的脑裂事故复盘的时候基本沿着五步走每一步都能筛掉一批干扰项。第一步锁定现象。业务侧反馈Redis开始大量写入失败同时监控显示某个分片出现瞬时主从切换。这时候先不要急着重启或切流把事故时间点记下来方便后面和日志对上。第二步查Redis日志。旧master的日志里会出现类似MASTER aborted replication with an error或REPLICAOF new-master enabled之类的记录表示它已经收到降级指令开始向新master同步。哨兵日志里会有switch-master事件记录旧master和新master的地址变化。把这两个时间点对齐就能看出脑裂窗口大概有多长。第三步对比复制偏移量。在旧master和新master上分别执行INFO replication看master_repl_offset和slave_repl_offset。正常情况下新master的偏移量会明显领先于旧master降级前的偏移量两者之间的差距就是丢失数据的规模。第四步查客户端行为。通过CLIENT LIST观察事故时间点有哪些客户端连接在旧master上持续写入。如果有服务端连接池没有及时刷新master地址会有一部分客户端在哨兵切换后仍然往旧master写直到连接被拒绝。第五步结合网络事件下结论。把Redis日志时间线和宿主机网络事件、交换机变更记录对齐。多数情况下你会发现Redis日志里没有任何报错真正的根因在操作系统层面或网络设备层面。4.2 排查命令与日志关键词速查表排查对象关键命令/日志判断要点当前节点角色INFO replicationrole:master还是role:slaveconnected_slaves是否正常复制进度INFO replication中的master_repl_offset、slave_repl_offset两者差值越来越小说明在追赶基本不变说明同步中断哨兵视角sentinel master mymaster看当前master地址是否和预期一致哨兵日志switch-master记录旧master到新master的切换时间和地址变化Redis实例日志MASTER aborted replication、REPLICAOF enabled旧master降级、开始全量同步的标记客户端连接情况CLIENT LIST事故时间点还有多少客户端在往旧master写数据慢查询与假死判断SLOWLOG GET结合宿主机GC时间排查进程假死导致的心跳无响应4.3 关于脑裂的三个常见误区第一个误区部署了哨兵就等于不会脑裂。实际上哨兵解决的是“自动故障转移”问题它不会阻止脑裂窗口内的数据丢失。哨兵切换做得越快丢失窗口越小但窗口永远存在。第二个误区把min-replicas-to-write配高一点就更安全。这个配置的前提和实际副本数强相关配得比真实副本数还高只能得到一个永远拒绝写入的集群。正确的做法是先数清楚每个master带几个slave再决定这个值。第三个误区Redis没报错就不是脑裂。大量脑裂场景中旧master的日志干干净净因为它确实没有感知到自己失联。排查思路不能只盯Redis本身要把网络层、宿主机层的事件一起拉出来看。5. 架构层面怎么减少脑裂影响5.1 客户端、应用和部署层面的配合只调Redis参数还不够客户端的行为在脑裂场景里同样关键。最基础的要求是客户端必须通过哨兵或Cluster的总控节点感知master地址不能把master地址写死在配置里。否则切换完成后一部分客户端还握着旧地址往里写Redis服务端会拒绝这些写入业务方会看到一堆READONLY错误但至少数据不会继续丢在旧master上。应用层可以做的兜底是把写入失败的数据记录到本地或消息队列等Redis恢复后重新投递。这个方案不复杂但收益非常明确即使Redis在极端情况下丢了数据应用层还有一个可回放的数据源。部署层面有个容易被忽略的点master和它的slave应该避免放在同一个网络故障域里比如同一台交换机的不同端口或者同一个机架。如果master和slave同时被一个交换机故障带走哨兵连候选从节点都找不到那就不是脑裂丢数据的问题而是整个分片不可用了。跨机架、跨可用区部署是降低脑裂影响的有效手段。5.2 面试被问“Redis脑裂”时怎么答这道题是Redis面试的高频题回答的时候不要只丢一个结论最好按“是什么、为什么、怎么防、防到什么程度”的框架来讲。先讲定义脑裂是网络分区导致的多个节点同时认为自己是主节点的现象。再讲Redis特有的场景旧master进程存活但网络失联继续接收写入哨兵选出新master后旧master降级并清空数据。接着讲危害异步复制的时间窗口内写入旧master的数据全部丢失严重时影响分布式锁互斥性。然后讲方案min-replicas-to-write和min-replicas-max-lag让master失去从节点ACK时拒绝写入压缩丢失窗口客户端通过哨兵感知master变化应用层做消息兜底。最后补充边界这个方案无法做到零丢失只能缩小窗口。能主动提到“复制偏移量对比”和“WAIT命令”会让面试官觉得你是真处理过问题而不是只背了八股文。5.3 说句实在话取舍比参数更重要每次配置完Redis的高可用环境我都会问自己一个问题如果现在把网络切断系统会丢多少数据能接受吗这个问题没有标准答案。有的业务丢了10秒数据也无所谓有的业务丢1条就是事故。min-replicas-max-lag配10还是配3cluster-node-timeout配15000还是5000本质上都是在延迟故障切换的误判率和数据丢失的窗口之间做取舍。参数调完以后我强烈建议找一个演练窗口人为断一次网或者关掉一台master的网卡观察集群的切换行为和数据丢失量。纸上谈兵永远没有真实故障来得直观。我自己踩过坑之后的习惯是线上集群巡检时固定看INFO replication的lag值只要发现主从复制延迟持续超过阈值就立刻查网络和慢日志。脑裂这种事防是防不住的但能不能在窗口期发现并止损才是真正区分经验和教训的地方。
返回列表