MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Redis旧版复制功能实现的底层逻辑

2023-07-017.1k 阅读

Redis 旧版复制功能概述

Redis 的复制功能允许一个 Redis 服务器(称为副本,Replica)复制另一个 Redis 服务器(称为主服务器,Master)的数据。在旧版 Redis 中,复制功能已经是其重要特性之一,用于实现数据冗余、读写分离以及故障恢复等功能。

复制的基本概念

在 Redis 复制模型中,主服务器负责处理写操作,并将数据变更以日志形式记录下来。副本服务器连接到主服务器,获取主服务器的日志,并在本地重放这些日志,从而保持与主服务器的数据一致性。这一过程确保了副本服务器的数据是主服务器数据的精确拷贝。

旧版复制功能的建立过程

  1. 副本连接主服务器 副本服务器通过 SLAVEOF 命令连接到主服务器。例如,在 Redis 客户端中执行 SLAVEOF <master_ip> <master_port> 命令,副本服务器就会尝试与指定的主服务器建立连接。

  2. 发送 SYNC 命令 连接建立后,副本会向主服务器发送 SYNC 命令。这个命令标志着复制过程的正式开始。

  3. 主服务器执行 BGSAVE 主服务器接收到 SYNC 命令后,会执行 BGSAVE 命令,在后台生成一个 RDB 文件。这个 RDB 文件包含了主服务器当前的所有数据快照。

  4. 主服务器发送 RDB 文件 主服务器生成 RDB 文件完成后,会将该文件发送给副本服务器。在发送 RDB 文件的同时,主服务器会继续处理客户端的写请求,并将这些写操作记录到一个缓冲区中。

  5. 副本服务器加载 RDB 文件 副本服务器接收到 RDB 文件后,会先清除本地当前的数据,然后加载这个 RDB 文件,从而将数据恢复到与主服务器生成 RDB 文件时一致的状态。

  6. 主服务器发送缓冲区中的写命令 副本服务器加载 RDB 文件完成后,主服务器会将之前记录在缓冲区中的写命令发送给副本服务器。副本服务器接收到这些命令后,会在本地重放这些命令,从而将数据更新到与主服务器完全一致的状态。

旧版复制功能的底层实现逻辑

数据结构

  1. 主服务器数据结构 在主服务器端,Redis 使用一些数据结构来管理复制相关的信息。例如,每个副本连接在主服务器中都有一个对应的 redisClient 结构,该结构用于存储与副本的连接信息、复制偏移量等。
// redisClient 结构简化示例
typedef struct redisClient {
    int fd; // 连接的文件描述符
    char *replstate; // 复制状态
    off_t reploff; // 复制偏移量
    // 其他字段省略
} redisClient;
  1. 副本服务器数据结构 在副本服务器端,同样使用 redisClient 结构来与主服务器进行交互。此外,副本服务器还需要记录主服务器的地址和端口等信息,以便在需要时重新连接。
// 副本服务器存储主服务器信息的结构示例
typedef struct {
    char *master_host;
    int master_port;
    // 其他相关信息
} replicationInfo;

命令处理

  1. SYNC 命令处理 在主服务器端,当接收到副本发送的 SYNC 命令时,会调用 syncCommand 函数。该函数首先会启动 BGSAVE 操作,然后将副本加入到等待 RDB 文件传输的队列中。
void syncCommand(redisClient *c) {
    // 启动 BGSAVE
    rdbSaveBackground(server.rdb_filename);
    // 将副本加入等待队列
    listAddNodeTail(server.repl_waiting_acks, c);
}
  1. RDB 文件传输 主服务器在 BGSAVE 完成后,会将 RDB 文件通过网络发送给副本服务器。在发送过程中,会根据网络情况进行适当的缓冲和发送控制。
void sendRDBToReplica(redisClient *c) {
    FILE *fp = fopen(server.rdb_filename, "r");
    if (!fp) return;
    size_t len;
    char buf[REDIS_IOBUF_LEN];
    while ((len = fread(buf, 1, REDIS_IOBUF_LEN, fp)) > 0) {
        if (write(c->fd, buf, len) != len) {
            // 处理写错误
            break;
        }
    }
    fclose(fp);
}
  1. 缓冲区写命令传输 主服务器在发送 RDB 文件的同时,会将写操作记录到一个缓冲区中。当副本服务器加载完 RDB 文件后,主服务器会将缓冲区中的写命令发送给副本服务器。
void sendBufferToReplica(redisClient *c) {
    listIter li;
    listNode *ln;
    listRewind(server.aof_buf, &li);
    while ((ln = listNext(&li))) {
        char *cmd = listNodeValue(ln);
        if (write(c->fd, cmd, strlen(cmd)) != strlen(cmd)) {
            // 处理写错误
            break;
        }
    }
}

心跳机制

  1. 主服务器心跳 主服务器会定期向副本服务器发送心跳信息,以确保副本服务器仍然处于连接状态并且正常工作。心跳信息通常是一个简单的 PING 命令。
void sendHeartbeatToReplica(redisClient *c) {
    char pingCmd[] = "PING\r\n";
    if (write(c->fd, pingCmd, sizeof(pingCmd) - 1) != sizeof(pingCmd) - 1) {
        // 处理写错误
    }
}
  1. 副本服务器心跳响应 副本服务器接收到主服务器的 PING 命令后,会回复一个 PONG 命令作为响应。这一过程确保了主服务器能够及时了解副本服务器的状态。
void handlePingFromMaster(redisClient *c) {
    char pongCmd[] = "PONG\r\n";
    if (write(c->fd, pongCmd, sizeof(pongCmd) - 1) != sizeof(pongCmd) - 1) {
        // 处理写错误
    }
}

旧版复制功能的故障处理

网络中断处理

  1. 副本服务器重连 当副本服务器检测到与主服务器的网络连接中断时,会尝试重新连接主服务器。副本服务器会根据配置的重连策略,在一定时间间隔后发起重连操作。
void reconnectToMaster(replicationInfo *info) {
    int sockfd;
    struct sockaddr_in servaddr;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        // 处理 socket 创建错误
        return;
    }
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(info->master_port);
    inet_pton(AF_INET, info->master_host, &servaddr.sin_addr);
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
        // 处理连接错误
        close(sockfd);
        return;
    }
    // 连接成功,进行后续复制操作
}
  1. 部分重同步(PSYNC)的缺失 在旧版 Redis 中,没有部分重同步(PSYNC)机制。当网络中断后重新连接,副本服务器会再次发送 SYNC 命令,主服务器会重新执行 BGSAVE 并发送完整的 RDB 文件,这在数据量较大时会带来较大的性能开销。

主服务器故障处理

  1. 副本提升 在旧版 Redis 中,当主服务器发生故障时,需要手动将一个副本服务器提升为新的主服务器。通常的做法是在副本服务器上执行 SLAVEOF NO ONE 命令,使其脱离原主服务器的复制关系,并成为独立的主服务器。
  2. 其他副本重新配置 其他副本服务器需要重新配置,将新提升的主服务器作为它们的主服务器。这可以通过在这些副本服务器上执行 SLAVEOF <new_master_ip> <new_master_port> 命令来实现。

总结旧版复制功能的优缺点

优点

  1. 简单易懂:旧版复制功能的实现逻辑相对简单,易于理解和维护。其基于 RDB 文件传输和命令重放的机制,对于初学者来说更容易掌握。
  2. 基本功能完备:能够满足基本的数据复制需求,实现数据冗余和读写分离,为 Redis 的高可用性提供了基础支持。

缺点

  1. 全量复制开销大:每次副本与主服务器重新连接或初次连接时,都需要进行全量复制,即主服务器生成并发送完整的 RDB 文件,这在数据量较大时会占用大量的网络带宽和系统资源。
  2. 故障恢复复杂:主服务器故障后,需要手动进行副本提升和其他副本的重新配置,这在实际生产环境中增加了运维的难度和复杂性。
  3. 缺乏部分重同步机制:没有部分重同步(PSYNC)机制,导致网络中断等情况下无法高效地恢复复制关系,影响系统的整体性能。

通过深入了解 Redis 旧版复制功能的底层逻辑,我们不仅可以更好地理解 Redis 的工作原理,还能为优化和改进 Redis 的部署与使用提供有力的理论支持。在实际应用中,需要根据具体的业务需求和场景,权衡旧版复制功能的优缺点,选择合适的 Redis 配置和使用方式。同时,随着 Redis 的不断发展,新版的复制功能如 PSYNC 等已经极大地改善了旧版的不足,在新的项目中应优先考虑使用新版的特性。