Redis多机模式(三):集群

本文详细介绍了Redis集群的概念、搭建步骤、功能实现(包括高性能、高可用性)、消息类型、关键命令和相关数据结构,适合理解Redis分布式架构的读者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录:

  1. 集群是什么?有什么用途?
  2. 集群的环境如何搭建?
  3. 集群的功能如何实现?
  4. 集群中的消息类型?
  5. 关于集群的命令?
  6. 集群相关的结构体?

1.集群是什么?有什么用途?

  • Redis集群是Redis提供的分布式数据库方案(adistributed implementation of Redis),将数据库分成 16384 个槽(slot)分派到集群中多个主机之上,并支持自动故障转移。
  • 集群模式的出现是为了解决单机Redis容量有限的问题(Sentinel哨兵模式虽然提供了高可用性,但仍然是单主节点提供写操作,海量数据场景下存在性能瓶颈)。

2.集群的环境如何搭建?

方法一: 自动创建cluster:

https://blog.youkuaiyun.com/u012062455/article/details/87280467

Step 1: 修改redis.conf

需要先修改 redis.conf 配置文件中的配置项:

cluster-enabled yes

Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。

Step 2 : 逐一启动各节点

单独启动各个节点(可使用脚本批量启动):

./redis-server redis.conf

Step 3 : redis-cli创建集群

使用 redis-cli 创建 cluster集群,命令如下:

./redis-cli --cluster create 127.0.0.1:9001 127.0.0.1:9002 127.0.0.1:9003 127.0.0.1:9004 127.0.0.1:9005 127.0.0.1:9006 --cluster-replicas 1

其中,--cluster-replicas 1 选项表示每个主节点配备一个从节点;
redis-cli 会按照给定的顺序设置主节点和从节点,例如当有6个节点时,前3个会被默认设置成主节点
(9001,9002,9003),后三个是从节点(9004,9005,9006)。
(使用 ./redis-cli --cluster help 可查看cluster支持的操作)

方法二: 手动创建cluster:

https://blog.youkuaiyun.com/u012062455/article/details/87258490

CLUSTER MEET: 将一个节点添加到集群中

127.0.0.1:9007> CLUSTER MEET 127.0.0.1:9001
OK

使9007这一节点加入到9001节点所在的集群之中;

CLUSTER ADDSLOTS: 手动指派槽

127.0.0.1:9007> CLUSTER ADDSLOTS 0 1 2 3 4 5
OK

将 0 1 2 3 4 5 号槽指派给9007这个节点。


3. 集群的功能如何实现?

集群的两个主要功能:
(1)高性能:多主节点(可扩展高达1000个节点)
(2)高可用性:故障转移(主从复制、故障检测、故障转移)

为实现高性能和高可用性,集群的具体实现包括:
3.1 集群节点维护;
3.2 槽指派(分片);
3.3 处理客户端请求命令(重定向);
3.4 主从复制;
3.5 故障检测;
3.6 故障转移。

3.1 集群中的节点:

集群的拓扑结构是网状结构,在一个共有N个节点的集群中,每个节点会与其余的(N-1)个节点都建
立TCP连接并且是长连接,因此,每个节点都会创建 N个 clusterNode 结构体,用于维护集群中所有节点的状态信息。
同时,每个节点还需另外维护N个 clusterLink 结构体用于表示与某节点的TCP连接信息;
维护一个 cluserState 结构体用于表示集群的状态信息。

(1) clusterNode: 表示集群中的某一个节点

struct clusterNode {
	mstime_t	ctime;						//节点创建时间
	char 		name[REDIS_CLUSTER_NAMELEN]; //节点ID(40位十六进制符)
	int 		flags; 						//节点标识(例如主/从、在线/下线)
	uint64_t 	configEpoch; 				//节点当前纪元(用于故障转移)
	
	char 		ip[REDIS_IP_STR_LEN]; 		//节点IP地址
	int 		port; 						//节点端口号
	
	clusterLink	*link; 						//节点连接信息(如是本节点则=NULL)
	
	unsigned char slots[16384/8]; 			//用16384个bit位表示16384个槽
	int 		  numbers; 					//本节点负责处理的槽数量
};

(2) clusterLink: 表示与某节点的TCP连接信息

typedef struct clusterLink {
	mstime_t		ctime;		//TCP连接的创建时间
	int				fd;			//套接字描述符(connfd)

	sds				sndbuf;		//发送缓冲区(待发送到其他节点的数据)
	sds				rcvbuf;		//接收缓冲区(从其他节点接收到的数据)

	struct clusterNode	*node;	//与此clusterLink关联的节点
} clusterLink;

(3) clusterState: 表示集群的状态信息

typedef struct clusterState {
	clusterNode	  *myself;			//指向自己
	uint64_t	  currentEpoch;		//集群当前配置纪元(用于故障转移)
	int		  	  state;			//集群状态(ok/fail)
	int		  	  size;				//集群中至少处理着一个槽的节点的数量
	dict		  *nodes;			//集群节点名单

	clusterNode   *slots[16384];	//记录16384个槽中每个槽被分配到哪个节点上
} clusterState;

3.2 槽指派:

Redis将整个数据库分成了 16384 个槽,并将它们分配到集群中的每个主节点上去,从而解决了单机Redis容量有限的问题。

关于槽信息的维护:

主节点会将自身所负责的槽保存在

struct clusterNode {
	unsigned char slots[16384/8];
};

slots[ ]数组之中,并会将本节点的槽指派信息发送给集群中的其他主节点;
其他主节点收到消息后,更新

typedef strut clusterState {
	struct clusterNode *slots[16384];
} clusterState;

*slots[ ]数组的值,这样集群中的每个节点就都知道了所有16384个槽的分布情况。

注:
struct clusterNode *nodes[16384]; 是一个超大的数组,这是一种空间换时间的做法,可以迅速找到任一槽位所在的节点。


3.3 处理客户端请求命令(重定向):

集群处理客户端请求命令的流程如下:

Step 1: 计算键所属槽位号:

slot_num = CRC16(key) & 16383;

Step 2: 判断槽是否由当前节点负责处理:

伪代码流程:

if(clusterState.slots[slot_num] == clusterState.myself) {
	//key所属槽位在本节点上,直接处理
	handleCommand();
}
else {
	ip = clusterState.slots[slot_num].ip;
	port = clusterState.slots[slot_num].ip;
	return {(error)MOVED <slot_num> <ip>:<port>};
}

如果客户端请求中的key所属的槽并不在本主节点上,则返回 MOVED 错误,并指示槽所在节点的IP地址和端口号,客户端收到 MOVED 错误信息后,即可根据IP和端口信息重定向到对应的节点。

注:

  1. 集群模式下的客户端(./redis-cli -c)会自动完成重定向,并隐藏 MOVED错误打印,而是直接打印出 Redirected to 重定向提示:
127.0.0.1:9001>set key value
-> Reditected to slot[12539] located at 127.0.0.1:9006
OK
  1. 集群模式下的客户端通常会与集群中的所有节点都建立网络连接,所以重定向无需新建连接,只是换一个套接字来发送命令即可。

3.4 主从复制:

(1) 主从信息维护:

struct clusterNode {
	struct clusterNode   *slaveof;	//当本节点是从节点,*slaveof指向主节点
	
	struct clusterNode	 **slaves;	//当本节点是主节点,**slaves指向正在复制本节点的从节点数组
	int					 numslaves;	//当本节点是主节点,numslaves表示正在复制本节点的从节点个数
};

(2) 手动配置主从关系:

//命令:
CLUSTER REPLICATE <node_id>

//举例:
127.0.0.1:9007> CLUSTER REPLICATE bc638929759c1ec5b4d4e748e03f5344fe36ac35
OK

注意在集群模式下不能使用 SLAVEOF 命令去配置主从关系,否则会报错。

3.5 故障检测:

Step 1. PING + PFAIL:

每个节点会按固定频率向集群中其他节点发送 PING 命令,若超时未收到PONG回复,则标记目标节点为疑似下线状态:

clusterNode.flags |= REDIS_NODE_PFAIL;

Step 2. fail_reports + FAIL:

集群各节点间会互相发送消息交换各个节点的状态信息,如果A节点通过消息得知B节点判定C节点进入了疑似下线状态,则A节点会将B节点的下线报告添加到本地 clusterNode结构的fail_reports链表中:

struct clusterNode {
	list *fail_reports;	//一个链表,记录所有其他节点对该节点的下线报告
};

当A节点发现集群中半数以上的节点都判定C节点进入了疑似下线状态,则A节点更新C节点的状态为 已下线(FAIL),并向集群广播C节点已下线的消息,所有收到FAIL消息的节点都会立即将C结点标记为已下线。

clusterNode.flags |= REDIS_NODE_FAIL;

3.6 故障转移:

当从节点正在复制的主节点已下线(状态变为FAIL),则从节点发起故障转移。
注:故障转移由已下线主节点的从节点发起

故障转移的步骤:

  1. 在已下线主节点的从节点中选举出一个新的主节点;
  2. 被选中的从节点执行SLAVEOF no one,变为主节点;
  3. 新主节点将旧主节点的槽转移到自己身上;
  4. 新主节点广播 PONG 消息到集群中的其他节点,通知更新主节点信息;
  5. 新主节点开始处理自己负责的槽有关的请求命令,转移完成。

选举新的主节点的步骤:

Step 1: 从节点发起投票:

当集群中的某个节点判定主节点已下线(超半数判断PFAIL),则更新此主节点状态为FAIL并向集群中的其他节点广播。当已下线主节点属下的从节点发现主节点已下线后,会向集群中广播 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST,要求收到此消息的主节点给自己投票;

Step 2: 其余主节点开始投票:

如果主节点具有投票权(正在负责处理槽)且尚未投票,则投给第一个发过来 FAILOVER_AUTH_REQUEST 消息的从节点;

Step 3: 投票结果:

如果从节点收到 (N/2 + 1) 张选票(即超过半数),则这个从节点升级为主节点;
如果没有任何从节点的选票超半数,则集群将纪元值(epoch) + 1,并重新开始一轮选举。

重新分片与ASK错误:

Redis集群可将由某个节点(源节点)负责处理的槽指派给另一个节点(目标节点),并且相关的槽所属的键值对也会从源节点移动到目标节点,这类操作称为 重新分片

在进行重新分片的源节点与目标节点间迁移某些槽的过程中,如果客户端访问的键所属的槽正在被迁移,则服务器会返回 ASK错误,客户端根据ASK错误中携带的IP地址和端口号去重定向访问新的目标节点。(ASK错误与MOVED错误的实现方式类似,区别在于ASK用于数据迁移过程中的重定向)


4. 集群中的消息类型:

Redis节点间通信使用的是Gossip协议。
Gossip协议是一种去中心化的分布式协议,用于实现节点或进程之间的信息交换,通常被用于大型的无中心化网络环境中,并且假设网络环境不太稳定,是分布式系统中被广泛使用的一种最终一致性协议。
关于Gossip协议的介绍:
https://blog.youkuaiyun.com/weixin_44676392/article/details/88053299

集群中节点间收发的消息分为5种:

(1) MEET 消息:

在调用 CLUSTER MEET <ip> <port> 命令后产生MEET消息,使节点加入到 < ip:port > 所在的集群中,并开始与其他节点进行网络通信;

(2) PING 消息:

节点间会频繁的发送PING消息,用于交换自己的状态及集群状态信息,并监测集群中其他节点的在线状态;

(3) PONG 消息:

PONG 是 PING 和 MEET 消息的返回消息,包含自己的状态和其他信息;
也可用于广播和更新;

(4) FAIL 消息:

某个节点判定另一个节点为已下线时,会向集群中的其他节点发送FAIL消息;

(5) PUBLISH 消息:

当节点收到一条PUBLISH命令后,就会向集群PUBLISH消息。


5. 关于集群的命令:

CLUSTER HELP

CLUSTER NODES						/* Return cluster configuration seen by node. */

CLUSTER INFO						/* Return information about the cluster. */

CLUSTER MEET <ip> <port>			/* Connect nodes into a working cluster. */

CLUSTER ADDSLOTS <slot> [slot ...] 	/* Assign slots to current node. */

CLUSTER SLOTS						/* Return information about slots range mappings. */

CLUSTER REPLICATE <node_id> 		/* Configure current node as replica to <node-id>. */

6. 集群相关的结构体:

clusterNode:

struct clusterNode {
	mstime_t	ctime;						//节点创建时间
	char 		name[REDIS_CLUSTER_NAMELEN]; //节点ID(40位十六进制符)
	int 		flags; 						//节点标识(例如主/从、在线/疑似下线/下线)
	uint64_t 	configEpoch; 				//节点当前纪元(用于故障转移)
	
	char 		ip[REDIS_IP_STR_LEN]; 		//节点IP地址
	int 		port; 						//节点端口号
	
	clusterLink	*link; 						//节点连接信息(如实本节点则=NULL)
	
	unsigned char slots[16384/8]; 			//用16384个bit位表示16384个槽
	int 		  numbers; 					//本节点负责处理的槽数量


	//主从信息维护:
	struct clusterNode   *slaveof;	//当本节点是从节点,*slaveof指向主节点
	
	struct clusterNode	 **slaves;	//当本节点是主节点,**slaves指向正在复制本节点的从节点数组
	int					 numslaves;	//当本节点是主节点,numslaves表示正在复制本节点的从节点个数


	//故障检测:
	list	*fail_reports;	//记录其他节点对该节点的下线报告
};

clusterLink:

typedef struct clusterLink {
	mstime_t		ctime;		//TCP连接的创建时间
	int				fd;			//套接字描述符(connfd)

	sds				sndbuf;		//发送缓冲区(待发送到其他节点的数据)
	sds				rcvbuf;		//接收缓冲区(从其他节点接收到的数据)

	struct clusterNode	*node;	//与此clusterLink关联的结节点
} clusterLink;

clusterState:

typedef sturct clusterState {
	clusterNode	  *myself;			//指向自己
	uint64_t	  currentEpoch;		//集群当前配置纪元(用于故障转移)
	int		  	  state;			//集群状态(ok/fail)
	int		  	  size;				//集群中至少处理着一个槽的节点的数量
	dict		  *nodes;			//集群节点名单

	clusterNode   *slots[16384];	//记录16384个槽中每个槽被分配到哪个节点上
} clusterState;

参考内容:

《Redis设计与实现》第三部分第17章 集群
Redis官方文档: https://redis.io/topics/cluster-spec

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值