Redis 主从握手流程,你真的了解了吗?
Redis 是开源的 key-value 存储系统,可作为数据库、缓存、消息组件。
Redis 的作者是 Salvatore Sanfilippo(网名为 antirez),他在 2009 年开发完成并开源了 Redis。
Redis 由于性能极高、功能强大,迅速在业界流行,现已成为高并发系统中最常用的组件之一。
Redis 提供了多种类型的数据结构,如字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。
Redis 还是分布式系统,主从集群可以实现数据热备份,哨兵(Sentinel)机制可以保证主从集群高可用,Cluster 集群则提供了水平扩展的能力。
Redis 还提供了持久化、Lua 脚本、Module 模块、Stream 消息流、Tracking 机制等一系统强大功能,适用于各种业务场景。
Redis 是一个典型的“小而美”的程序。
Redis 实现简单,源码非常优雅简洁,阅读起来并不吃力,而且 Redis 功能齐全,涵盖了数据存储、分布式、消息流等众多特性,非常值得深入学习。
Redis 中的一个重要概念就是主从复制机制。
下面详细分析 Redis 主从复制机制中主从握手的过程。
Redis 主从复制机制中有两个角色:主节点与从节点。
主节点处理用户请求,并将数据复制给从节点。
主从复制机制主要有以下作用:
(1)数据冗余,将数据热备份到从节点,即使主节点由于磁盘损坏丢失数据,从节点依然保留数据副本。
(2)读/写分离,可以由主节点提供写服务,从节点提供读服务,提高 Redis 服务整体吞吐量。
(3)故障恢复,主节点故障下线后,可以手动将从节点切换为主节点,继续提供服务。
(4)高可用基础,主从复制机制是 Sentinel 和 Cluster 机制的基础,Sentinel 和 Cluster 都实现了故障转移,即主节点故障停止后,Redis 负责选择一个从节点切换为主节点,继续提供服务。
下面将主从复制流程分为三个阶段。
(1)握手阶段:主从连接成功后,从节点需要将自身信息(如 IP 地址、端口等)发送给主节点,以便主节点能认识自己。
(2)同步阶段:从节点连接主节点后,需要先同步数据,数据达到一致(或者只有最新的变更不一致)后才进入复制阶段。
Redis 支持两种同步机制:
全量同步:从节点发送命令 PSYNC ? -1,要求进行全量同步,主节点返回响应+FULLRESYNC,表明同意全量同步。随后,主节点生成 RDB 数据并发送给从节点。这种方式常用于新的从节点首次同步数据。
部分同步:从节点发送命令 PSYNC replid offset,要求进行部分同步,主节点响应+CONTINUE,表明同意部分同步。主节点只需要把复制积压区中 offset 偏移量之后的命令发送给从节点即可(主节点会将执行的写命令都写入复制积压区)。这种方式常用于主从连接断开重连时同步数据。如果 offset 不在复制积压区中,那么主节点也会返回+FULLRESYNC,要求进行全量同步。
(3)复制阶段:主节点在运行期间,将执行的写命令传播给从节点,从节点接收并执行这些命令,从而达到复制数据的效果。Redis 使用的是异步复制,主节点传播命令后,并不会等待从节点返回 ACK 确认。异步复制的优点是低延迟和高性能,缺点是可能在短期内主从节点数据不一致。
本文中指的命令,包含命令名及执行命令的参数。
PSYNC 命令涉及以下属性:
server.master_repl_offset:记录当前服务器已执行命令的偏移量。
server.replid:40 位十六进制的随机字符串,在主节点中是自身 ID,在从节点中记录的是主节点 ID。
server.replid2:用于主节点,存放上一个主节点 ID。
server.repl_backlog:复制积压区,主节点将最近执行的写命令写入复制积压区,用于实现部分同步。
下面介绍一下 Redis 主从握手流程。
主从复制的机制是由从节点发起流程,我们可以发送 REPLICAOF 命令到某个服务器,要求它成为指定服务器的从节点:
REPLICAOF <masterip> <masterport>或者在配置文件中添加配置 REPLICAOF <masterip> <masterport>,这样 Redis 服务器启动后将成为指定服务器的从节点。
提示:从 Redis 5 开始为 SLAVEOF 命令提供别名 REPLICAOF,这两个命令的作用一样。
下面以从节点的视角,分析主从握手的过程。
从节点握手阶段涉及以下属性。
server.repl_state:用于从节点,标志从节点当前复制状态。有如下值:
REPL_STATE_NONE:无主从复制关系。
REPL_STATE_CONNECT:待连接。
REPL_STATE_CONNECTING:正在连接。
…(部分握手状态并没有列出)
REPL_STATE_TRANSFER:从节点正在接收 RDB 数据。
REPL_STATE_CONNECTED:已连接,主从同步完成。
从节点使用 replicaofCommand 函数处理 REPLICAOF 命令。
该函数执行如下逻辑:
(1)如果处理的命令是 REPLICAOF NO ONE,则将当前服务器转换为主节点,取消原来的主从复制关系,退出函数。
(2)调用 replicationSetMaster 函数,与给定服务器建立主从复制关系。
另外,我们在配置文件中配置 REPLICAOF <masterip> <masterport>,Redis 加载该配置,也会将 server.repl_state 设置为 REPL_STATE_CONNECT 状态(config.c)。
从节点 server.repl_state 进入 REPL_STATE_CONNECT 状态后,主从复制流程已经开始。
serverCron 时间事件负责对 REPL_STATE_CONNECT 状态进行处理:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {...if (server.repl_state == REPL_STATE_CONNECT) {if (connectWithMaster() == C_OK) {serverLog(LL_NOTICE,"MASTER <-> REPLICA sync started");}}}调用 connectWithMaster 函数进行处理,该函数负责建立主从网络连接:
int connectWithMaster(void) {// [1]server.repl_transfer_s = server.tls_replication ? connCreateTLS() : connCreateSocket();// [2]if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {...return C_ERR;}
}【1】创建一个 Socket 套接字。connCreateTLS 函数创建 TLS 连接,connCreateSocket 函数创建 TCP 连接,它们都返回套接字文件描述符。该连接是主从节点网络通信的连接,本书称之为主从连接。
【2】connConnect 函数负责连接到主节点,并且在连接成功后调用 syncWithMaster 函数。
【3】从节点 server.repl_state 进入 REPL_STATE_CONNECTING 状态。
网络连接成功后,从节点调用 syncWithMaster 函数,进入握手阶段:
void syncWithMaster(connection *conn) {char tmpfile[256], *err = NULL;int dfd = -1, maxtries = 5;int psync_result;...// [1]if (server.repl_state == REPL_STATE_CONNECTING) {connSetReadHandler(conn, syncWithMaster);connSetWriteHandler(conn, NULL);server.repl_state = REPL_STATE_RECEIVE_PONG;err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);if (err) goto write_error;return;}...// [2]if (server.repl_state != REPL_STATE_RECEIVE_PSYNC) {goto error;}
}【1】根据 server.repl_state 状态,执行对应操作。
从节点发送给主节点的信息,主节点会记录在从节点客户端,并在 INFO 命令中输出这些信息。另外,Sentinel 模块需要从主节点 INFO 命令响应中获取这些从节点信息。
【2】执行到这里,主从握手阶段已经完成。server.repl_state 必须处于 REPL_STATE_ RECEIVE_PSYNC 状态,否则报错。
下面使用 Linux tcpdump 工具抓取主从连接报文,分析主从节点握手阶段的通信内容(主节点端口为 6000):
tcpdump tcp -i lo -nn port 6000 -T RESPtcpdump 支持 RESP 协议,最后一个选项-T RESP 要求 tcpdump 以 RESP 协议格式解析报文。
其中 6000 端口为主节点端口,60374 端口为从节点通信端口。从 tcpdump 的输出可以清晰地看到主从节点在握手阶段的通信内容。
提示:tcpdump 解析后的 RESP 内容并不会展示数据类型的标志符,如主节点对从节点 PING 命令的响应实际上是“-NOAUTH Authentication required.”,请读者阅读源码时注意。
以主节点视角分析握手阶段,主节点不断处理来自从节点的命令(包括 PING、AUTH、REPLCONF),感兴趣的读者可自行阅读代码。
Redis 主从握手流程到此就分析完毕了。
▼
本内容摘自《Redis 核心原理与实践》,想了解更多关于 Redis 的内容,欢迎阅读此书。
▊《Redis 核心原理与实践》
梁国斌 著
新版本:基于 Redis 6.0.9,分析了 Redis 新特性,如 Redis 6 的 ACL、Tracking 等机制。
重实践:本书在对应知识点的基础上提供了详细的应用示例,帮助读者循序渐进、由浅到深地学习和理解 Redis 新特性。
易掌握:本书总结了 Redis 各个核心功能的实现原理,并以适量图文,对 Redis 源码及其实现原理进行详细分析,向读者展示 Redis 核心功能的设计思想和实现流程。
可扩展:本书由 Redis 延展出了两方面内容:一是 Redis 中使用的 UNIX 机制,二是如何通过 Redis 实现一个分布式系统,主要是 Sentinel、Cluster 机制的实现原理。
本书深入地分析了 Redis 核心功能的内部机制与实现方式,大部分内容源自对 Redis 源码的分析,并从中总结出实现原理。通过阅读本书,读者可以快速、轻松地了解 Redis 的内部运行机制。
(京东满 100 减 50,快快扫码抢购吧!)
评论