Redis源码阅读(三)集群-连接初始化

Redis源码阅读(三)集群-连接建立对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能。3.0之后的Redis支持了集群模式。Redis官方提供的集群功能是无中心的,命令请求可以发送到任意一个Redis节点,如果该请求的key不是由该节点负责处理,则会返回给客户端MOVED错误,提示客户端需要转向到该key对应的处理节点上。支持集群模式的redi...

Redis源码阅读(三)集群-连接初始化

Redis源码阅读(三)集群-连接建立

  对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能。3.0之后的Redis支持了集群模式。

  Redis官方提供的集群功能是无中心的,命令请求可以发送到任意一个Redis节点,如果该请求的key不是由该节点负责处理,则会返回给客户端MOVED错误,提示客户端需要转向到该key对应的处理节点上。支持集群模式的redis客户端会自动进行转向,普通模式客户端则只返回MOVED错误。

  先看下常见的Redis集群结构:

  节点两两之间都有连接,只有主节点可以处理客户端的命令请求;从节点复制主节点数据,并在主节点下线后,升级为主节点。每个主节点可以挂多个从节点,在主节点下线后从节点需要竞争,只有一个从节点会被选举为主节点。

考虑以下几个关键点:

  1. 节点是如何互发现的,请求又是如何分配到各个节点的?
  2. 其中部分节点出现故障,其他节点是如何发现的又是怎样恢复的?
  3. 主节点下线后从节点是如何竞争的?
  4. 是否可以不中断Redis服务进行动态的扩容?

  接下来几篇会从这几个关键问题入手来分析Redis集群源码;首先先看集群的基本数据结构,以及节点之间是如何建立连接的

1.数据结构

  Redis集群是无中心的,每个节点会存储整个集群各个节点的信息。我们看下Redis源码中存储集群节点信息的数据结构

struct clusterNode { //clusterState->nodes结构  集群数据交互接收的地方在clusterProcessPacket mstime_t ctime; /* Node object creation time. */
char name[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */ int flags;/* REDIS_NODE_... */ //取值可以参考clusterGenNodeDescription uint64_t configEpoch; /* Last configEpoch observed for this node */ unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */ int numslots;/* Number of slots handled by this node */ int numslaves; /* Number of slave nodes, if this is a master */ struct clusterNode **slaves; /* pointers to slave nodes */ struct clusterNode *slaveof; /* pointer to the master node */ //注意ClusterNode.slaveof与clusterMsg.slaveof的关联 mstime_t ping_sent;/* Unix time we sent latest ping */ mstime_t pong_received; /* Unix time we received the pong */ mstime_t fail_time;/* Unix time when FAIL flag was set */ mstime_t voted_time; /* Last time we voted for a slave of this master */ mstime_t repl_offset_time; /* Unix time we received offset for this node */ long long repl_offset;/* Last known repl offset for this node. */ char ip[REDIS_IP_STR_LEN]; /* Latest known IP address of this node */ int port; /* Latest known port of this node */ A节点 B节点 clusterNode-B(link1) ---> link2(该link不属于任何clusterNode) (A发起meet到B) 步骤1 link4<----clusterNode-A(link3) (该link不属于任何clusterNode) (B收到meet后,再下一个clusterCron中向A发起连接) 步骤2 */ //clusterCron如果节点的link为NULL,则需要进行重连,在freeClusterLink中如果和集群中某个节点异常挂掉,则本节点通过读写事件而感知到,
//然后在freeClusterLink置为NULL
clusterLink *link; /* TCP/IP link with this node */ //还有个赋值的地方在clusterCron,当主动和对端建立连接的时候赋值
list *fail_reports;/* List of nodes signaling this as failing */ //链表中成员类型为clusterNodeFailReport};typedef struct clusterNode clusterNode;

  clusterNode结构体存储了一个节点的基本信息,包括节点的IP,port,连接信息等;Redis节点每次和其他节点建立连接都会创建一个clusterNode用来记录其他节点的信息, 这些clusterNode都会存储到clusterState结构中,每个节点自身只拥有一个clusterState,用来存储整个集群系统的状态和信息。

typedef struct clusterState { //数据源头在server.cluster//集群相关配置加载在clusterLoadConfig clusterNode *myself;  /* This node */ uint64_t currentEpoch;   int state;/* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */ int size; /* Num of master nodes with at least one slot */ //默认从1开始,而不是从0开始 dict *nodes; /* Hash table of name -> clusterNode structures */  ......
// 例如 slots[i] = clusterNode_A 表示槽 i 由节点 A 处理 clusterNode *slots[REDIS_CLUSTER_SLOTS];
zskiplist *slots_to_keys; /* The following fields are used to take the slave state on elections. */ ......} clusterState;

  clusterState结构中还有很多是故障迁移时需要用到的成员,与集群连接初始化关系不大,可以先不关注,后面再分析。nodes* 存储的就是本节点所知的集群所有节点的信息。

2 连接建立

  集群节点在初始化前都是孤立的Redis服务节点,还没有连成一个整体。其他节点的信息是如何被该节点获取的,整个集群是如何连接起来的呢?

  这里有两种途径:

  1)人为干预指定让节点和其他节点连接,也就是通过cluster meet命令来指定要连入的其他节点;

  2)集群自发传播,靠集群内部的gossip协议自发扩散其他节点的信息。想象下如果没有集群内部的自发传播,任意两个节点间的连接都需要人为输入命令来建立;节点数如果为n, 整个集群建立的总连接数量会达到n*(n-1);要想建立起整个集群,让每个节点都知道完整的集群信息,需要的cluster meet指令数量是O(n2),节点多起来的话初始化的成本会很高。所以说内部自发的传播是很有必要的。

  下面来看两种方式的源码实现:

Meet指令

CLUSTER MEET <ip> <port>

  该指令会指定另一个节点的ip和port,让接收到MEET命令的Redis节点去和该ip和端口建立连接;

struct redisCommand redisCommandTable[] = {  //sentinelcmds  redisCommandTable  配置文件加载见loadServerConfigFromString 所有配置文件加载见loadServerConfigFromStringsentinel {"get",getCommand,2,"r",0,NULL,1,1,1,0,0}, {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0}, {"setnx",setnxCommand,3,"wm",0,NULL,1,1,1,0,0},   ......   {"cluster",clusterCommand,-2,"ar",0,NULL,0,0,0,0,0},   ......}

  可以看出Redis服务处理cluster meet指令的函数是clusterCommand。

//CLUSTER 命令的实现void clusterCommand(redisCl
源文地址:https://www.guoxiongfei.cn/cntech/134.html