有道无术,术尚可求,有术无道,止于术。
本系列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) 7001
4) 1) "127.0.0.1" # 从节点地址端口
2) (integer) 7004
2) 1) (integer) 0
2) (integer) 5460
3) 1) "127.0.0.1"
2) (integer) 7000
4) 1) "127.0.0.1"
2) (integer) 7003
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7002
4) 1) "127.0.0.1"
2) (integer) 7005