尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Redis主从复制“三次握手”源码拆解:状态机与同步机制
用INFO replication观察过一次主从同步的人应该都有过类似的经历从库刚配好replicaof日志里刷出一堆“MASTER - REPLICA sync started”主库那边同步进度也跳了好几个状态最后两边都指向online数据也追平了。过程看起来很简单但真去翻Redis源码你会发现主从之间从建立连接到数据完全同步中间夹杂着身份确认、能力协商、同步请求、全量搬数据、进度上报等好几个环节远比想象中复杂。很多人一听到“三次握手”第一反应是TCP三次握手其实Redis主从复制里的“三次握手”完全是另一码事。它指的是从库与主库在应用层的三轮交互先确认链路和质量再做同步协商、决定全量还是增量最后通过ACK确认把从库正式转正。把这三个阶段搞清楚主从复制的源码就入门了大半。这篇文章是Redis主从复制源码阅读系列的第2篇目标很明确把主从之间这三次握手从replication.c和server状态机出发一层层拆开给你看。1. 先理解主从复制的整体图景1.1 主从复制不是“复制”那么简单很多人对主从复制的理解停留在“从库把主库的数据拿过来一份”。这个说法方向没错但它远远低估了这套机制的严谨程度。Redis的主从复制在实现上是一套完整的状态机从执行REPLICAOF命令开始到从库进入online状态中间要经历连接、握手、协商、传输、加载、确认等阶段任何一个环节出问题都会影响最终结果。我在阅读源码前习惯先用状态机的视角把流程过一遍。Redis把整个复制过程拆成了多个状态核心状态定义在replication.c里常见的有REPL_STATE_NONE初始无复制、REPL_STATE_CONNECT等待连接主库、REPL_STATE_CONNECTING连接已建立、REPL_STATE_RECEIVE_PING_REPLY等待PONG、REPL_STATE_SEND_PSYNC发送同步请求、REPL_STATE_RECEIVE_PSYNC_REPLY等待同步回复、REPL_STATE_TRANSFER接收RDB、REPL_STATE_CONNECTED已在线。你看到这一长串状态就知道主从复制绝不是一个简单的“拷贝”动作而是一套严谨的、可追踪的通信协议。从源码阅读的角度这套状态机是最好的“地图”。你不需要一次性看懂所有函数只需要跟着状态跳转走从库每次状态变化背后一定有一个对应的函数在驱动主库每收到一条指令也一定有一个对应的命令处理器在响应。把这套地图刻在脑子里看代码时就不会迷路。1.2 “三次握手”这个提法是怎么来的严格来说Redis官方文档里并没有“主从三次握手”这个术语。这个说法更多是源码阅读者在分析过程中的一种归纳目的是把这套冗长的交互流程简化成三个便于理解和记忆的阶段。正因为这是“约定俗称”的分析模型不同的人可能划分方式不同你需要先接受我这里的划分。我习惯把三次握手定义为三个阶段。第一次握手从库与主库建立TCP连接后先发送PING探活随后通过REPLCONF上报自己的监听端口和能力capa主库完成对该客户端的初步标记第二次握手从库发送PSYNC命令携带runid和偏移量主库根据自身复制积压缓冲区情况决定回复FULLRESYNC还是CONTINUE双方确认同步方式第三次握手全量数据RDB传输完成后从库加载完毕并向主库发送REPLCONF ACK主库确认从库的复制进度并标记为online随后进入持续的增量传播阶段。这样划分的好处是第一次解决“连接是否可用、你是谁”的问题第二次解决“怎么同步、从哪开始”的问题第三次解决“数据交接是否完成、后续怎么持续”的问题。和TCP三次握手一样每一轮都有明确目的每一轮失败都能在状态里找到对应的位置。1.3 源码地图把要反复打开的文件提前摊开主从复制的核心代码几乎都集中在src/replication.c少量状态定义散落在server.h和server.c里。我的建议是读源码前先把下面这张“函数地图”过一遍理解每个函数的角色再跟着流程走会轻松很多。函数名所属节点职责说明replicaofCommand从库/主库REPLICAOF命令入口触发复制关系建立replicationSetMaster从库记录主库地址设置连接状态replicationCron从库周期性检查复制状态负责发起连接和重连syncWithMaster从库主从连接建立后的核心回调驱动三次握手readSyncBulkPayload从库读取并加载主库发来的RDB数据syncCommand主库处理从库发来的PSYNC命令决定全量/增量replconfCommand主库处理REPLCONF命令识别从库端口、能力与ACKreplicationFeedSlaves主库把写命令传播给所有在线从库你需要记住一个总原则从库侧的动作集中在“如何发起连接、如何发起请求、如何处理回复”主库侧的动作集中在“如何接受请求、如何判断同步方式、如何发送数据”。接下来每一节我都会一边贴代码逻辑一边解释它在这张地图上的位置。2. 第一次握手从库连上来先把“我是谁”说清楚2.1 连接从何开始REPLICAOF之后的内部状态变化先看从库侧。当你在从库执行REPLICAOF masterip masterport时入口函数是replicaofCommand。这个命令的工作分两种情况如果参数是“NO ONE”就清空复制设置如果带了主库地址就调用replicationSetMaster。replicationSetMaster的核心逻辑很简短但信息量很大它会记录主库的host和port把server.repl_state置为REPL_STATE_CONNECT同时把master_runid清空、master_initial_offset设为-1。这几个动作的含义是从库还没连上主库也不知道主库的身份标识和同步起点一切要从零开始。之后进入复制的主循环。从库的replicationCron定时任务由server.hz驱动默认每秒10次会检查server.repl_state如果发现处于REPL_STATE_CONNECT就会调用connectWithMaster尝试建立TCP连接。这里的连接是非阻塞的connectWithMaster注册一个可写事件事件回调就是syncWithMaster。换句话说当TCP连接真正建立的那一刻Redis的io线程会触发syncWithMaster第一次握手正式开场。这里有个值得新手注意的细节从库并不是自己写了个死循环去connect而是把连接过程交给事件循环连接成功后再由回调函数接管后续动作。理解这一点对读源码非常重要Redis很多网络交互都是这种事件驱动模型函数之间不是“顺序调用”而是“通过事件串联”。2.2 先PING后报身份SYNC握手的第一步干了三件事syncWithMaster是第一次握手的关键函数。站在源码角度它内部会根据server.repl_state的不同取值完成阶段性的任务。连接刚建立时状态是REPL_STATE_CONNECTING此时从库会向主库发送一条PING命令并等待PONG回复。这一步和TCP握手里的SYN很相似本质是验证链路是通的、主库的协议解析是正常的、版本兼容性没问题。收到PONG后状态进入下一步从库紧接着发送两条REPLCONF命令。第一条是“REPLCONF listening-port 从库端口”作用是把从库的监听端口告知主库。主库收到后会把端口记录到对应client结构体的repl_listening_port字段之后你在主库执行INFO replication时看到的“slave0:ipxxx,portxxx”就是靠这个字段展示的。第二条是“REPLCONF capa eof capa psync2”这是能力协商告诉主库“我支持RDB的EOF格式我支持psync2协议”。能力协商是一个很容易被忽略但对兼容性至关重要的机制不同版本的Redis对同步协议的支持程度不同主库需要根据从库上报的能力选择合适的数据传输方式。我对第一次握手有一个形象类比PING是敲门听没听见REPLCONF listening-port是递名片让主库知道来访者是谁REPLCONF capa是亮出自己带了哪些证件方便后面办业务。这三个动作全部完成从库的状态才推进到REPL_STATE_SEND_PSYNC准备进入第二次握手。2.3 主库侧的反应第一次握手时主库做了什么从主库的视角看第一次握手接收到的PING、REPLCONF都只是普通命令但主库对待它们的方式早有设计。当从库建立TCP连接后主库在accept时会给这个client打上CLIENT_SLAVE以外的普通标记直到从库发送REPLCONF后主库才在replconfCommand中提取出端口、能力等元数据。这里需要特别提示一点第一次握手的过程中主库并不会立刻把这个连接当作正式的复制客户端。真正让主库“认定”这是一个从库的是第二次握手时收到的PSYNC命令。所以在源码里你会在syncCommand函数中找到明确的处理逻辑一旦收到PSYNC主库才会启动复制相关的数据结构比如创建复制积压缓冲区repl_backlog、把客户端标记为从库、加入server.slaves链表。我最初读这块源码时一直以为“从库连接成功 已经是复制客户端了”后来发现不是。这个认知纠正很重要主库对从库的“身份认定”是分步完成的第一步只登记信息第二步才生效复制关系。这也解释了为什么主库日志里出现“Replica asks for synchronization”的时机总是比连接建立的时机晚——因为那是PSYNC到达的时刻。3. 第二次握手PSYNC协商真正决定的是全量还是增量3.1 别把PSYNC理解成“同步请求”它是断点续传协商很多资料把PSYNC解释成“同步命令”这没错但不够准确。从源码角度来看PSYNC更接近于一种“断点续传协商”从库请求的不是“给我一份全量数据”而是“我已知某个数据流位置请你告诉我能否从这个位置继续”。Redis把从库已知的复制进度抽象成两个核心参数主库的runid和复制偏移量offset。runid是一个40位十六进制随机字符串在Redis每次启动时生成用于标识主库的“身份代际”。如果主库重启runid就变了。offset则是复制数据流的字节偏移量从库消费到哪就到哪。从库发起PSYNC时会带上自己记录的master_runid和master_repl_offset。如果这是从库第一次同步它对主库的runid和offset一无所知就会带上特殊的“? -1”。源码里PSYNC命令的经典形态是“PSYNC ”。从库在syncWithMaster中构建这条命令时会检查server.master_runid是否为空为空就发送“PSYNC ? -1”否则发送“PSYNC ”。为什么首次要用“?-1”因为-1表示“我不知道同步位置”?表示“我不知道主库身份”。主库收到这种请求只能认为从库没有任何基础必须做全量同步。3.2 主库的判决FULLRESYNC还是CONTINUE主库收到PSYNC后进入syncCommand函数。这个函数的职责是“判决同步方式”。判决的逻辑可以拆成三步。第一步主库检查从库发送的runid是否等于当前主库的server.replid即当前身份第二步主库检查从库发送的offset是否落在自己的复制积压缓冲区repl_backlog范围内第三步主库检查复制积压缓冲区是否存在。只有三个条件同时满足主库才回复“CONTINUE ”表示可以继续增量复制。任何一个条件不满足主库都会选择全量重同步回复“FULLRESYNC ”。这条回复信息量很大从库收到后会把主库的replid写进server.master_runid把回复的offset记成server.master_initial_offset。此后从库的复制位置就从master_initial_offset开始追。你看到这里会发现第二次握手的本质是主库做一个数学判断——你能不能从我记录的进度继续能就增量不能就全量。我有一段时间对“CONTINUE”有一个误解以为支持增量同步的前提是从库之前连过、且数据没断过。其实核心是runid backlog。就算网络断过只要主库没重启runid没变且从库的offset还在backlog里就能部分重同步。反过来如果主库重启导致runid变了即使网络一直连着也必须全量。Redis重启丢runid这件事是生产环境里“周期性全量同步”最常见的元凶之一。3.3 全量重同步背后的准备BGSAVE与传输方式选择当主库决定全量同步后还有一堆细节要处理。syncCommand会先把从库加入server.slaves链表设置它的复制状态为SLAVE_STATE_WAIT_BGSAVE_START等待开启后台保存然后尝试触发BGSAVE。这里有一个优化点如果主库本身已经有一份正在生成的RDB且当前没有其他从库在等这份RDB主库可以让该从库复用同一份RDB而不是再fork一次。BGSAVE的落盘方式也分两种RDB目标为磁盘disk和RDB目标为从库socketsocket。disk方式先把RDB写到本地磁盘再读出来发给从库socket方式则直接把RDB流式地写给从库。源码中这个选择由repl-diskless-sync配置控制二者有不同适用场景disk方式实现简单但对磁盘IO有压力socket方式能减少一次磁盘读写但对网络稳定性要求高且从库必须实时接收。从库侧此时进入REPL_STATE_RECEIVE_PSYNC_REPLY阶段等待主库的FULLRESYNC或CONTINUE。一旦收到FULLRESYNC从库解析出主库的replid和初始offset状态推进到REPL_STATE_TRANSFER开始进入真正的全量数据传输阶段。到这里第二次握手结束但距离从库“转正”还差最后一步。4. 第三次握手RDB搬完后的身份转正与增量接力4.1 RDB传输期间从库在干嘛第二次握手确定了全量同步后主库后台生成RDB生成完毕就把RDB数据推送给从库。从库在REPL_STATE_TRANSFER阶段readSyncBulkPayload函数会负责接收数据。这个函数的名字很形象读取“同步的批量数据”也就是RDB文件。接收过程中有几个关键细节值得注意。首先从库并不会一边收一边写磁盘而是先把数据写入临时文件通常是temp- .rdb等整个RDB接收完整后再一次性加载到内存。其次RDB数据流会有一个EOF标记老版本用“EOF作为文件结尾”的方式新版本通过capa能力协商后会用文件长度前缀的方式判断数据是否完整。这就是第一次握手时从库上报“capa eof”的用武之地。等RDB文件完整接收后从库会做两件事先清空自己现有的数据再把RDB加载进内存。源码中你会看到类似“FLUSHALL”逻辑的执行紧接着调用rdbLoad加载新数据。这一过程对业务是有阻塞风险的尤其是从库上数据量大的场景加载期间从库无法对外服务。生产环境里安排全量同步一定要评估好从库的容量和业务压力。4.2 REPLCONF ACK第三次握手的核心动作RDB加载完成后从库状态进入REPL_STATE_CONNECTED。这个状态代表着复制关系正式建立但主库侧还没100%确认从库已经“接住”了数据。真正让主库确认的是从库随后发起的REPLCONF ACK请求。REPLCONF ACK的发送频率在Redis中是每秒一次或根据配置的repl-ping-replica-period调整。从库会周期性发送“REPLCONF ACK 已复制的偏移量”这个偏移量就是从库当前追到的主库复制流位置。主库在replconfCommand中收到ACK后会更新该从库的repl_ack_off字段同时记录最近一次收到ACK的时间。这个字段的作用很多主库INFO replication里展示的lag就是据此计算主从故障检测也依赖它甚至在主库提升从库时也要参考ACK进度来决定可以放行多少数据。我把这理解成第三次握手的核心从库用ACK数据包告诉主库“我的数据已经加载完了我从这个偏移量开始等你喂命令”。主库只有在收到ACK后才会真正把从库标记为完全online状态开始正常推送增量命令。对增量复制而言ACK不只是一次性动作它是一种持续的心跳和进度上报类似TCP连接建立后的保活机制。4.3 online之后增量数据是怎么喂给从库的第三次握手完成主从之间的数据流转才进入常态增量传播。增量传播的底层机制是命令传播管道。主库每处理一条写命令在真正执行的同时会调用propagate把命令写入AOF缓冲和复制缓冲最终由replicationFeedSlaves函数把命令追加到每个在线从库的输出缓冲区。源码里replicationFeedSlaves做的事情有两件一是把命令写入server.repl_backlog复制积压缓冲区保留最近一段时间的命令供断线重连时部分重同步二是遍历server.slaves链表把命令写入每个非WAIT状态的从库client发送缓冲区。主库靠事件循环把缓冲区数据刷到网络从库再接收到、追加到自己的复制流里。从库同时也在继续向上汇报自己的offset。主库每次收到ACK时更新进度如果发现从库的offset已经追平lag就变小如果长时间收不到ACKlag会增长主库会认为从库可能断连。整个循环就是主库写命令并分发从库消费并回报进度二者通过offset对齐。第三握手里那个“REPLCONF ACK”机制在这一阶段持续发挥着作用。4.4 断线重连、重启场景下三次握手有什么不同如果说前三次握手描述的是“从零开始建立复制”的理想路径那么生产环境里更常见、更需要理解的是“复制中断后重连”的路径。重连时有个重要差异第一次握手仍然是PING REPLCONF全套但从库发送PSYNC时不再是“? -1”而是会带上自己断线前记录的runid和offset。这条信息的价值在于如果主库身份没变runid匹配、且从库的断线位置还在主库backlog里主库就回复CONTINUE把断线期间的命令补发给从库。这时第三次握手会省去RDB传输的漫长过程从库只需要补齐一小段gap就能online效率非常高。这也是Redis主从复制最优雅的地方设计初期就把“断点续传”作为一等公民。但要注意部分重同步是有条件的。如果主库在从库断开期间也重启过runid变化部分重同步必然失败回退为全量重同步如果断线时间太长backlog被覆盖也会全量。你在源码里找createReplicationBacklog和修改backlog大小的配置repl-backlog-size就能理解为什么建议生产环境把backlog调大——它直接决定了断线窗口可以多长。5. 源码阅读实录如何自己追一遍三次握手5.1 跟着状态机走一遍源码追踪路径读主从复制源码最怕“一头扎进函数细节出不来”。我自己摸索下来比较高效的方式是“状态驱动追踪法”先明确当前状态再找驱动状态变化的函数然后顺藤摸瓜。第一次握手可以从replicaofCommand开始顺着replicationSetMaster、replicationCron、connectWithMaster一路走到syncWithMaster第二次握手在syncWithMaster内部看它如何发送PSYNC然后跳到主库的syncCommand第三次握手跟着readSyncBulkPayload和replconfCommand走。如果你用gdb调试建议在这几个地方打断点syncWithMaster看第一次握手时的状态流转、syncCommand看主库的判断逻辑在返回FULLRESYNC还是CONTINUE处最直观、replconfCommand看ACK处理。配合Redis的日志级别你几乎能看到从库每一行日志背后对应的源码位置。比如从库日志“Master replied to PING, replication can continue...”对应的是第一次握手PONG到达日志“Full resync requested by master”对应第二次握手的FULLRESYNC回复日志“MASTER - REPLICA sync: Finished with success”对应第三次握手完成。5.2 用日志和INFO命令复现三次握手光看代码还不够实际操作一次能帮你把代码和现象对应起来。建议准备一个最小复现环境本机起两个Redis实例一个主一个从从库执行REPLICAOF后立刻观察两边日志。主库侧出现的关键日志顺序是“Replica : asks for synchronization”后紧跟“Full resync requested by replica : ”代表第二次握手全量判决完成随后“Starting BGSAVE for SYNC”代表RDB开始生成。从库侧则是“MASTER - REPLICA sync started” → “Master replied to PING” → “Full resync requested” → “MASTER - REPLICA sync: receiving N bytes from master” → “MASTER - REPLICA sync: Flushing old data” → “Loading DB in memory” → “MASTER - REPLICA sync: Finished with success”。把日志按时间排一排三次握手的轮廓就非常清晰了。INFO replication也值得同时观察。从库侧master_link_status从down变为up、master_sync_in_progress从1变0是第三次握手完成的重要信号主库侧connected_slaves从0变1且slave列表里的state字段从wait_bgsave变为online说明主库已经认了这个从库。这套现象和源码对照着看你会对状态机有非常具体的体感。5.3 我在阅读和实验中踩过的三个坑最后分享几个我实际踩过的坑。第一个坑是把REPLCONF ACK当成只有同步完成后才发送的东西其实从库online后是周期性持续发送的甚至在加载RDB期间也会尝试发送旧位置的ACK。理解了这一点你就知道查看lag时如果lag一直很大可能不是网络问题而是主库事务繁忙导致处理ACK延迟。第二个坑是忽略了runid变化对部分重同步的致命影响。我给一个测试主库配置了maxmemory策略在订阅压力下频繁崩溃重启从库每次都会触发全量同步日志里全是full resync。当时排查很久才意识到重启后runid变了backlog也是空的部分重同步在理论上就不成立。这个案例也提醒我生产环境里想让从库稳定走增量复制主库的稳定性比网络带宽更重要。第三个坑是RDB传输期间调整了repl-timeout导致全量同步反复失败。repl-timeout控制的是主从之间各类交互的超时时间如果设置得太小比如1秒遇到RDB传输耗时较长的情况从库长时间没收到数据就会判断超时主动断开整个握手前功尽弃。源码里replicationCron的检活逻辑依赖这个超时值调参之前一定要先评估全量数据量和网络环境。回过头看Redis主从复制的三次握手并不存在一个名叫“handshake”的函数而是由状态机、命令处理和事件回调共同搭起来的一套协作流程。这也是阅读网络协议类源码最容易踩坑的地方功能不是某一个函数完成的而是分散在一组函数里完成。当你习惯了按照“状态驱动函数、函数推进状态”的方式去读代码Redis主从复制的全貌就会变得非常清晰。如果你也想自己动手追一遍建议就从这篇文章里的函数地图开始把日志和源码对照着啃一遍比我在这里写一万字都管用。
RELATED

相关推荐

多智能体协同实战:从单兵作战到智能体军团

多智能体协同实战:从单兵作战到智能体军团

1. 为什么“单打独斗”的智能体走不远1.1 单智能体的三个天花板我见过太多人把大模型接上一个工具,就觉得拥有了一个“全能助手”。前两周确实惊艳,什么问题都能聊几句,可一旦丢给它一个真正复杂一点的任务——比如“做一个季度竞品分析报告并…

📅 2026/10/22 3:21:56
YOLOv26结合AAttn区域注意力:复杂场景目标检测精度提升实践

YOLOv26结合AAttn区域注意力:复杂场景目标检测精度提升实践

做目标检测的朋友应该都有体会:模型小了漏检多,模型大了又跑不动,多数时间都在"精度"和"速度"之间找平衡。YOLO系列一路升版本,YOLOv26的骨干网络和颈部结构已经相当成熟,但在遮挡目标、密集小目标…

📅 2026/10/22 3:21:56
AI辅助论文写作:四类工具分阶段使用与避坑指南

AI辅助论文写作:四类工具分阶段使用与避坑指南

如果你现在正盯着空白的Word文档,光标在标题下跳了十分钟,一个字都憋不出来,那你确实需要AI帮一把。但先别急着让它一口气生成8000字——我见过太多人这么干,结果提纲漂亮得像杂志目录,正文却经不起一次追问&#xff0…

📅 2026/10/22 3:21:56
MORE NEWS

更多资讯

📰

基座模型重塑自动驾驶:架构拆解与工程落地实践

作为长期盯智能驾驶技术栈的人,这几年我有个特别明显的感受:行业每隔一段时间就会冒出一次“换赛道”级别的概念,而今年风向几乎统一指向了基座模型。一端是自动驾驶量产落地,另一端是通用于多种任务的AI能力底座,这两…

📰

Hadoop完全分布式集群从零搭建:配置解析与踩坑排查指南

做Hadoop实验,第一个绕不开的坎就是搭集群。不夸张地说,大多数人在大数据这条路上踩的第一个坑,就是以为按教程敲几行命令就能把集群跑起来,结果卡在NameNode起不来、DataNode连不上、Web页面打不开各种问题上。这篇博文就把我从零…

📰

流批一体不只有Flink,实时数据模型才是真正的分水岭

聊流批一体的时候,我发现大多数人第一反应都是Flink,引擎层确实被它统一了不少,但真正让项目从“能跑”到“能持续稳定交付”的,反而是下游那一层经常被忽略的东西——数据模型。我去年接手一个实时数仓项目,Flink SQL…

📰

基于Python与CNN卷积神经网络的会飞昆虫识别

1. 毕业设计选题:从需求到可行性每年到毕设季,总有同学在选题上纠结半天。有的题目太宽泛,做起来没有边界;有的题目太偏门,查资料都费劲。这次分享的这个案例——基于Python与CNN卷积神经网络的会飞的昆虫识别&#xf…

📰

开源数据同步中间件实战:CDC增量、断点续传与多源接入指南

简介:这是一款开源数据同步中间件DBSyncer的完整资源包,面向需要处理异构数据库间数据同步的开发者、运维工程师及架构师。它覆盖MySQL、Oracle、SqlServer、PostgreSQL、Elasticsearch、Kafka、File、SQL等多种同步场景,支持上传插件自定义同…

📰

跌倒检测AI系统实战:从六类行为数据集到边缘部署全流程

1. 为什么老人跌倒这件事,必须靠AI来盯1.1 跌倒不是小概率事件,而是监护场景里的头号风险做老人监护这块儿,绕不开一个数据:跌倒是老年人因伤致死的首位原因。这个结论不是我随口说的,是公共卫生领域反复引用过的统计结…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬