
主从复制这东西我估计是很多Redis使用者又爱又恨的一个话题。爱的是它确实能扛读压力、做高可用恨的是它一旦出问题什么主从延迟、数据不一致、全量同步风暴全冒出来了线上事故一个接一个。这篇我打算把Redis主从复制的底层逻辑彻底讲透包括全量同步、增量同步、断线续传、复制积压缓冲区这些核心概念再给出一套可以直接照抄的搭建和排查方案。这不是给新手普及概念而是一份能真正支撑你上线、排障、做容量规划的经验之谈。我接触Redis主从复制这七八年踩过的坑比很多人看过的文档都多。早期以为主从就是配置一条slaveof命令就完事了结果生产环境一上各种问题接踵而至主从延迟导致读到旧数据、网络抖动触发全量重同步把主节点带宽打满、从节点数据跟主节点对不上账……这些问题的根源几乎都藏在复制协议和同步机制的细节里。所以这篇不只要讲怎么搭更要讲清楚每一步背后发生了什么出了问题怎么定位。1. 为什么需要主从复制从单机困境说起1.1 单机Redis的三大痛点先别急着看命令搞清楚主从复制解决了什么问题你才能理解它的设计决策。单机Redis第一个痛点是读性能天花板。一个Redis实例的读写能力再强也有上限通常单实例QPS能做到十万级别听起来很猛但一旦业务量上来读流量轻松突破这个数单机CPU最先扛不住接着就是网络和内存的连锁反应。最要命的是Redis是单线程模型一个慢查询或一次大key操作就能拖垮整个实例。第二个痛点是可用性。Redis作为缓存层挂掉意味着所有请求直接穿透到数据库数据库瞬间被压垮的场景我在生产环境见过不止一次。更麻烦的是Redis本身没有故障自动恢复的能力进程挂了就是挂了得靠人去重启、去恢复数据。第三个痛点是数据安全问题。虽然Redis有RDB和AOF两种持久化机制但持久化文件放在本机磁盘损坏就意味着数据彻底丢失。而且持久化本身也有性能代价尤其是AOF重写或RDB生成期间主进程fork子进程带来的内存和CPU开销不容忽视。主从复制就是冲着这三个痛点去的。一个主节点负责写多个从节点分担读主节点挂了从节点可以顶上继续提供服务数据在主从之间实时复制等于多了一份异地备份。这就是为什么几乎所有Redis高可用方案——无论是哨兵Sentinel还是集群Cluster——底层都离不开主从复制。1.2 主从复制到底复制了什么这里我先打一个比方。很多人以为主从复制是直接拷贝内存数据实际上Redis的设计思路更像日志重放主节点把每一次写操作记录下来从节点拿到这些操作指令后在本地一模一样的执行一遍最终达到数据一致的状态。这意味着两件事。第一主从复制不是数据文件的物理拷贝而是操作逻辑的复制所以主从两边的数据结构、key的编码方式必须完全一致版本不能差太多。第二从节点的数据最终一致但存在时间差这个时间差就是主从延迟是后续所有一致性问题的根源。搞清楚这个基本逻辑再去看全量同步、增量同步这些机制你会觉得顺理成章。主节点没法预知从节点现在缺哪些数据最保险的方案就是把当前全量数据快照发过去然后从某个时间点开始持续发送新操作——这就是全量同步加增量同步的组合拳。2. 主从复制的核心机制从全量同步到增量同步2.1 复制偏移量与积压缓冲区两个绕不开的概念你查主从复制状态的时候一定会看到master_repl_offset和slave_repl_offset这两个字段很多教程一句话带过但实际上整个增量同步机制都是围绕它们转的。复制偏移量说白了就是一个字节计数器。主节点每处理一个写命令就把命令的字节长度加到自己的master_repl_offset上从节点每接收并应用一个命令也会把字节数加到自己的slave_repl_offset上。正常情况下两个值是一致的差多少就说明从节点落后了多少字节。与其配套的是复制积压缓冲区repl_backlog。这是一个由主节点维护的环形缓冲区默认大小1MB里面存的是最近发送给从节点的原始命令数据。为什么要这个缓冲区因为网络总有抖动从节点可能断开几秒甚至几分钟它重连之后主节点只需要找到它最后接收到的偏移量把缓冲区内后续的数据发给它就行不用做全量同步。这里有个关键点如果从节点的断连时间太长超过了积压缓冲区能覆盖的范围或者缓冲区因为写流量太大被新数据覆盖了那么主节点找不到从节点缺失的数据只能降级为全量同步。这就是我后面要讲的全量同步风暴的一个触发条件。repl_backlog的大小设置很有讲究。默认1MB说实话在写密集场景下几秒钟就覆盖一遍我建议至少在64MB以上计算公式很简单主节点每秒写入的数据量 * 从节点可能断连的最长时间。比如主节点每秒产生2MB写数据你希望容忍从节点断连5分钟那缓冲区至少要2MB * 300秒 600MB当然还要留出余量配1GB也不夸张。2.2 全量同步流程的完整拆解全量同步发生在从节点第一次连接主节点或者从节点的断连时间超过积压缓冲区覆盖范围的时候。整个过程大致分五个阶段阶段一从节点发起连接并发送同步命令。新版Redis使用PSYNC命令携带从节点自己的runid和偏移量。这两个参数的作用是让主节点判断能否做增量同步。如果从节点从来没有同步过偏移量就是-1。阶段二主节点生成RDB快照。主节点收到PSYNC后如果决定做全量同步会立即触发一次bgsave操作fork一个子进程生成当前内存数据的完整RDB文件。注意这里有个细节fork期间主节点依然在处理写请求这些新写入的数据不会出现在RDB快照里所以主节点会把这些写命令缓存到复制积压缓冲区里。阶段三传输RDB文件。RDB文件生成完毕后主节点通过网络发送给从节点。这个传输过程是阻塞主节点的吗我可以明确告诉你不是。RDB传输由主节点的socket发送缓冲区完成但在大数据集下传输期间主节点依然要处理数据网络带宽会成为瓶颈这点后面讲全量同步风暴时再说。阶段四从节点清空旧数据并加载RDB。从节点收到完整的RDB文件后会先清空自己内存里的所有旧数据然后加载RDB文件。清空这一步很关键也很危险——如果RDB文件损坏或传输不完整从节点就变成了一台空实例。阶段五主节点补发缓存期间的写命令。从节点加载完RDB后主节点会把阶段二开始缓存在积压缓冲区里的写命令发给从节点从节点依次执行最终达到与主节点一致的状态。全量同步的代价非常大。RDB生成耗费主节点CPU和内存fork开销RDB传输耗费网络带宽从节点清空和加载耗费磁盘和内存。所以生产环境要尽量避免全量同步这也是为什么积压缓冲区要配大一点的根本原因。2.3 增量同步与断线续传的实现细节增量同步其实不复杂就是在从节点追上了主节点的偏移量之后主节点实时把新的写命令推送给从节点。这个推送是异步的主节点不会等从节点确认所以性能开销很小但代价是主从之间天然存在一定的延迟窗口。断线续传是Redis 2.8引入的重要优化。在这之前从节点只要断开连接重连后只能做全量同步那场景简直灾难——主节点每秒写几千个key从节点每次抖动都要重新RDB主节点CPU直接被打满。引入了复制偏移量和积压缓冲区之后从节点重连时带上自己的偏移量主节点只要能在缓冲区里找到对应的位置就用增量同步补齐缺失的数据。但断线续传有一个前提就是积压缓冲区不能断档。我曾经遇到过一个案例主节点写流量特别大积压缓冲区配的16MB从节点因为网络闪断重连花了不到十秒结果缓冲区已经被新数据覆盖了主节点在缓冲区里找不到从节点上次断开的偏移量位置只能做全量同步。所以配置积压缓冲区大小的时候不要只看我配了多大要结合写流量算清楚它能覆盖多长时间的断连。还可以看看socket层面的参数。从节点连主节点时TCP连接的SO_SNDBUF和SO_RCVBUF大小会影响大数据量传输的效率。Linux内核动态调整buffer的情况下你可能还要检查tcp_wmem是否设置过小导致传输慢。这块细节在传输RDB到从节点、追增量时尤其明显。3. 搭建主从复制的完整实操3.1 从零开始的环境准备与参数选择开始动手之前先把需要准备的清单列出来。这里我推荐用Docker快速起环境生产环境则建议用物理机或虚拟机但参数逻辑一致。准备两台节点可以是同一台机器的两个实例但生产环境一定要分开部署主节点master端口6379从节点slave端口6380Redis版本建议至少6.x因为6.0以后复制流程和PSYNC实现已经非常成熟。如果你还在用3.x甚至2.8请立刻升级老版本的复制机制没有断线续传优化、没有WAIT命令、也没有repl-conf的很多精细控制线上出大问题的概率成倍增加。主节点的核心配置# redis-master.conf bind 0.0.0.0 port 6379 protected-mode no daemonize yes pidfile /var/run/redis-master.pid logfile /var/log/redis-master.log # 持久化配置主节点必须开启AOF且everysec appendonly yes appendfsync everysec # 复制相关 repl-backlog-size 256mb repl-backlog-ttl 3600 # 建议设置一个较快的超时 repl-timeout 60从节点的核心配置# redis-slave.conf bind 0.0.0.0 port 6380 protected-mode no daemonize yes pidfile /var/run/redis-slave.pid logfile /var/log/redis-slave.log appendonly yes appendfsync everysec # 主从配置 replicaof 192.168.1.10 6379 replica-read-only yes这里有几个点值得多说一句。主节点开AOF是必须的不仅是数据安全问题也关系到从节点异常重启后是否能恢复到最新状态。RDB和AOF都开也行Redis重启时加载顺序是优先AOF。从节点的replica-read-only yes意味着从节点只接受读请求任何写操作都会报错。这个默认配置在生产环境不要改否则一旦业务方误写了从节点主从数据就分歧了。还有一个容易忽略的点如果从节点自身也开启了appendonly yes那么从节点的本地AOF文件在重启后也能恢复部分数据对降低全量同步频率有帮助。但要注意从节点AOF和RDB的意义不是备份而是为了重启后能尽可能继承已有数据、减少同步压力。3.2 三种常见的搭建方式与适用场景方式一replicaof静态配置。这是最传统的方式在从节点的配置文件里写死replicaof master-ip master-port启动即自动建立主从关系。适合固定拓扑、长期不变的场景。缺点是修改关系需要重启不够灵活。方式二运行时REPLICAOF命令动态切换。不要重启服务直接在redis-cli里执行# 在从节点上执行 REPLICAOF 192.168.1.10 6379切换的时候有一个细节如果目标主节点数据量很大触发全量同步期间从节点会处于SYNC状态此时从节点对外不可读。所以做主从切换时要提前评估全量同步的时间窗口业务上要有对应的容错。方式三通过Sentinel或Cluster自动管理。哨兵模式和集群模式下主从关系的建立由协调组件自动完成。手动干预的场景比较少但理解了手动方式再看自动方式就很轻松——本质上它们用的都是同一套复制协议。我的建议是测试环境用手动配置生产环境用哨兵或集群托管但所有节点都要预先配好合理的复制参数别指望托管组件帮你优化底层参数。3.3 验证复制状态与性能指标搭建完成后在从节点执行redis-cli -p 6380 INFO replication关注这几个字段# Replication role:slave master_host:192.168.1.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:123456789 slave_priority:100 slave_read_only:1 connected_slaves:1 master_repl_offset:123456789 repl_backlog_active:1 repl_backlog_size:268435456 repl_backlog_first_byte_offset:119000000 repl_backlog_histlen:4456789master_link_status必须是up如果是down说明连接有问题可能是网络不通、认证失败或者配置错误。slave_repl_offset和master_repl_offset的差值就是主从延迟的字节数注意这是字节差距不是时间差距。想看时间延迟另一种办法是自己造一个基准key# 主节点 redis-cli -p 6379 SET sync_test $(date %s%N) # 从节点持续检查 while true; do val$(redis-cli -p 6380 GET sync_test) now$(date %s%N) echo delay_ns$((now - val)) sleep 0.1 done延迟应控制在毫秒级如果出现几百毫秒甚至秒级延迟优先检查网络带宽、从节点是否在做AOF重写bgrewriteaof或者存在大key阻塞。4. 日常运维中的排查经验与避坑指南4.1 错误排查速查表现象可能原因排查思路解决方案master_link_status:down网络不通、主节点挂了、密码不一致redis-cli -p 6379 PING检查防火墙和安全组恢复网络、启动主节点、统一requirepass和masterauth主从延迟持续增大带宽不足、从节点性能差、大key阻塞看INFO replication的字节差距观察从节点CPU增加带宽、拆分大key、升级从节点规格触发全量同步积压缓冲区太小、断连太久、主节点重启INFO replication看sync_full和sync_partial_ok计数增大repl-backlog-size避免从节点长时间离线从节点数据与主节点不一致业务误写从节点、复制中断未恢复比对key数量、抽样hash比对设置replica-read-only yes监控复制状态全量同步风暴主节点同时连接大量从节点、全部过期看connected_slaves和sync_full错峰重连、改用哨兵滚动切换、限制从节点重连并发4.2 主从延迟和数据一致性永远的课题主从延迟是复制机制天生的属性无法消除只能控制。WAIT命令提供了一种强制同步机制在主节点执行写命令后可以等待指定数量的从节点确认收到。比如WAIT 1 1000表示等待至少1个从节点确认超时上限1000毫秒返回确认的从节点数量。但WAIT真的能保证强一致吗不能。WAIT等待的是从节点已经收到并执行了命令但如果从节点在确认后立刻宕机数据还是可能丢。它最多只能算有限度的同步确认。更实际的方案是借助外部手段兜底。比如在业务中设置写后读延迟策略或者用版本号机制避免读到旧数据覆盖新数据。有一说一Redis是最终一致性模型如果业务要求强一致就不该用缓存兜底直接走数据库才是正解别为难Redis。4.3 全量同步风暴的产生与规避多个从节点同时重连主节点主节点被迫为每个从节点各生成一份RDB快照CPU瞬间飙满网络带宽被打爆甚至主节点对外服务都受影响。这就是全量同步风暴。规避手段要提前做第一合理设置积压缓冲区目标是大多数断连都能走增量同步不触发全量。这是成本最低、效果最明显的优化。第二错峰重连。如果运维脚本要在多个从节点上执行重启用随机延迟打散时间点别让它们扎堆重连。第三控制从节点总数。一台主节点挂十几个从节点本身设计就不合理要考虑使用从节点的从节点——即链式复制让中间层从节点继续向下级从节点分发数据降低主节点压力。第四监控sync_full的计数器变化。这个值每次增加都意味着一次全量同步发生如果短期内频繁增长说明复制拓扑或参数设置有问题尽早处理别等出大事才看。4.4 一把梭我总结的几个不试不知道的经验最后分享几个从实战里摸出来的教训。从节点maxmemory要配得比主节点小错大错特错。从节点内存必须大于或等于主节点否则一旦数据量略增从节点开始逐出key和主节点的数据就永远无法一致后续增量同步会持续出问题。这个坑我见过太多次了。关于主从切换很多人以为哨兵把新的主节点选出来就完事了实际上旧主节点恢复后会自动变成新主节点的从节点这个自动归队的过程同样可能是全量同步。所以每次主从切换后要审视新主节点的积压缓冲区配置确保能覆盖多数从节点的断连时间。还有一点可能被忽略Redis主从复制不具备幂等性。从节点执行每个写命令都会产生新的写入放大这就是为什么在写密集场景下从节点的CPU和磁盘负载不一定比主节点低不能理所当然地认为从节点只读所以很闲。给从节点预留足够的CPU和磁盘IO能力别配置完就不管了。主从复制是Redis高可用的地基但它的问题也恰恰藏在那套看似简单的流程里。把全量同步、增量同步、偏移量、积压缓冲区这些底层机制真正吃透你才能在设计架构、排查故障的时候做到心里有数。很多人问我有没有什么神器能一劳永逸解决问题我的答案一直很直接没有。唯一的捷径就是把原理弄懂把参数调对把监控建好。这篇分享里的每一条都是我真实踩过坑之后沉淀下来的希望能帮你少走一段弯路。