有道无术,术尚可求,有术无道,止于术。
本系列Redis 版本 7.2.5
源码地址:https://gitee.com/pearl-organization/study-redis-demo
文章目录
- 1. 概述
- 2 节点和节点
- 2.1 集群拓扑
- 2.2 集群总线协议
- 2.3 流言协议
- 2.4 心跳机制
- 2.5 节点握手
- 3. 客户端和节点
- 3.1 RESP 协议
- 3.2 重定向
- 3.2.1 MOVED
- 3.2.2 ASK
- 3.2.3 客户端重定向处理
1. 概述
官方文档
在 Redis
集群中,节点负责存储数据,并管理集群的状态,包括将键映射到正确的节点。集群节点还能够自动发现其他节点,检测非工作节点,并在需要时提升副本节点为主节点,以便在发生故障时继续运行。
节点和节点之间,节点和客户端之间,都需要高效安全的通信机制,确保整个集群能如期正常运行。
2 节点和节点
所有集群节点之间都是互相连接的,并使用以下协议进行通信:
- 集群总线协议:节点之间的连接协议
- 流言协议(
Gossip Protocol
):传播集群信息,以便发现新节点、发送Ping
数据包 - 发布/订阅(
Pub
/Sub
):
2.1 集群拓扑
Redis
集群是一个网状结构,其中每个节点通过 TCP
连接与其他每个节点连接,类似于网络中的网状拓扑结构:
在一个包含 N
个节点的集群中,每个节点有 N-1
个出站连接和 N-1
个入站连接。这些 TCP
连接始终保持活动状态,不是按需创建的。当一个节点在集群总线上期待收到对 Pong
回复时,在等待足够长时间标记节点为不可达之前,会尝试通过从头重新连接来刷新与节点的连接。
网状拓扑结构具有较高的可靠性,但其结构复杂,实现起来费用较高,不易管理和维护。但是 Redis
节点在正常情况下使用 Gossip
协议和配置更新机制,以避免在节点之间交换过多的消息,因此交换的消息数量不会呈指数增长。
2.2 集群总线协议
节点之间的通信完全通过集群总线和集群总线协议进行,集群总线协议是一种二进制协议,仅用于内部集群通信,目前没有相关说明,需要在源码中了解。
该协议需要使用集群总线端口进行连接。每个 Redis
集群节点除了监听 6379
运行端口外,还会开放一个额外的 TCP
端口,用于接收来自其他 Redis
集群节点的连接,该端口的计算方式是将运行端口号加上 10000
。
例如,如果一个 Redis
节点在 6379
端口上监听客户端连接,并且在 redis.conf
中未添加 cluster-port
参数,那么集群总线端口 16379
将会被打开。
可以通过配置文件中的 cluster-port
参数指定集群总线端口:
cluster-port 20000
在安装集群时,需要注意节点默认使用 6379、16379 两个端口
2.3 流言协议
Gossip Protocol
(流言协议)是一种高效的分布式信息交换协议,通过模拟流行病传播的方式,实现了节点间信息的快速传播和同步。其去中心化、可扩展性和容错性强的特点,广泛应用于多个分布式系统。
Redis
集群中的节点,会周期性地随机选择一些节点,通过 Ping
将当前节点的信息传递过去。收到信息的节点,也会使用同样的方式传播自己的节点信息。这个过程会持续进行,直到信息被传播到集群中的每一个节点,每个节点都会保存所有其他节点的信息。
2.4 心跳机制
Redis Cluster
会通过心跳检测迅速感知到节点故障,并且在节点故障时自动进行恢复,以确保数据在集群中的可用性。
Redis
集群节点不断地交换 Ping
和 Pong
数据包,用于检测其他节点的存活状态。这两种数据包总称为心跳数据包,它们具有相同的结构,并且都携带重要的配置信息。
集群节点发送心跳的几种触发方式:
- 每秒钟向几个随机节点发送
Ping
- 尝试重新建立与其他节点的
TCP
连接,以确保节点不会因为当前的TCP
连接问题而被认为是不可达的 - 向在
NODE_TIMEOUT
时间内没有发送过Ping
的节点进行发送
心跳数据包 包含了一些通用的内容信息:
- 节点
ID
: 节点创建时分配的全局唯一标识 - 当前时期(
currentEpoch
)和配置时期(configEpoch
): 发送节点的当前时期和配置时期字段,用于解决配置冲突和故障转移 - 节点标识: 标识节点是从节点、主节点或其他节点。
- 哈希槽位图: 发送节点服务的哈希槽位图,或者如果节点是副本,则为其主节点服务的槽位图。
- 发送者
TCP
数据端口:Redis
用于接受客户端命令的基本端口(6379
)。 - 集群总线端口:
Redis
节点间通信使用的端口。 - 发送者视角下的集群状态: 表示发送节点对集群状态的视角,可以是“
down
”或“ok
”。
新加如节点时,心跳数据包 还包含一些 Gossip
信息:
- 新节点
ID
。 - 新节点的
IP
地址和端口。 - 新节点标识。
2.5 节点握手
集群节点之间,始终通过集群总线端口保持连接,新节点加入 Redis Cluster
时,需要与集群中的其他节点进行握手,以获取集群的拓扑信息和状态。节点之间会交换握手消息,确认自身角色(主节点、从节点或未分配节点)和负责的槽分配情况。
节点握手的整个流程如下:
- 任意主节点(例如
A
)上执行CLUSTER MEET
命令,新节点的IP
地址(例如X
)和端口号作为参数。 A
和X
进行握手操作,以确认彼此的存在和状态。- 其他节点通过
Gossip
发现X
节点,并完成握手 - 随着时间的推移,集群中的所有节点都会通过
Gossip
协议知道新节点的存在,并将其纳入集群的元数据中。
在 Redis Cluster
网状拓扑中加入节点,集群能够自动发现其他节点,最终会自动形成一个完整的链路。这种机制使集群更加健壮,并确保了集群的灵活性和可扩展性。
3. 客户端和节点
3.1 RESP 协议
官方文档
Redis
客户端和服务端之间,通过 RESP
(Redis Serialization Protocol
)协议进行通信,它是一个简单的二进制安全协议。Redis 1.2
引入了 RESP
协议的第一个版本。客户端通过创建到服务器端口的 TCP
连接(默认端口为 6379
)连接到 Redis
服务端。
RESP
具有以下优点:
- 易于实现:协议的设计简洁明了,便于开发者实现。
- 快速解析:协议格式高效,可以迅速被解析,减少通信延迟。
- 人类可读:尽管主要用于机器通信,但协议格式也便于人类阅读和理解。
RESP
能够序列化不同的数据类型,包括整数、字符串和数组,并且还具有一个专门用于错误的类型。客户端向 Redis
服务器发送请求时,请求以字符串数组的形式发送,数组的内容包括要执行的命令及其参数。服务器的回复类型取决于具体的命令。
RESP
是二进制安全的,并使用前缀长度来传输大量数据,因此它不需要处理从一个进程传输到另一个进程的大量数据。这种设计使得数据传输更加高效和安全。
RESP仅用于客户端与服务器之间的通信,集群使用不同的二进制协议在节点之间交换消息
3.2 重定向
由于集群节点不能代理请求,因此客户端可能会使用重定向错误( -MOVED
和 -ASK
)被重定向到其他节点。
理论上,客户端可以自由地向集群中的所有节点发送请求,并在需要时进行重定向,因此客户端不需要持有集群的状态。但是,缓存键和节点之间映射的客户端可以显著提高性能。
3.2.1 MOVED
集群节点会自动分配哈希槽,节点内部也会维护其他所有节点和哈希槽的映射关系,例如,以下三个主节点:
Redis
客户端可以随意的向集群中的任意一个节点发送查询命令,例如,在 192.168.56.101:6379
节点上执行插入、查询操作,当前 key
的哈希槽编号为 1180
:
[root@localhost ~]# redis-cli -a cluster123456 -p 6379
127.0.0.1:6379> set aa bb
127.0.0.1:6379> cluster keyslot aa
(integer) 1180
127.0.0.1:6379> get aa
bb
如果在 192.168.56.103:6379
节点上执行查询操作,由于该节点的哈希槽为 5461 - 10922
,节点检查内部映射表时,发现哈希槽编号为 1180
的 key
不属于该节点管理,会向客户端回复一个 MOVED
错误:
127.0.0.1:6379> get aa
(error) MOVED 1180 192.168.56.101:6379
MOVED
错误中,包含了 key
的哈希槽编号,以及能够处理该查询的集群节点,客户端需要将查询重新发送到指定的节点。
一般客户端,会自动进行重定向,而且不会单独去请求某一个节点,而是维护了所有节点,并在内部维护了一个哈希槽到节点的映射,对于开发者来说,MOVED
重定向是无感知的。
注意:这里需要使用 redis-cli 工具进行测试,其他工具可能会自动重定向
此外,还可以使用 redis-cli -c
设置自动重定向:
[root@localhost ~]# redis-cli -a cluster123456 -p 6379 -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> get aa
-> Redirected to slot [1180] located at 192.168.56.101:6379
"bb"
3.2.2 ASK
Redis
集群进行伸缩(扩容 / 缩容)时,会进行哈希槽的迁移,当访问目标节点时,数据可能已经迁移到新的节点中,这时会产生 ASK
重定向。
在哈希槽的迁移过程中,槽中对应的多个 Key
是分批次进行移动的,而不是一次性的整体迁移,因此迁移槽中的 Key
一部分在老的服务节点,一部分在新的服务节点。当访问的 Key
正在发生迁移时,ASK
仅指示将下一个查询重定下到指定节点。
和 MOVED
的区别:
MOVED
:适用于哈希槽永久由另一个节点服务,接下来的查询应该尝试指定的节点。ASK
:适用于哈希槽正在迁移,指示仅将下一个查询发送到指定节点。
3.2.3 客户端重定向处理
为了保持高效处理能力,Redis Cluster
客户端会在本地维护当前哈希槽映射表,但是这个映射表需要保持是最新的,当客户端连接到错误的节点导致重定向时,客户端可更新本地的哈希槽映射表。
客户端通常需要在以下两种情况下进行更新:
- 在启动时初始化映射表
- 收到
MOVED
重定向
推荐重新获取完整的映射表,而不是更新变动的某一条数据,这样更简单高效。客户端可以通过发出CLUSTER SLOTS
命令来获取一个包含哈希槽范围及其对应节点的数组。
示例:
127.0.0.1:7000> cluster slots
1) 1) (integer) 5461 # 哈希槽范围的开始2) (integer) 10922 # 哈希槽范围的结束3) 1) "127.0.0.1" # 主节点地址端口2) (integer) 70014) 1) "127.0.0.1" # 从节点地址端口2) (integer) 7004
2) 1) (integer) 02) (integer) 54603) 1) "127.0.0.1"2) (integer) 70004) 1) "127.0.0.1"2) (integer) 7003
3) 1) (integer) 109232) (integer) 163833) 1) "127.0.0.1"2) (integer) 70024) 1) "127.0.0.1"2) (integer) 7005