Redis旧版复制功能实现的底层逻辑
Redis 旧版复制功能概述
Redis 的复制功能允许一个 Redis 服务器(称为副本,Replica)复制另一个 Redis 服务器(称为主服务器,Master)的数据。在旧版 Redis 中,复制功能已经是其重要特性之一,用于实现数据冗余、读写分离以及故障恢复等功能。
复制的基本概念
在 Redis 复制模型中,主服务器负责处理写操作,并将数据变更以日志形式记录下来。副本服务器连接到主服务器,获取主服务器的日志,并在本地重放这些日志,从而保持与主服务器的数据一致性。这一过程确保了副本服务器的数据是主服务器数据的精确拷贝。
旧版复制功能的建立过程
-
副本连接主服务器 副本服务器通过
SLAVEOF
命令连接到主服务器。例如,在 Redis 客户端中执行SLAVEOF <master_ip> <master_port>
命令,副本服务器就会尝试与指定的主服务器建立连接。 -
发送 SYNC 命令 连接建立后,副本会向主服务器发送
SYNC
命令。这个命令标志着复制过程的正式开始。 -
主服务器执行 BGSAVE 主服务器接收到
SYNC
命令后,会执行BGSAVE
命令,在后台生成一个 RDB 文件。这个 RDB 文件包含了主服务器当前的所有数据快照。 -
主服务器发送 RDB 文件 主服务器生成 RDB 文件完成后,会将该文件发送给副本服务器。在发送 RDB 文件的同时,主服务器会继续处理客户端的写请求,并将这些写操作记录到一个缓冲区中。
-
副本服务器加载 RDB 文件 副本服务器接收到 RDB 文件后,会先清除本地当前的数据,然后加载这个 RDB 文件,从而将数据恢复到与主服务器生成 RDB 文件时一致的状态。
-
主服务器发送缓冲区中的写命令 副本服务器加载 RDB 文件完成后,主服务器会将之前记录在缓冲区中的写命令发送给副本服务器。副本服务器接收到这些命令后,会在本地重放这些命令,从而将数据更新到与主服务器完全一致的状态。
旧版复制功能的底层实现逻辑
数据结构
- 主服务器数据结构
在主服务器端,Redis 使用一些数据结构来管理复制相关的信息。例如,每个副本连接在主服务器中都有一个对应的
redisClient
结构,该结构用于存储与副本的连接信息、复制偏移量等。
// redisClient 结构简化示例
typedef struct redisClient {
int fd; // 连接的文件描述符
char *replstate; // 复制状态
off_t reploff; // 复制偏移量
// 其他字段省略
} redisClient;
- 副本服务器数据结构
在副本服务器端,同样使用
redisClient
结构来与主服务器进行交互。此外,副本服务器还需要记录主服务器的地址和端口等信息,以便在需要时重新连接。
// 副本服务器存储主服务器信息的结构示例
typedef struct {
char *master_host;
int master_port;
// 其他相关信息
} replicationInfo;
命令处理
- SYNC 命令处理
在主服务器端,当接收到副本发送的
SYNC
命令时,会调用syncCommand
函数。该函数首先会启动BGSAVE
操作,然后将副本加入到等待 RDB 文件传输的队列中。
void syncCommand(redisClient *c) {
// 启动 BGSAVE
rdbSaveBackground(server.rdb_filename);
// 将副本加入等待队列
listAddNodeTail(server.repl_waiting_acks, c);
}
- 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);
}
- 缓冲区写命令传输 主服务器在发送 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;
}
}
}
心跳机制
- 主服务器心跳
主服务器会定期向副本服务器发送心跳信息,以确保副本服务器仍然处于连接状态并且正常工作。心跳信息通常是一个简单的
PING
命令。
void sendHeartbeatToReplica(redisClient *c) {
char pingCmd[] = "PING\r\n";
if (write(c->fd, pingCmd, sizeof(pingCmd) - 1) != sizeof(pingCmd) - 1) {
// 处理写错误
}
}
- 副本服务器心跳响应
副本服务器接收到主服务器的
PING
命令后,会回复一个PONG
命令作为响应。这一过程确保了主服务器能够及时了解副本服务器的状态。
void handlePingFromMaster(redisClient *c) {
char pongCmd[] = "PONG\r\n";
if (write(c->fd, pongCmd, sizeof(pongCmd) - 1) != sizeof(pongCmd) - 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;
}
// 连接成功,进行后续复制操作
}
- 部分重同步(PSYNC)的缺失
在旧版 Redis 中,没有部分重同步(PSYNC)机制。当网络中断后重新连接,副本服务器会再次发送
SYNC
命令,主服务器会重新执行BGSAVE
并发送完整的 RDB 文件,这在数据量较大时会带来较大的性能开销。
主服务器故障处理
- 副本提升
在旧版 Redis 中,当主服务器发生故障时,需要手动将一个副本服务器提升为新的主服务器。通常的做法是在副本服务器上执行
SLAVEOF NO ONE
命令,使其脱离原主服务器的复制关系,并成为独立的主服务器。 - 其他副本重新配置
其他副本服务器需要重新配置,将新提升的主服务器作为它们的主服务器。这可以通过在这些副本服务器上执行
SLAVEOF <new_master_ip> <new_master_port>
命令来实现。
总结旧版复制功能的优缺点
优点
- 简单易懂:旧版复制功能的实现逻辑相对简单,易于理解和维护。其基于 RDB 文件传输和命令重放的机制,对于初学者来说更容易掌握。
- 基本功能完备:能够满足基本的数据复制需求,实现数据冗余和读写分离,为 Redis 的高可用性提供了基础支持。
缺点
- 全量复制开销大:每次副本与主服务器重新连接或初次连接时,都需要进行全量复制,即主服务器生成并发送完整的 RDB 文件,这在数据量较大时会占用大量的网络带宽和系统资源。
- 故障恢复复杂:主服务器故障后,需要手动进行副本提升和其他副本的重新配置,这在实际生产环境中增加了运维的难度和复杂性。
- 缺乏部分重同步机制:没有部分重同步(PSYNC)机制,导致网络中断等情况下无法高效地恢复复制关系,影响系统的整体性能。
通过深入了解 Redis 旧版复制功能的底层逻辑,我们不仅可以更好地理解 Redis 的工作原理,还能为优化和改进 Redis 的部署与使用提供有力的理论支持。在实际应用中,需要根据具体的业务需求和场景,权衡旧版复制功能的优缺点,选择合适的 Redis 配置和使用方式。同时,随着 Redis 的不断发展,新版的复制功能如 PSYNC 等已经极大地改善了旧版的不足,在新的项目中应优先考虑使用新版的特性。