Redis集群

Redis支持集群模式,集群中可以存在多个master,每个master又可以拥有多个slave。数据根据关键字映射到不同的slot,每一个master负责一部分的slots,数据被存储在负责它的slot的master节点上。slave会同步它的master节点上的数据到本节点,当master节点挂掉时,slave可以上升为master节点继续服务,保障集群的完整性与可靠性。

Redis集群中的每一个节点都拥有其它所有节点的信息,任意节点都知道客户端请求的数据被存储在哪一个master节点上,因此客户端可以连接到任意节点执行操作。这不同于hdfs文件系统的namenode与datanode的概念,namenode一旦挂掉,整个系统就不可用了,namenode成为了系统的瓶颈。Redis中master不仅拥有多个slave,并且空闲的slave还可以在不同的master之间流动,加强集群的可靠性。

这篇文章主要介绍redis的集群结构及内部集群信息的管理,slave与master之间数据的同步将在其它文章中介绍。

1. Redis集群结构

Redis中存在多个master,每个master又可以有多个slave,现假设有集群中有9个节点,3个master,每个master节点又有2个slave,那么它的结构可以表示如图1-1:

图1-1 集群结构图

不同的master可以拥有不同数量的slave,且集群中任意一个节点都与其它所有节点建立了连接,每一个节点都在称为cluster port的端口监听其它节点的集群通信连接。

2. Redis集群数据结构

与集群相关的数据结构主要有clusterState与clusterNode两个(在cluster.h源文件中声明):每一个redis实例拥有唯一一个clusterState实例,即server.cluster;而clusterNode的实例与集群中的节点数目n对应,每一个节点上都拥有n个clusterNode实例表示它知道的n个节点的信息,存储在clusterState结构的nodes成员中。

clusterState中有2个与集群结构相关的成员,声明如下(省略了大部分成员,仅留下了与集群整体结构相关的成员):

typedef struct clusterState {
clusterNode *myself; /* This node */
  ...
dict *nodes; /* Hash table of name -> clusterNode structures */
  ...
clusterNode *slots[CLUSTER_SLOTS];
  ...
} clusterState;

nodes成员是一个字典,以节点nameid为关键字,指向clusterNode的指针为值,假设集群有4个节点,那么nodes存储内容大致可以表示如下:

图2-1 nodes结构

集群中的节点都有一个nameid,以nameid为索引即可在nodes字典中找到描述该节点信息的clusterNode实例。而slots是一个clusterNode结构的指针数组,CLUSTER_SLOTS是redis中定义的支持的slot最大值,所有的key计算得到的slot都小于该值。slots[slot]存储着负责该slot的master节点的clusterNode结构的指针。每一个节点上都拥有该slots数组,因此在任意节点上都可以查找到负责某个slot的主节点的信息。假设集群拥有3个master节点,那么slots结构可表示如下:

图2-2 clusterState中的指针数组slots

clusterNode结构描述了一个节点的基本信息,如ip,port, cluster port等,其声明如下:

typedef struct clusterNode {
mstime_t ctime; /* Node object creation time. */
char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
int flags; /* CLUSTER_NODE_... */
  ...
unsigned char slots[CLUSTER_SLOTS/]; /* 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. Note that it
may be NULL even if the node is a slave
if we don't have the master node in our
tables. */
  ...
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
int port; /* Latest known clients port of this node */
int cport; /* Latest known cluster port of this node. */
clusterLink *link; /* TCP/IP link with this node */
  ...
} clusterNode;

name即是nodes字典中用作关键字的节点nameid

slots与clusterState中的slots有所不同,这里以bit的索引作为slot值,以该bit的状态标识该clusterNode对应的节点是否负责该slot。

slaves与slaveof代表了节点之间的master-slave关系。如果这是一个master节点,那么它的slave节点的clusterNode指针存储在slaves数组中;如果这是一个slave节点,那么slaveof指向了它的master节点的clusterNode。

link,指向当前节点与该clusterNode代表的节点之间的连接的相关信息,节点之间通过该link定期发送ping/pong消息。

在每一个节点中都维护了它知道的所有节点的clusterNode结构,从集群角度上来讲所有节点地位都是平等的,避免了瓶颈节点的出现。而这些结构中的信息主要用于节点之间的通信(即ping/pong),类似于心跳信息,维护整个集群的状态。

3. Redis集群通信结构

Redis集群中的节点没有namenode与datanode的区别,每一个节点都维护了所有节点的信息。如前一小节介绍,clusterNode结构中的link指向了当前节点与clusterNode所代表的节点之间的连接,Redis中每一个节点都与它所知道的所有节点之间维护了一个连接,通过这些连接发ping/pong消息,同步集群信息。集群中任意两个节点之间都建立了两个tcp连接,例如有nodeA与nodeB,那么nodeA中代表nodeB的clusterNode中有一个link维护了A主动与B建立的连接,而nodeB中代表nodeA的clusterNode中也有一个link维护了B主动与A建立的连接,即构建了一个全双工的通信链路。假设集群中存在3个节点,那么它们之间的通信结构如下所示:

图3-1 3个节点的集群通信结构

注意每个节点也维护了自身的clusterNode结构,并且在clusterState中使用myself指向它。方便修改自身节点的状态。

4. link的建立与节点发现

每一个节点都会在cluster port端口监听tcp连接请求,参见clusterInit函数,并且每个节点都有一个定时任务clusterCron,其中会遍历nodes字典,检测其中的clusterNode的link是否建立,如果没有建立连接,那么会主动连接该clusterNode所代表的节点建立连接。如果nodes字典中没有某个节点clusterNode结构,那么便不会与它建立连接。

建立clusterNode的时机大致有如下几处:

  1. 从文件中加载节点信息建立 clusterNode结构,在函数clusterLoadConfig中。
  2. 客户端执行meet命令告知节点信息,建立相应的clusterNode结构,由函数clusterCommand调用clusterStartHandshake完成。
  3. 接收到meet类消息,建立与发送方对应的clusterNode结构,在函数clusterProcessPacket中。
  4. 接收到的ping/pong/meet消息中带有其它不知道的节点信息,建立相应的clusterNode结构,同样在clusterProcessPacket函数中,调用clusterStartHandshake完成。

新建立的clusterNode的nameid是随机的,并且此时的clusterNode中flag设置为CLUSTER_NODE_HANDSHAKE状态,表示尚未首次通信。当clusterCron中建立相应的link,并发送ping/meet消息,收到响应消息(Pong)时去除CLUSTER_NODE_HANDSHAKE状态,并将clusterNode的nameid修改为响应消息中附带的nameid,至此成功建立一个方向的连接,反方向的连接由对方主动发起建立。

5. Redis处理通信的函数结构

  1. clusterInit中监听端口cport,注册读事件,响应函数为clusterAcceptHandler。
  2. clusterCron中主动建立连接,并将连接结构保存到clusterNode中的link指针中。注册读事件,响应函数为clusterReadHandler,并主动发送ping/meet消息,若先前未注册写事件,则为该link注册写事件,响应函数为clusterWriteHandler。
  3. clusterAcceptHandler中接受连接后建立link结构(未保存),并注册读事件,响应函数为clusterReadHandler。
  4. clusterReadHandler中接收数据,当接收到一个完整的消息后,调用clusterProcessPacket函数处理。

Redis中定义了集群通信消息的结构,每一个消息至少包含一个消息头,而消息头中包含整个消息的长度,因此clusterReadHandler中可以判断是否接收到一个完整的数据包。

link指向的结构中包含了sndbuf与rcvbuf两个缓存,其定义如下:

/* clusterLink encapsulates everything needed to talk with a remote node. */
typedef struct clusterLink {
mstime_t ctime; /* Link creation time */
int fd; /* TCP socket file descriptor */
sds sndbuf; /* Packet send buffer */
sds rcvbuf; /* Packet reception buffer */
struct clusterNode *node; /* Node related to this link if any, or NULL */
} clusterLink;

clusterWriteHandler负责将sndbuf中的数据发送出去,clusterReadHandler负责将数据接收到rcvbuf中,需要发送的集群数据都先填充到sndbuf中,需要接收到数据都先缓存到rcvbuf中,rcvbuf中积累了一个完整数据包再由clusterProcessPacket函数处理。通过缓存将io与数据包处理逻辑分离,简化代码结构。

最新文章

  1. Linux NetHogs监控工具介绍
  2. Emmet (Zen Coding) 官方文档中HTML语法的总结
  3. Spring MVC实现防止表单重复提交(转)
  4. 【USACO】packrec
  5. 一次PostgreSql数据迁移,使用nodejs来完成
  6. Jenkins iOS – Git, xcodebuild, TestFlight
  7. php下删除一篇文章生成的多个静态页面
  8. websocket++简单使用例子
  9. hdoj 4006 The kth great number【优先队列】
  10. Task的运行过程分析
  11. Java读书推荐
  12. c++友元函数与友元类
  13. pythn print格式化输出---------"%s 和 % d" 都是什么意思?
  14. python学习第七篇——字典访问键与值
  15. input file 多张图片上传 获取地址 ——fileReader
  16. ASP.NET Core 注入和获取 AppSettings 配置
  17. 把MP3保存到数据库中
  18. 使用My97DatePicker设置日期的属性示例
  19. leetcode第72题:编辑距离
  20. 【python问题】UnicodeEncodeError: 'ascii' codec can't encode characters in position 306-309: ordinal not in range(128)

热门文章

  1. datetime.now()提示没有now方法
  2. PHP引用(&)
  3. codeblock代码片段
  4. 【LG2839】[国家集训队]middle
  5. JDBC-select练习&jdbc工具类&数据库登录案例
  6. linux高性能服务器编程 (三) --TCP协议详解
  7. shell for 循环演示
  8. 【Gamma】Scrum Meeting 3
  9. SAS PROC SGPLOT
  10. 用Python自动办公,做职场高手(完结)