资讯动态

Redis主从复制机制全解:偏移量、积压缓冲区与延迟排查

发布时间:2026/10/27 20:11:39 来源:尧图企业网站定制
主从复制这件事很多团队是在业务量上来之后才临时补的结果配置一加、从库一挂问题全冒出来了。我在不同规模的线上环境里见过太多类似的场景主库的INFO replication里connected_slaves显示三个从库master_link_status全是up看起来一切正常可应用侧读到的数据就是比主库慢半拍某些键莫名其妙地“消失”又“复活”。这类问题几乎都不是 Redis 本身坏了而是对主从复制的运行机制缺少完整的认识不知道数据是以什么粒度在链路上流动的不知道全量同步和增量同步的分界线在哪里也不知道哪些参数决定了断线之后还能不能接得上。这篇内容围绕 Redis 主从复制机制本身展开从偏移量、复制 ID、积压缓冲区这些底层概念讲到实际搭建、拓扑选型、复制风暴与延迟排查再到没有哨兵时的手工切主流程。适合已经能把 Redis 跑起来、但还没把主从彻底摸透的开发者也适合正在为读写分离方案做技术选型的人。1. 主从复制的真实定位它不是备份也不是高可用1.1 复制解决的是“读”和“热备”不是“丢数据”先把定位摆正后面所有配置决策才不会跑偏。Redis 主从复制本质上是单向的、异步的数据流复制主库把写命令按执行顺序打包持续推给从库从库重放这些命令来保持数据一致。它解决的核心问题是两件事——横向扩展读能力以及准备一份可以随时接管的热备副本。但它不解决“数据绝对不丢”。主库执行完一条SET就返回客户端成功此时这条命令还在主库的复制缓冲区里排队从库收到没有、落盘没有主库根本不关心。所以只要主库在命令发出后、从库确认前的窗口里挂掉这条数据就是丢了而且丢得毫无痕迹。这一点和很多人印象里的“从库就是备份”差距很大。真正意义上的备份是 RDB 快照文件、AOF 文件被搬运到异地存储介质上那份数据不依赖任何进程活着。从库顶多算一份“热副本”它可能比主库慢、可能在复制中断期间缺少一段数据、也可能因为一次误操作被主库的FLUSHALL同步清空。把从库当备份用是线上事故里最常见的一类误判。我在做方案评审时通常只问一句这个从库是用来扛读流量的还是用来做数据兜底的答案不同后面的资源配置、replica-read-only设置、监控指标、甚至拓扑结构都不一样。如果是为了兜底那从库不应该承接任何业务读最好独立部署、定期把 RDB 转移到别的存储上如果是扛读流量那就得接受“从库数据有延迟”这个前提并在应用层设计好降级逻辑。1.2 三个必须先想清楚的问题读一致性、延迟预算、切换方式动手配replicaof之前有三个问题必须先有答案否则搭出来的主从迟早要返工。第一个是读一致性要求。业务能不能接受“刚写完立刻读可能读不到”如果某个接口是典型的“写后立即读”比如下单后立刻查订单状态那这个读请求必须走主库或者走从库但加一层“写时把 key 标记、读时校验版本”的兜底。很多团队的做法是简单粗暴地全部读从库然后在压测时发现偶发数据不一致回头排查半天才发现是复制延迟。这个坑不该在线上踩。第二个是延迟预算。主从延迟不是恒定的它随写入量、网络状况、从库负载波动。正常情况下同机房内网延迟在毫秒级但如果主库写入突增、或者从库执行了某个KEYS类的慢命令被阻塞延迟能瞬间冲到几秒甚至几十秒。业务侧需要明确一个可接受的上限比如 200ms超过这个值就应该触发告警而不是等用户投诉。第三个是切换方式。手工切、哨兵自动切、还是集群模式三者的复杂度和运维成本差得很远。手工切最灵活但对故障响应速度要求高哨兵能自动发现主库故障并选举新主但引入了新的组件和新的不确定性比如网络分区时的脑裂。这一层想在前面后面才不会在主库挂掉的时候手忙脚乱。提示如果团队里没人能说清楚“主库挂了之后谁来切、切完应用怎么连”那这套主从只是搭给监控看的不具备真实的高可用能力。2. 复制链路里流动的到底是什么offset、replid 与 backlog2.1 字节偏移量主从状态对账的唯一硬指标复制偏移量replication offset是整个主从机制里最核心也最容易被忽略的概念。它不是“第几条命令”而是主库复制流的字节累计值。主库每向复制流写入一个字节master_repl_offset就加一从库每重放一个字节slave_repl_offset也加一。这个设计非常巧妙偏移量天然是全序的、单调递增的主从双方只要报出自己的偏移量就能立刻算出差距有多大。在从库上执行INFO replication看到master_repl_offset: 12345678 slave_repl_offset: 12345600这两个值的差 78意味着从库落后主库 78 个字节的复制流。注意是字节不是命令条数一条SET user:1 xxx可能几十个字节具体要看 value 长度和协议开销。实操中我更习惯在主库上执行INFO replication看slave0、slave1这些行的offset字段和主库的master_repl_offset逐一做减法。同时关注lag字段它表示从库最后一次向主库发送REPLCONF ACK距今的秒数。这两个指标要结合起来看如果 offset 差得很大但 lag 是 0说明从库一直在正常确认只是追不上写入速度如果 lag 已经几十秒了那基本可以判定链路已经出问题了。需要注意lag的精度是一秒级它反映的是“最后一次确认距今多久”不是精确的延迟值。想看更细的延迟得靠应用层打点比如在写主库的同时记录时间戳读从库时对比。2.2 replid 与 replid2换了“血统”还能不能续上复制 IDreplication ID简称 replid是主库的“身份标识”一个 40 位的十六进制字符串主库启动时随机生成。从库连接主库时会记住这个 ID断线重连时带着它一起发过去主库一看“哦你之前跟的是我”才愿意做增量同步。问题在于主库重启。如果主库是正常重启它会从 RDB 或 AOF 里恢复出原来的 replid从库还能续上。但如果主库是通过replicaof no one提升上来的、或者配置被清掉了它会生成一个全新的 replid此时原先的从库带着旧 ID 来请求增量同步主库直接拒绝返回FULLRESYNC走全量。为了让“主库重启 从库提升”这类场景不至于每次都全量Redis 引入了replid2机制PSYNC2。简单说从库被提升为主库时会同时保留旧主的 replid 作为replid2并记录一个second_repl_offset作为分界点。这样旧主的其他从库拿着老 ID 来连只要偏移量在分界点之前新主仍然可以接受它们做部分重同步。这个机制在“主库挂了、从库顶上、旧主恢复后重新加入”的典型流程里价值极大能省掉一次全量传输。在INFO replication里能看到master_replid和master_replid2两个字段。日常运维不太需要直接操作它们但排查“为什么这次重连又全量了”时看一眼 replid 有没有变往往能定位到根因。2.3 repl-backlog那个默认只有 1MB 的环形缓冲区积压缓冲区replication backlog是主库维护的一个固定大小的环形缓冲区专门存最近一段时间的复制流。从库断线重连请求增量同步时主库会检查从库的偏移量是否还落在 backlog 覆盖的范围内在的话就把缺失的那段数据补给它不在就全量。关键点在于默认的repl-backlog-size只有 1MB。这个值放在今天几乎是个玩笑。假设主库复制流每秒写 500KB这个量级对于一个中等写入压力的业务很常见一条 value 200 字节的写命令每秒 2500 次写入就够了1MB 的 backlog 只能覆盖大约 2 秒的断线。也就是说只要网络抖一下超过两秒从库重连就得全量同步。这个参数该设多大有个简单的估算方法参数计算方式示例复制流写入速率用主库master_repl_offset的增量除以时间10 秒内增长 5MB即 0.5MB/s可容忍断线时长按网络抖动、运维操作窗口估60 秒理论需要值速率 × 时长30MB实际配置值理论值 × 1.5 到 2 倍冗余64MB所以 64MB 到 128MB 是个比较稳妥的起点写入特别重的实例可以给到 256MB。代价是主库会额外占用这么多内存但这个开销比起一次全量同步带来的 fork 阻塞和带宽冲击完全值得。还有一个参数repl-backlog-ttl默认 3600 秒。它控制的是“主库上没有任何从库连接之后backlog 保留多久再释放”。如果你的场景是主库重启后从库会延迟一段时间才连上来可以考虑调大这个值让 backlog 多留一会儿。注意repl-backlog-size可以在运行时用CONFIG SET调整但调整后 backlog 会被重建之前的内容丢失。所以要在业务低峰期做且调整后立即用CONFIG REWRITE写回配置文件避免重启丢失。3. 一次 PSYNC 的完整旅程从 PING 到命令流3.1 握手阶段的四步与常见卡点从库配置了replicaof之后它并不会立刻开始收数据中间要走一套完整的握手。这套流程理解了很多“从库一直是down状态”的问题能自己定位。第一步从库向主库建立 TCP 连接发送PING主库回PONG。如果这一步就失败说明网络不通、端口不对、或者主库根本没起来。这是最常见的一类问题用telnet或nc从从库机器上试一下就知道了。第二步如果主库配置了requirepass从库需要用masterauth里的密码做AUTH。这里有个非常经典的坑主库有密码但从库没配masterauth从库连上之后一直报NOAUTH状态卡在down。另一个反向的坑是主从密码不一致日志里会有Unable to AUTH to MASTER。第三步从库发送REPLCONF listening-port port告诉主库自己监听的端口主库会把这个信息记进INFO replication的slaveN行里。同时发送REPLCONF capa eof capa psync2声明自己支持无盘传输和 PSYNC2 协议。如果这里能力协商失败就会退化成老版本的SYNC每次都是全量。第四步从库发送PSYNC replid offset。首次连接时是PSYNC ? -1意思是“我没有历史给我全量”。主库收到后如果判断无法做部分重同步就返回FULLRESYNC replid offset把当前主库的 replid 和最新偏移量告诉从库随后开始全量流程。这四步里任何一步失败从库的master_link_status都会是downmaster_last_io_seconds_ago会持续增长。排查顺序就按这四步来基本能覆盖 90% 的连接类故障。3.2 FULLRESYNC 期间的三份数据缺一不可全量同步是整个主从机制里最“重”的操作也是故障高发区。理解它的关键是知道在 fork 出去生成 RDB 的那段时间里主库同时在处理三份数据。第一份是基准快照。主库调用fork()创建子进程子进程把当前内存里的数据写成一个 RDB 文件或者直接通过 socket 写给从库这就是无盘复制。fork 本身是写时复制COW阻塞时间通常很短但内存页表大的时候可能达到几十毫秒甚至上百毫秒这段时间主库是完全停止响应的。第二份是增量缓冲。子进程在写 RDB 的整个过程中主库还在接收新的写命令。这些命令不能丢会被追加到该从库的复制输出缓冲区里等 RDB 传完之后再补发。这部分数据量取决于 RDB 生成耗时和写入压力如果 RDB 写了 30 秒、期间写入很猛缓冲区可能堆到几百 MB直接触发client-output-buffer-limit把从库踢掉。第三份是积压缓冲区。前面提到的repl-backlog在主库有从库连接期间一直存在全量同步结束后从库的偏移量会和它对齐后续的增量同步就靠它兜底。从库这边的动作是收到 RDB 后先清空自己现有的全部数据FLUSHALL级别的效果再加载 RDB。这就是为什么从库在做全量同步期间内存可能短暂翻倍——旧数据还没释放干净新数据已经在加载。如果从库内存吃紧很可能在这个阶段被系统 OOM 杀掉。无盘复制repl-diskless-sync可以省掉磁盘 IO但需要网络带宽足够且repl-diskless-sync-delay默认是 5 秒意思是主库会等 5 秒看有没有更多从库来连凑一批一起做全量减少 fork 次数。这个设计很聪明但首次同步的从库会多等 5 秒才拿到数据。追求快速同步的场景可以调小这个值代价是 fork 更频繁。3.3 增量同步的判定条件与 CONTINUE全量同步是最坏情况日常更关心的是断线重连能不能走增量。主库收到PSYNC replid offset后要同时满足两个条件才会回CONTINUE一是从库带来的replid必须等于主库当前的master_replid或者等于master_replid2。前者是直接续上后者是跨了一次提升的续上。二是从库的offset必须落在 backlog 的覆盖范围内。backlog 有一个repl_backlog_first_byte_offset表示最老的有效位置从库的 offset 如果小于这个值说明那段数据已经被覆盖了只能全量。这两个条件缺一个主库就回FULLRESYNC。所以你会看到一个很有意思的现象网络只抖了两秒从库重连却触发了全量。原因往往就是 backlog 太小两秒的写入量已经把 1MB 的缓冲区冲掉了。CONTINUE之后主库会把 backlog 里从该 offset 开始的数据一次性推过去从库重放完之后就恢复正常的命令流传播模式。之后主库每执行一条写命令都会异步地写进每个从库的输出缓冲区从库每秒发一次REPLCONF ACK offset告诉主库自己处理到哪了主库根据这个值更新slaveN的offset字段。有一条经验值得记住不要把REPLCONF ACK的间隔当延迟指标。它固定一秒一次反映的是从库“还活着且处理到了某位置”而不是“实时延迟”。真正的延迟要靠 offset 差值来算。4. 动手搭一套配置项、运行时命令与拓扑选型4.1 配置文件与运行时命令的等价写法配置从库有两种方式效果一样但适用场景不同。配置文件方式适合固化的生产环境运行时命令方式适合临时验证和动态调整。配置文件写法从库的redis.confreplicaof 10.0.0.11 6379 masterauth your_password_here replica-read-only yes replica-serve-stale-data yes repl-timeout 60改完之后需要重启从库生效。如果主库没有设密码masterauth这行可以省略但建议即使当前没密码也先在配置里预留注释方便后续启用。运行时命令方式# 在从库上执行 config set masterauth your_password_here # 先设置否则后续连不上 replicaof 10.0.0.11 6379 config rewrite # 写回配置文件避免重启丢失顺序很重要。如果先执行replicaof再设密码从库会先尝试连接、认证失败、状态变成down然后你设完密码它还要等下一轮重试才恢复。虽然最终也能成但会多绕一会儿。反过来把一个从库提升成主库replicaof no one这条命令会让从库断开与主库的复制关系保留现有数据开始接受写请求。注意它不会自动清空masterauth之类的配置后续如果要把这个实例重新变成从库直接再replicaof指过去就行。Redis 5.0 之后推荐用replicaof而不是老的slaveof。两者功能等价但后者在新版本里已经被标记为兼容保留新项目没必要再用旧名字。同理replica-read-only对应老的slave-read-onlyINFO replication里的字段也逐步从slave前缀改成了replica不过为了兼容很多字段名至今没变看输出时不用困惑。4.2 三种拓扑的代价对比主从的拓扑结构不是随便连的不同结构对应的资源消耗和故障影响面差别很大。拓扑结构优点代价与风险一主多从星型所有从库直连主库结构简单延迟最低主库 fork 一次要服务 N 个从库从库多时主库出口带宽吃紧全量同步风暴级联树状主库 → 中间层从库 → 叶子从库分摊主库复制压力叶子节点多时优势明显层级越深延迟越大中间层故障会连带影响整棵子树主从从链式主库 → 从库A → 从库B部署简单适合跨机房延迟累积严重A 挂了 B 也断不推荐做业务读星型结构适合从库数量不多比如 3 到 5 个、且都在同一机房的情况。这时候主库的压力可控延迟也最容易保证。一旦从库数量上到 10 个以上或者有跨机房读的需求就该考虑级联。让每个机房的第一个从库去连主库同机房的其他从库连这个“代理从库”主库的复制连接数立刻从 10 降到 3。代价是叶子节点比主库慢两跳延迟会显著增加。链式结构我一般不建议用于承载业务读它的价值更多在于“临时扩一个只读副本做数据分析”用完就拆。长期挂着容易出现“A 重启了B 不知道”这类连锁问题。4.3 搭建完成后的验证清单搭完之后不能只看master_link_status是不是up那只能说明连接建立了。我通常按下面这份清单逐项验证检查项命令期望结果角色是否正确主库INFO replicationrole:masterconnected_slaves大于 0从库链路状态从库INFO replicationmaster_link_status:upmaster_sync_in_progress:0偏移量是否追平对比主从master_repl_offset差值在可接受范围内且不持续扩大数据是否同步主库SET test:key 1从库GET test:key从库能读到从库是否只读从库执行SET x 1返回READONLY You cant write against a read only replica全量后内存正常从库INFO memoryused_memory与主库接近无异常膨胀这份清单我在每次扩容从库时都会走一遍尤其是偏移量那一项。有些环境里从库显示up但 offset 差值一直在缓慢扩大说明从库处理能力跟不上主库写入速度这种“假健康”状态最危险因为监控看起来是绿的。5. 复制风暴与延迟雪崩压测阶段才暴露的五个问题5.1 全量同步风暴fork、磁盘 IO 与带宽的三重叠加最典型的场景是一次机房网络抖动五个从库同时掉线恢复后同时重连、同时请求全量。主库在几秒内连续 fork 五次每次都要生成完整的 RDB 文件磁盘写满、CPU 打满、网络出口带宽占满主库自己的读写请求全部超时业务侧直接雪崩。这个问题的根因在于全量同步对主库来说是重操作而且是不可控的。缓解手段有三层第一层是配置层面。repl-diskless-sync-delay默认 5 秒的作用就是攒批主库会等一小段时间把同时来请求的从库合并成一次 fork、一份 RDB、多路分发。这个机制能把五次 fork 压缩成一次效果非常明显。前提是repl-diskless-sync打开着这个配置在较新版本里默认开启老版本默认关闭动手前用CONFIG GET repl-diskless-sync确认一下。第二层是结构层面。把从库改成级联让主库只面对一两个中间层全量同步的冲击被分散到中间层去扛。中间层自己做全量虽然也会慢但它不承接业务写影响面小得多。第三层是容量层面。主库的内存不要超过物理内存的 50%给 fork 的写时复制留足空间。同时确保vm.overcommit_memory设为 1否则 fork 在某些内核配置下会直接失败日志里报Cant save in background: fork: Cannot allocate memory。提示vm.overcommit_memory1这个参数必须在操作系统层面设置写进/etc/sysctl.confRedis 自身的配置改不了它。这个坑我在至少三个环境里见过都是主库内存涨上来之后全量同步突然失败。5.2 输出缓冲区溢出从库被主库“踢下线”的现场主库给每个从库维护一个输出缓冲区用来暂存还没发出去的命令。如果从库消费太慢缓冲区会越堆越大超过client-output-buffer-limit replica设置的上限主库就会主动断开这个从库。默认值是256mb 64mb 60三个数字分别是硬上限、软上限、软上限持续的秒数。含义是缓冲区超过 256MB 立即断开或者超过 64MB 且持续 60 秒也断开。这个机制本身是保护主库的防止慢从库把主库内存吃光。但它带来的现象很容易误判从库日志里会看到连接被重置重连之后又触发全量全量期间又慢又被断开形成循环。排查这类问题的思路是先在主库上观察INFO clients里的client_recent_max_output_buffer看缓冲区峰值。如果确实接近上限再分析从库为什么慢。常见的慢因包括从库上有大 key 操作、从库执行了KEYS或大范围SCAN、从库磁盘 IO 打满开了 AOF 且appendfsync always、从库网络带宽不足。调整上限要谨慎。直接调大能缓解断开但只是把问题延后真正的解法是让从库消费能力跟上。如果确实是因为业务读把从库拖慢了那应该考虑再加一个从库分流而不是一味调大缓冲区。5.3 延迟怎么量从 offset 差值到链路耗时延迟的量化有两层一层是复制链路的延迟一层是业务感知的延迟两者不是一回事。复制链路延迟用 offset 差值算最直接。在主库上连续两次INFO replication间隔 10 秒记录master_repl_offset的增量和某个从库offset的增量两者的差就是这 10 秒内从库落后的量。除以写入速率大致能换算出落后多少秒。但业务感知的延迟还受另外两个因素影响一是从库上是否有慢查询阻塞了事件循环二是客户端到从库的网络路径。曾经遇到过一个案例主从 offset 差只有几十 KB理论上延迟不到 10ms但业务侧读到的数据延迟了将近 1 秒。最后定位到是从库上有个定时任务每分钟跑一次全量SCAN把事件循环卡住了。这种问题光看复制指标永远发现不了。所以监控要分层复制层看 offset 差值和master_last_io_seconds_ago执行层看SLOWLOG和latency相关指标业务层做端到端的写后读校验打点。三层都有数据定位问题才快。5.4 树状拓扑的延迟累积与读一致性代价级联拓扑解决了主库压力但引入了延迟累积。主库到中间层是一跳中间层到叶子是第二跳叶子的数据至少要经过两次传播。如果每一跳平均 20ms叶子节点就稳定落后 40ms 以上而且一旦中间层出现瞬时阻塞叶子的延迟会被放大。这个代价在读写分离场景里必须算进去。假设业务要求“写后 100ms 内读到新值”那叶子节点就不适合承接这类读请求应该由中间层或者主库来承担。实际做法通常是给叶子节点打标签让业务按一致性要求路由到不同层级的副本。还有一个容易被忽略的点级联结构下中间层从库如果开启了replica-read-only yes默认就是它既是主库的从库又是叶子节点的主库这个角色是自动的不需要额外配置。但它同时也承接了业务读一旦业务读把它的内存或者 CPU 打满叶子节点的复制就会受影响。所以中间层最好独立部署不要和业务读的从库混用。6. 几个反直觉的复制语义只读、过期键与内存策略6.1 replica-read-only 之后的写请求发生了什么从库默认replica-read-only yes此时任何写命令都会被拒绝返回READONLY You cant write against a read only replica。这个错误信息很明确但在实际项目里经常被应用框架吞掉表现成“偶发的写入失败”排查起来很费时。有几种情况会导致写请求打到从库上客户端连接池配置错误、读写分离中间件路由规则写反、主库故障切换后应用配置没更新、某些 SDK 在重连时随机选节点。这些都不是 Redis 的问题但表现都是从库报READONLY。从库能不能设成可写可以replica-read-only no。但我强烈不建议在业务环境这么做。一旦从库可写本地写入的数据和主库同步过来的数据会混在一起主库执行FLUSHALL或者做全量同步时这些本地数据会被直接清掉而且不会有任何提示。这种“看起来能写、实际上随时会丢”的状态是最坑的。真要往从库写数据正确做法是单独起一个实例不要和主从链路混在一起。如果是因为某些临时脚本需要写那也应该直连主库或者用独立实例。6.2 从库上的过期键逻辑过期与物理残留这个问题非常反直觉但理解了之后对排查“数据不一致”类问题帮助很大。Redis 的过期键删除有两种触发方式被动删除访问时发现过期就删和主动删除后台定时抽样删除。在从库上主动删除是被禁用的从库不会自己删过期键它只等主库发来的DEL命令。这个设计是为了保证主从数据一致如果从库自己删了、主库还没删那主从就不一致了。带来的现象是一个键在主库上已经过期消失了但从库的DBSIZE里可能还统计着它INFO keyspace的expires数量也可能对不上。不过从 Redis 3.2 开始从库在读取到逻辑上已过期的键时会返回空值当作不存在所以业务侧读到的是“没有这个键”而不是“读到了过期数据”。物理数据会一直留着直到主库的DEL传播过来。这个机制在内存排查时会造成困惑从库的used_memory比主库高出一截看起来像是内存泄漏。实际情况往往是大量已过期但未被删除的键还占着内存。等主库的过期删除跑完、DEL传播过来从库的内存就会回落。需要提醒的是如果主库的过期策略是volatile-lru之类的淘汰策略被淘汰的键会以DEL的形式传播给从库这部分是同步的。真正会造成从库内存偏高的主要是“过期了但还没被主库主动删除”的那批键。6.3 maxmemory 在从库上的行为差异从库不主动删除键这一点也直接影响了maxmemory的行为。从库不会像主库那样在内存达到上限时触发淘汰它只是老老实实地执行主库传过来的命令。这意味着如果从库配置了和主库一样的maxmemory很容易出现从库内存超限的情况。因为从库上还留着那批“逻辑过期但物理未删”的键实际占用会比主库高。超限之后如果淘汰策略是noeviction从库执行写命令会返回 OOM 错误复制流会中断日志里出现OOM command not allowed when used memory maxmemory。实操建议很简单从库的maxmemory要么不设要么设得比主库大 20% 到 30%。同时监控从库的内存增长曲线如果持续高于主库且差距在扩大那就要去查是不是有过期键堆积、或者主库的淘汰策略导致从库内存结构不同。还有一层是maxmemory-policy。从库上这个配置实际上不生效因为不主动淘汰但保持和主库一致是个好习惯方便后续提升为主库时行为正确。切主之后如果策略不一致新主可能会突然开始大量淘汰把业务打懵。7. 手工切主与回归没有哨兵时的兜底操作7.1 判断“该切了”的三个信号没有哨兵的场景下切主是人工决策判断依据必须清晰否则很容易出现“犹豫太久业务已经挂了”或者“切早了其实主库只是抖了一下”。第一个信号是主库完全失联。从库上master_link_status变成downmaster_last_io_seconds_ago持续增长超过repl-timeout同时从其他途径比如 ping、监控确认主库进程或机器确实挂了。如果只是从库单方面认为主库挂了但主库其实活着那可能是网络分区这时候切主会造成双主。第二个信号是主库无法恢复。确认主库重启需要很长时间比如磁盘故障需要重建业务等不起那就得切。这时候要接受“主库上最后一段未同步的写入会丢失”这个事实。第三个信号是写入完全阻塞。如果主库还活着但已经无法处理写请求比如被DEBUG SLEEP卡住、或者内存打满无法写入业务侧已经全面报错那切换是止损手段。我个人的判断习惯是先看监控确认主库是真的挂了还是只是网络抖动再看从库的 offset 是不是最新的差距越小切换丢的数据越少最后确认应用侧能不能快速改连接。三个条件都满足再动手避免误切。7.2 切主操作序列与旧主回归的脏数据处理假设主库是10.0.0.11选定的新主是从库10.0.0.12其他从库是10.0.0.13、10.0.0.14。操作序列如下。第一步在新主上检查数据完整性INFO replication # 确认 master_link_status 曾经是 upslave_repl_offset 尽量接近主库最后的值 DBSIZE # 记录键数量切完后对比第二步提升新主replicaof no one CONFIG REWRITE第三步让其他从库改挂新主# 在 10.0.0.13 和 10.0.0.14 上执行 replicaof 10.0.0.12 6379 CONFIG REWRITE第四步更新应用侧连接配置把写请求指向10.0.0.12。这一步越快越好中间的空窗期业务是报错的。第五步处理旧主回归。这是最容易出事的一步。旧主恢复后它本地还有一份数据这份数据里可能有“已经写成功但没同步给从库”的记录也可能有其他从库都没有的脏数据。绝对不能直接把它挂成新主的从库否则全量同步会把新主的数据覆盖成旧主的状态虽然全量同步的方向是主到从但如果旧主自己先被当成主库对外服务过那问题更复杂。正确做法是旧主恢复后先不要挂到主从链路里而是单独启动、用redis-check-rdb校验数据文件、导出需要保留的差异数据确认无误后再执行replicaof 10.0.0.12 6379让它做一次全量同步把本地数据彻底覆盖掉。如果旧主上有确实没同步出去的重要写入那得单独捞出来人工合并不能指望自动流程。注意旧主回归前一定要确认它的replicaof配置是清空状态否则它可能自己认为自己还是主库接受写请求造成双主写入。这个检查别省。7.3 用 WAIT 与 min-replicas-to-write 兜住写丢失窗口异步复制天然有丢数据的窗口Redis 提供了两个手段来收窄这个窗口虽然都不能做到强一致但比什么都不做强得多。WAIT numreplicas timeout是个同步命令执行后客户端会阻塞直到指定数量的从库确认收到了之前的写命令或者超时。用法SET order:1001 paid WAIT 1 100含义是等至少 1 个从库确认收到SET order:1001 paid这条命令最多等 100 毫秒。返回值是实际确认的从库数量如果返回 0说明超时了这条数据可能还在路上。这个命令的价值在于对于关键写操作你可以用它来确保数据至少落到了一个从库上。但它不是强一致超时之后命令其实已经执行了只是没等到确认而且它只保证“确认收到”不保证从库已经把数据落盘。min-replicas-to-write是另一个角度的保护它控制的是“主库在有多少个健康从库的情况下才允许写入”min-replicas-to-write 1 min-replicas-max-lag 10配置含义是至少要有 1 个从库且它的lag不超过 10 秒主库才接受写请求。否则主库直接返回错误拒绝写入。这个配置在防止脑裂导致的数据丢失上很有用。想象一下主库和所有从库之间的网络断了主库还活着但成了孤岛如果没有这个配置主库会继续接受写入等网络恢复后这些写入全都会被新主的数据覆盖掉。有了这个配置主库发现自己没有健康从库直接拒绝写入业务侧立刻感知到异常反而能更快触发人工介入。代价是可用性下降从库全挂的时候主库也写不了了。所以这个值要结合业务容忍度来设通常设 1 就够了设 2 或者更高会让可用性变得很脆弱。# 查看当前生效值 CONFIG GET min-replicas-to-write CONFIG GET min-replicas-max-lag最后分享一个小技巧做切主演练的时候不要只演练“主库直接挂掉”这一种场景。更值得演练的是“主库挂掉、从库提升、旧主恢复、旧主重新加入”这个完整闭环因为真正的事故往往出在最后一步。演练时把每一步的命令和时间点记下来形成一份可执行的 checklist真出事的时候照着敲就行比临场思考靠谱得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑