Redis集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。Redis cluster特点:

1、在线水平扩容能力

2、Failover能力和高可用性


3、架构简单:无中心架构,各个节点度等。slave节点提供数据冗余,master节点异常时提升为master。相对于使用Proxy三层架构,系统复杂度降低,且可节约大量的硬件资源;架构层次减少,提升读写性能,且系统约束降低。

4、cluster不保证主从数据强一致性,failover后可能存在少了的数据丢失

Redis cluster架构图:



1、节点

一个Redis集群通常有多个节点(node)组成。每个节点记录了自身的状态信息和整个集群的状态信息 。

节点状态: clusterNode 结构保存了一个节点的当前状态, 比如节点的创建时间, 节点的名字, 节点当前的配置纪元, 节点的 IP 和地址, 等等。

每个节点都会使用一个 clusterNode 结构来记录自己的状态, 并为集群中的所有其他节点(包括主节点和从节点)都创建一个相应的clusterNode 结构, 以此来记录其他节点的状态:


<span style="font-family:Microsoft YaHei;">struct clusterNode {

    // 创建节点的时间
    mstime_t ctime;

    // 节点的名字,由 40 个十六进制字符组成
    // 例如 68eef66df23420a5862208ef5b1a7005b806f2ff
    char name[REDIS_CLUSTER_NAMELEN];

    // 节点标识
    // 使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
    // 以及节点目前所处的状态(比如在线或者下线)。
    int flags;

    // 节点当前的配置纪元,用于实现故障转移
    uint64_t configEpoch;

    // 节点的 IP 地址
    char ip[REDIS_IP_STR_LEN];

    // 节点的端口号
    int port;

    // 保存连接节点所需的有关信息
    clusterLink *link;

    // ...

};</span>

有三个节点:7000、7001、7003。7000节点状态信息如下:




 集群状态:  clusterStat记录了在当前节点的视角下, 集群目前所处的状态 —— 比如集群是在线还是下线, 集群包含多少个节点, 集群当前的配置纪元, 诸如此类:


<span style="font-family:Microsoft YaHei;">typedef struct clusterState {

    // 指向当前节点的指针
    clusterNode *myself;

    // 集群当前的配置纪元,用于实现故障转移
    uint64_t currentEpoch;

    // 集群当前的状态:是在线还是下线
    int state;

    // 集群中至少处理着一个槽的节点的数量
    int size;

    // 集群节点名单(包括 myself 节点)
    // 字典的键为节点的名字,字典的值为节点对应的 clusterNode 结构
    dict *nodes;

    // ...

} clusterState;</span>




 2、槽

Redis集群通过分片的方式来保持数据库的键值对:集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。

当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中的任何一个槽没有得到处理,那么集群处于下线状态(fail)。通过向节点发送CLUSTER ADDSLOTS命令,可以将一个或多个槽指派给节点负责。在节点的状态信息clusterNode里面记录了该节点负责处理的槽;在集群的状态信息clusterStat里面记录了每个槽由那个节点负责处理。 

在槽指派的过程中,一个基点不仅要记录它要处理的槽,还要将其负责的槽信息传播给集群的其他所有节点 ;因此,每个节点不仅知道自己负责的槽,也知道其他节点负责的槽。

在集群的状态信息clusterStat里面记录 每个槽由那个节点负责,是为了能够快速的知道某个槽是否已经被指派或者取得负责处理该槽的节点,时间复杂度为O(1)。

如:有三个节点的集群,节点7000负责0~5000的槽、节点7001负责5001~10000的槽、节点7002负责10001~16383的槽。clusterStat结构如下:





3、在集群中执行命令

在对数据库中的16384个槽都进行了指派后,集群就会进入上线状态,这时客户端就可以向集群中的节点发送数据命令了。

当客户端向节点发送与数据键相关的命令时,接收命令的节点会计算出命令要处理的数据库建属于哪个槽,并检查这个槽是否指派给了自己:

       1)如果键所在的槽正好就指派给了当前的节点,那么节点直接执行这个命令。

       2)如果键所在的槽并没有指派给当前的节点,那么客户端会向客户端返回一个MOVE操作,指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。 


  

4、重新分片


Redis集群的重新分片操作个将任意数量已经指派给某个节点的槽改为指派给另一个节点,而且相关槽所属的键值对也会从源节点被移动到目标节点。

重新分片可以在线进行,在重新分片的过成功中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。 

Redis集群的重新分片操作是由Redis的集群管理软件redis-trib负责执行的,Redis提供对了进行重新分片的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。

redis-trib对集群的单个槽进行重新分片的操作步骤:

        1)redis-trib向目标节点发送CLUSTER SETSLOT<slot>IMPORTING<source_id>命令,让目标节点准备好从源节点导入属于槽slot的键值对。

        2)redis-trib对源节点发送CLUSTER SETSLOT<slot>MIGRATING<target_id>命令,让源节点准备好将属于槽slot的键值对移植至目标节点。

        3)redis-trib向源节点发送CLUSTER GETKEYSINSLOT<slot><count>命令,获取最多count个属于槽slot的键值对的键名。

        4)对于获得的每个键名,redis-trib向源节点一个MIGRATE<target_id><target_port><key_name>0<timeout>命令,将所选的键原子地从源节点迁移至目标节点。

        5)重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对迁移至目标节点为止。

        6)redis-trib向集群任意一个节点发送 CLUSTER SETSLOT<slot>NODE<target_id>命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽slot已经指派给了目标节点。


              迁移键的过程:


 

5、复制与故障转移

Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,从节点则用于复制某个主节点,并在复制的主节点下线时,代替下线主节点继续处理命令。

5.1 故障检测

      集群中的每个节点都会定期的向其他节点发送PING消息,以此来检测对方是否在线,如果接受PING的节点没有在指定的时间内,向发送PING消息的节点返回PONG下线,那么发送PING消息的节点就会将接收PING消息的节点标标记为疑似下线(PFAIL)。如果在一个集群里面,半数以上负责处理槽的节点都将某个主节点x标记为疑似下线,那么这个主节点x就会被标记为已下线(FAIL);将主节点x表姐为已下线的节点会向集群广播一条关于主节点x下线的消息,所有收到该消息的节点都会立即将主节点x标记为已下线。

5.2 故障转移

      当一个从节点发现自己正在复制的主节点已经进入下线状态是,从节点就将开始对下线主节点进行故障转移。步骤如下:

              1)复制下线主节点的所有从节点里面,会有一个从节点被选中。

              2)被选中的从节点会执行SLAVEOF no one命令,成为新的主节点。

              3)新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。

              4)新节点向集群广播一条PONG消息,说明它已成为主节点,并且接管了原本由下线主节点所处理的所有槽。

              5)新的主节点开始接收和自己负责处理的槽有关的命令请求。故障转移完成。

 

6、集群相关的部分指令

CLUSTER MEET <ip><port>:将一个节点加入集群

CLUSTER NODES:查看集群节点信息

CLUSTER INFO:查看集群信息

CLUSTER ADDSLOTS <slot>[slots...]:向节点指派槽

CLUSTER  KEYSLOT <key>:查看键属于哪个槽