Redis主从复制源码解析:深度剖析PSYNC实现与偏移量管理机制
1. Redis主从复制概述与PSYNC背景
Redis主从复制是其高可用架构的基础,通过将数据复制到多个从节点,实现了读写分离和故障恢复。传统的主从复制使用SYNC命令进行全量同步,在主从节点断线重连时,无论数据量多大都会执行全量数据同步,导致大量网络带宽和磁盘I/O浪费。Redis 2.8引入了PSYNC(Partial SYNC)命令,实现了基于复制的断线重连增量同步,大幅提升了复制效率。
PSYNC的核心思想是主节点维护一个复制积压缓冲区(repl_backlog_buffer),记录最近的写操作,当从节点断线重连时,如果请求的偏移量仍在缓冲区范围内,主节点可以只发送缺失的部分数据,而非全部数据。PSYNC通过版本号(runid)和偏移量(offset)两个关键参数来标识复制状态,确保数据一致性。
2. PSYNC实现机制详解
PSYNC的实现涉及主从节点的状态管理和命令处理流程。在Redis源码中,PSYNC的处理主要在replication.c文件中实现。当从节点连接主节点时,会发送PSYNC命令,主节点根据当前状态决定执行全量同步还是增量同步。
// 主节点处理PSYNC命令的伪代码
int processPSyncCommand(client *c) {
// 如果是首次连接,执行全量同步
if (c->replica_capa & REPLICA_CAPA_PSYNC == 0) {
return startFullSync(c);
}
// 获取从节点发送的runid和offset
char *runid = c->argv[1]->ptr;
long long offset = strtoll(c->argv[2]->ptr, NULL, 10);
// 检查runid是否匹配
if (strcmp(server.repl_id, runid) != 0) {
// runid不匹配,需要全量同步
return startFullSync(c);
}
// 检查offset是否在缓冲区范围内
if (offset < server.repl_backlog->offset) {
// offset太旧,需要全量同步
return startFullSync(c);
} else if (offset > server.repl_backlog->offset + server.repl_backlog->size) {
// offset超出范围,需要全量同步
return startFullSync(c);
} else {
// 执行增量同步
return startPartialSync(c, offset);
}
}
PSYNC的关键点在于主节点如何判断同步策略:
3. 复制积压缓冲区设计与管理
复制积压缓冲区(repl_backlog)是PSYNC实现的核心数据结构,它是一个固定大小的环形缓冲区,用于存储主节点最近的写操作命令。
// 复制积压缓冲区结构体定义
typedef struct {
char *buf; // 缓冲区内存
long long size; // 缓冲区大小
long long offset;// 当前偏移量
long long histlen; // 已写入历史长度
} replBacklog;
Redis源码中,复制积压缓冲区通过repl_backlog_size参数配置,默认为1mb。当主节点有写操作时,会先将命令写入缓冲区,然后再发送给从节点:
// 主节点写命令同时写入缓冲区和发送给从节点
void feedReplicationBacklog(void *p, size_t len) {
// 获取当前偏移量
long long current_offset = server.repl_backlog->offset + server.repl_backlog->histlen;
// 计算写入位置
long long insert_pos = server.repl_backlog->offset + server.repl_backlog->histlen;
// 环形缓冲区处理
if (insert_pos + len > server.repl_backlog->offset + server.repl_backlog->size) {
// 缓冲区溢出,移动offset
long long new_offset = insert_pos + len – server.repl_backlog->size;
server.repl_backlog->offset = new_offset;
server.repl_backlog->histlen = server.repl_backlog->size;
insert_pos = server.repl_backlog->size – 1;
}
// 写入数据
memcpy(server.repl_backlog->buf + (insert_pos % server.repl_backlog->size), p, len);
// 更新历史长度
if (server.repl_backlog->histlen + len > server.repl_backlog->size) {
// 缓冲区已满,不增加histlen
} else {
server.repl_backlog->histlen += len;
}
// 更新全局复制偏移量
server.master_repl_offset += len;
}
缓冲区溢出处理是关键点:当缓冲区写满时,新的数据会覆盖最旧的数据,同时更新offset,确保缓冲区只保存最新的数据。这种设计使得从节点在断线重连时,如果请求的offset超出缓冲区范围,就必须执行全量同步。
4. 偏移量管理与复制进度跟踪
偏移量(offset)是PSYNC机制的核心,用于标识复制进度。主节点为每个写入的命令分配一个唯一的偏移量,从节点记录自己已接收的最后偏移量,并在断线重连时告知主节点自己的偏移量。
// 主节点更新复制偏移量
void update replicationOffset(void) {
// 获取当前复制偏移量
server.master_repl_offset += len;
// 更新从节点偏移量
if (listLength(server.slaves) > 0) {
// 遍历所有从节点,更新偏移量
listIter li;
listNode *ln;
listRewind(server.slaves, &li);
while ((ln = listNext(&li))) {
client *slave = ln->value;
if (slave->repl_state == REPL_STATE_CONNECTED) {
// 更新从节点的已确认偏移量
slave->repl_ack_offset = server.master_repl_offset;
}
}
}
}
从节点则定期向主节点发送REPLCONF ACK命令,报告自己的偏移量,以便主节点检测复制延迟:
// 从节点发送ACK命令给主节点
void sendReplicationAck(void) {
// 构造REPLCONF ACK命令
sds cmd = sdscatprintf(sdsempty(), "REPLCONF ACK %lld\\r\\n",
server.master_repl_offset + readlen);
// 发送命令
if (server.master) {
writeToClient(server.master, cmd, sdslen(cmd));
}
sdsfree(cmd);
}
偏移量管理的关键点:
5. 实践示例与注意事项
最小可运行示例
以下是最简单的Redis主从复制配置示例:
# 主节点配置
# redis.conf
port 6379
bind 127.0.0.1
# 从节点配置
# redis-slave.conf
port 6380
bind 127.0.0.1
slaveof 127.0.0.1 6379
repl-backlog-size 1mb
启动主从节点并测试PSYNC:
# 启动主节点
redis-server redis.conf
# 启动从节点
redis-server redis-slave.conf
# 在主节点写入数据
redis-cli -p 6379 set key1 value1
# 查看复制状态
redis-cli -p 6380 info replication
# 模拟断线重连
redis-cli -p 6380 client kill type replica
redis-cli -p 6380 slaveof 127.0.0.1 6379
注意事项
#publish-mermaid-1787878867439-0{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1787878867439-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1787878867439-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1787878867439-0 .error-icon{fill:#552222;}#publish-mermaid-1787878867439-0 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1787878867439-0 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1787878867439-0 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1787878867439-0 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1787878867439-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1787878867439-0 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1787878867439-0 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1787878867439-0 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1787878867439-0 .marker.cross{stroke:#333333;}#publish-mermaid-1787878867439-0 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#publish-mermaid-1787878867439-0 p{margin:0;}#publish-mermaid-1787878867439-0 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#publish-mermaid-1787878867439-0 .cluster-label text{fill:#333;}#publish-mermaid-1787878867439-0 .cluster-label span{color:#333;}#publish-mermaid-1787878867439-0 .cluster-label span p{background-color:transparent;}#publish-mermaid-1787878867439-0 .label text,#publish-mermaid-1787878867439-0 span{fill:#333;color:#333;}#publish-mermaid-1787878867439-0 .node rect,#publish-mermaid-1787878867439-0 .node circle,#publish-mermaid-1787878867439-0 .node ellipse,#publish-mermaid-1787878867439-0 .node polygon,#publish-mermaid-1787878867439-0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1787878867439-0 .rough-node .label text,#publish-mermaid-1787878867439-0 .node .label text,#publish-mermaid-1787878867439-0 .image-shape .label,#publish-mermaid-1787878867439-0 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1787878867439-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1787878867439-0 .rough-node .label,#publish-mermaid-1787878867439-0 .node .label,#publish-mermaid-1787878867439-0 .image-shape .label,#publish-mermaid-1787878867439-0 .icon-shape .label{text-align:center;}#publish-mermaid-1787878867439-0 .node.clickable{cursor:pointer;}#publish-mermaid-1787878867439-0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1787878867439-0 .arrowheadPath{fill:#333333;}#publish-mermaid-1787878867439-0 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1787878867439-0 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1787878867439-0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1787878867439-0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1787878867439-0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1787878867439-0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1787878867439-0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1787878867439-0 .cluster text{fill:#333;}#publish-mermaid-1787878867439-0 .cluster span{color:#333;}#publish-mermaid-1787878867439-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1787878867439-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1787878867439-0 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1787878867439-0 .icon-shape,#publish-mermaid-1787878867439-0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1787878867439-0 .icon-shape p,#publish-mermaid-1787878867439-0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1787878867439-0 .icon-shape .label rect,#publish-mermaid-1787878867439-0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1787878867439-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1787878867439-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1787878867439-0 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].node rect,#publish-mermaid-1787878867439-0 [data-look=\”neo\”].cluster rect,#publish-mermaid-1787878867439-0 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1787878867439-0 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1787878867439-0 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}首次连接断线重连是否
主从复制初始化
从节点发送PSYNC命令
主节点处理PSYNC请求
执行全量同步
检查复制积压缓冲区
缓冲区是否包含缺失数据
发送增量同步数据
从节点接收并应用数据
更新偏移量
继续复制流程
通过理解PSYNC实现、复制积压缓冲区与偏移量管理的核心机制,开发者可以更好地优化Redis主从复制架构,提高系统稳定性和数据一致性。




