欢迎光临
我们一直在努力

Redis主从复制源码解析:深度剖析PSYNC实现与偏移量管理机制

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的关键点在于主节点如何判断同步策略:

  • 检查从节点是否支持PSYNC能力
  • 验证从节点发送的runid是否与主节点当前runid匹配
  • 检查从节点请求的offset是否在复制积压缓冲区范围内
  • 根据上述条件决定执行全量同步还是增量同步
  • 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);
    }

    偏移量管理的关键点:

  • 主节点为每个写操作分配唯一偏移量
  • 从节点记录并维护自己的复制偏移量
  • 从节点定期向主节点发送ACK报告偏移量
  • 主节点通过偏移量检测复制延迟和同步状态
  • 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

    注意事项

  • 复制积压缓冲区大小配置:repl-backlog-size参数应根据主节点的写入速度和从节点的平均断线时间来设置。公式:buffer_size = expected_repl_net_ops_per_sec * expected_reconnect_delay_sec。默认1mb适用于中等负载场景。
  • 复制延迟监控:通过INFO replication命令查看master_repl_offset和slave_repl_offset,计算复制延迟。延迟过高可能表明从节点处理能力不足或网络问题。
  • PSYNC限制:如果从节点断线时间过长,请求的offset超出复制积压缓冲区范围,仍会触发全量同步。高写入场景下,应适当增大缓冲区大小。
  • runid管理:主节点的runid在重启后会改变,导致从节点无法识别,触发全量同步。可以使用CONFIG SET repl-dynamic-yes启用动态ID,避免重启导致的重新全量同步。
  • #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主从复制架构,提高系统稳定性和数据一致性。

    赞(0)
    未经允许不得转载:171主机测评 » Redis主从复制源码解析:深度剖析PSYNC实现与偏移量管理机制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址