redis多机数据库的实现(主从复制,哨兵,集群)

第三部分 多机数据库的实现

一、复制

redis中,用户可以通过执行SALAVEOF命令或者设置salveof选项,让一个服务器去复制另一个服务器,被复制的服务器为主服务器,对主服务器进行复制的是从服务器。进行复制中的主从服务器双方的数据库将保存相同的数据,概念上将这种现象称作“数据库一致”。

1.1旧版复制功能的实现

redis的复制功能分为同步和命令传播两个操作:

  • 同步操作将从服务器的数据库状态更新至主服务器当前所处的数据库状态
  • 命令传播操作则用于在主服务器的数据库状态被修改,导致主服务器的数据库状态出现不一致时,让主服务器的数据库重新回到一致状态

同步: 从服务器对主服务器的同步操作需要通过向主服务器发送SYNC命令来完成:

  1. 从服务器向主服务器发送SYNC命令
  2. 收到SYNC命令的主服务器执行BGSAVE命令,在后台生成RDB文件,并使用一个缓冲区记录从现在开始执行的所有命令
  3. 当服务器的BGSAVE命令执行完毕时,主服务器会将BGSAVE命令生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态
  4. 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态主服务器数据库当前的状态

redis多机数据库的实现(主从复制,哨兵,集群)

命令传播: 每当主服务器执行客户端发送的名时,主服务器的数据库就有可能被修改,并导致主从服务器状态不再一致。为了保持一致,主服务器需要对从服务器执行命令传播操作,主服务器将自己执行的写命令,发送给从服务器,从服务器执行那条命令。

旧版复制功能的缺陷:

  1. 初次复制: 从服务器以前没有复制过任何主服务器,或者从服务器当前要复制的主服务器和上一次复制的主服务器不同。

  2. 断线后重复制:处于命令传播阶段的主服务器因为网络原因而中断了复制,但从服务器通过自动重连接重新连接上了主服务器,并继续复制主服务器。

对于初次复制来说,旧版复制功能能够很好地完成任务,但对于断线后重复制来说,旧版复制功能效率很低。

如果主从服务器有一千个相同的数据,这时候从服务器断开连接,主服务器又执行了一条命令操作,从服务重新连接,主从服务器只有一条不同的数据。这时为了让从服务器补足这一小部分缺失的数据,就要重新执行一次SYNC命令(SYNC是一个非常耗时的操作),无疑是非常低效的。

1.2新版复制功能的实现

redis从2.8版本开始用PSYNC命令代替SYNC命令来执行复制时的同步操作,PSYNC有完整重同步和部分重同步两种模式。

  • 完整重同步用于处理初次复制情况:和SYNC执行步骤一样

  • 部分重同步用于断线后重新复制情况:从服务器重新连接主服务器,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只需要接收并执行这些写命令,就可以将数据库更新至主服务器当前状态
    redis多机数据库的实现(主从复制,哨兵,集群)

1.3 部分重同步的实现

三部分:主服务器的复制偏移量和从服务器的复制偏移量、主服务器的复制积压缓冲区、服务器的运行ID

复制偏移量: 主从服务器都分别维护一个复制偏移量

主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N。

从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N
redis多机数据库的实现(主从复制,哨兵,集群)
redis多机数据库的实现(主从复制,哨兵,集群)

通过对比主从服务器的复制偏移量,程序可以很容易地知道主从服务器是否处于一致态

复制积压缓冲区:是一个由主服务器维护的一个固定长度先进先出队列,默认大小为1MB。主服务器进行命令传播时,不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区。

redis多机数据库的实现(主从复制,哨兵,集群)

因此,主服务器的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量。
redis多机数据库的实现(主从复制,哨兵,集群)

当从服务器重连后,从服务器发送PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行哪种操作:

  • 如果offset偏移量之后的数据(也即是偏移量offset+1开始的数据)任然存在于复制缓冲区里面,那么主服务器对从服务器执行部分重同步操作,反之,完整重同步。

如图所示的断开操作,以下是执行步骤:
redis多机数据库的实现(主从复制,哨兵,集群)

  1. 从服务器A重连,并发送PSYNC命令,报告自己的复制偏移量10086
  2. 主服务器收到10086这个偏移量,先检查10086是否存在于复制缓冲区,因为存在,所以主服务器向从服务器发送+CONTINUE回复,表示将重同步操作进行数据同步
  3. 接着主服务器会将复制积压缓冲区10086后的书记发送给从服务器
  4. 从服务器接收只收到这33字节的确实数据,就可以回到与主服务器一致的状态
    redis多机数据库的实现(主从复制,哨兵,集群)

服务器运行ID: 每个redis服务器,都有自己的运行ID,每个ID在服务器启动时自动生成。

  1. 当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID传送给从服务器,而从服务器则会将这个运行ID保存下来。从服务器断开重新连接上一个主服务器时,从服务器将向当前连接的主服务器发送之前保存的运行ID
  2. 如果相同,执行部分重同步操作
  3. 如果不同,说明不是刚才的那个服务器,执行完整重同步操作

1.4 PSYNC命令的实现

redis多机数据库的实现(主从复制,哨兵,集群)

1.5 复制的实现

通过向从服务器发送SLAVEOF命令,我们可以让一个从服务器去复制一个主服务器:

SLAVEOF <master_ip> <master_port>

  1. 设置主服务器的地址和端口:从服务器首先要做的是将客户端给定的主服务器IP地址和端口号保存到服务器状态的masterhost属性和masterport属性里面
  2. 建立套接字链接:从服务器创建套接字成功,从服务器会为这个套接字关联一个专门用于处理复制工作的文件事件处理器。主服务器接收从服务器的套接字链接后,将为套接字创建相应的客户端状态,将从服务器看成一个主服务器的客户端。
  3. 从服务器发送PING:虽然建立了链接,但是双方未使用该套接字任何通信,可以检查套接字的读写状态是否正常。还可以检查主服务器能否正常处理命令。
  4. 身份验证:如果从服务器设置了masterauth选项,进行身份验证,反之,不验证

redis多机数据库的实现(主从复制,哨兵,集群)

  1. 发送端口信息:从服务器执行命令REPLCONF listening-port ,向主服务器发送从服务器的监听端口号
  2. 同步:从服务器向主服务器发送PSYNC命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前的状态
  3. 命令传播:主服务器将自己执行的写命令发送给从服务器

1.6 心跳检测

在命令传播阶段,从服务器会每秒向主服务器发送命令

REPLCONF ACK <replication_offset>

replication_offset是从服务器当前的复制偏移量

作用:

  1. 检测主从服务器网络链接状态:如果主服务器超过疫苗证没有收到从服务器发送来的REPLCONF ACK ,那么主服务器就知道连接出问题了
  2. 复制实现min-slaves选项:可以防止主服务器在不安全的情况下执行写命令
  3. 检测命令丢失:传播过程中可能因为网络原因丢失一部分写命令,导致主从服务器复制偏移量不同,主服务器再次补发缺失的命令发送给从服务器

redis多机数据库的实现(主从复制,哨兵,集群)

1.7 小结

redis多机数据库的实现(主从复制,哨兵,集群)

二、Sentinel(哨兵)

哨兵是redis高可用性解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这个主服务器下的从服务器,并在被监视的主服务器进入下线专题时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器已代替下线的主服务器继续处理命令

redis多机数据库的实现(主从复制,哨兵,集群)

如果server1的下线时长超过用户设定的下线时长上限时,Sentinel系统就会对server1执行故障转移操作:

  1. Sentinel系统选一个server1属下的从服务器,并将这个从服务器升级为新的主服务器
  2. Sentinel系统会向server1属下的所有从服务器发送新的复制指令,让他们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕
  3. Sentinel还会继续监视以下线的server1,并在它重新上线时,将他设置为新的主服务器的从服务器

redis多机数据库的实现(主从复制,哨兵,集群)

2.1 启动并初始化Sentinel

启动一个Sentinel可以使用命令

redis-sentinel /path/to/your/sentinel.conf 或者

redis-sentinel /path/to/your/sentinel.conf --sentinel

执行步骤:

  1. 初始化服务器:Sentinel不使用数据库,所以初始化不会载入RDB或者AOF文件,

    Sentinel还使用对数据库操作的一些命令,如SET、DEL、FLUSHDB等,因为并没有载入这些命令,PING、SENTINEL、INFO、SUBSCRIBE、UNSUBSCRIBE、 PSUBSCRIBE、PUNSUBSCRIBE这就是客户端对sentinel的全部命令了

  2. 将普通redis服务器使用的代码替换为Sentinel专业代码

  3. 初始化Sentinel状态:应用了sentinel的专用代码后,服务器会初始化一个sentinel.c/sentinelState结构,保存了服务器中所有和sentinel功能有关的状态

  4. 根据给定的配置文件,初始化Sentinel的监视主服务器列表:master字典记录了所有被sentinel监视的主服务器有关信息(字典的键是被监视主服务器的名字,值是被监视主服务器对应的sentinel.c/sentinelRedisInstance结构),每个sentinelRedisInstance结构代表一个被sentinel监视的redis服务器实例,这个实例可以是从服务器,主服务器,或者另一个sentinel。

redis多机数据库的实现(主从复制,哨兵,集群)

redis多机数据库的实现(主从复制,哨兵,集群)

  1. 创建连向主服务器的网络连接:sentinel成为主服务器的客户端,它可以向主服务器发送服务器发送命令,并从命令回复中获取相关的信息

对于被sentinel监视的主服务器来说,sentinel会创建两个连向主服务器的异步网络连接:

  • 一个是命令连接,这个连接专门用于向主服务器发送命令,并接受命令回复
  • 另一个是订阅连接,这个连接专门用于订阅服务器的_sentinel_:hello频道
    redis多机数据库的实现(主从复制,哨兵,集群)

2.2获取主服务器信息

sentinel默认每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前消息。

通过分析主服务器返回的INFO命令回复,sentinel可以获取两个方面的信息:

  1. 关于主服务器本身的信息,包括run_id域记录的服务器运行ID,以及role域记录的服务器角色。
  2. 关于主服务器属下的所有从服务器的信息,每个从服务器都由一个“slave”字符串开头的行记录,每行的ip=域记录了从服务器的IP地址,而port=域则记录了从服务器的端口号。根据ip地址和端口号,sentinel无需用户提供从服务器的地址信息,就可以自动发现从服务器。

根据run_id域和role域记录的信息,sentinel将对主服务器的实例结构进行更新。例如,主服务器重启之后,它的运行ID就会和实例结构之前保存的运行ID不同,sentinel检测到这一情况之后,就会对视力结构的运行ID进行更新。

返回的从服务器信息,用于更新主服务器实例结构的slaves字典,这个字典记录了主服务器属下从服务器的名单。字典的键是ip:port ;值是服务器对应的实例结构

redis多机数据库的实现(主从复制,哨兵,集群)

2.3获取从服务器信息

当sentinel发现主服务器有新的从服务器出现时,sentinel除了会为这个新的服务器创建相应的实例结构之外,还会创建连接到从服务器的命令连接和订阅连接
redis多机数据库的实现(主从复制,哨兵,集群)

创建连接后,sentinel会以每十秒一次的频率向从服务器发送INFO命令,获取的内容如下:

  • 从服务器的运行ID run_id
  • 从服务器的角色role
  • 主服务器的IP地址master_host,以及主服务器的杜鳌口红master_port
  • 主从服务器的连接状态master_link_status
  • 从服务器的优先级slave_priority
  • 从服务器的复制偏移量slave_repl_offset

根据这些信息sentinel会对从服务器实例结构更新

redis多机数据库的实现(主从复制,哨兵,集群)

2.4向主服务器和从服务器发送信息

sentinel默认会每两秒一次的频率,通过命令连接向所有被监视的至服务器和从服务器发送命令

redis多机数据库的实现(主从复制,哨兵,集群)

redis多机数据库的实现(主从复制,哨兵,集群)

2.5接收来自主服务器和从服务器的频道信息

当sentinel与一个主服务器或从服务器建立订阅连接之后,sentinel就会通过订阅连接,向服务器发送命令:SUBSCRIBE _sentinel_:hello。 sentinel对 _sentinel_:hello频道的订阅会一直持续到sentinel与服务器断开为止。

sentinel会通过命令连接向服务器的 _sentinel_:hello频道发送信息,又通过订阅连接从服务器的 _sentinel_:hello频道接收信息。
redis多机数据库的实现(主从复制,哨兵,集群)

假设sentinel1向服务器的 _sentinel_:hello频道发送一条信息,所有订阅了 _sentinel_:hello频道的sentinel都会收到这条信息

redis多机数据库的实现(主从复制,哨兵,集群)

当sentinel收到从 _sentinel_:hello频道的信息时,他会进行检查:

  • 如果信息中记录的sentinel运行ID和接收信息的sentinel的运行ID相同,那么说明这条信息是自己发送的,丢弃
  • 反之,那么说明这条信息是监视同一个服务器的其他sentinel发过来的,接收信息的sentinel将根据信息中的各个参数,对相应主服务器的实例结构进行更新

更新sentinels字典:

  • sentinel为主服务器创建的实例结构中的sentinels字典保存了除sentinel本身之外,所有同样监视这个主服务器的其他sentinel的资料。sentinels的键是ip:port,值是键所对应sentinel的实例结构。
  • sentinel收到其他sentinel发来的信息时,目标sentinel会从信息中分析并提取出下面两个 方面参数:1. 与sentinel有关的参数(源sentinel的IP地址。端口号、运行ID和配置纪元) 2. 与主服务器有关的参数(源sentinel正在监视的主服务器的名字IP地址。端接口和配置纪元)
  • 根据信息中的主服务器参数,目标sentinel会在自己的sentinel状态的masters字典中找相应的主服务器实例结构,然后根据取出的sentinel参数,检查主服务器实例结构的sentinels字典中,源sentinel的实例结构是否存在,如果存在,那么更新;不存在,说明源sentinel是刚刚开始监视主服务器的心的sentinel,目标sentinel会为源sentinel创建一个新的实例结构,并将这个结构添加到sentinels字典里面

创建连向其他sentinel的命令连接:当sentinel通过频道信息发送一个新的sentinel时,会为新的sentinel在sentinels字典里创建相应的实例结构,还会创建一个连向新的sentinel的命令连接,新的sentinel也同样创建连向这个sentinel的命令连接,最终形成相互连接的网络。
redis多机数据库的实现(主从复制,哨兵,集群)

2.6检测主观下线状态

默认情况下,sentinel会以每秒一次的频率向所有与它创建了命令连接的实例发送PING命令,通过实例返回的PING命令回复来判断实例是否在线。

redis多机数据库的实现(主从复制,哨兵,集群)

有效回复:实例返回+PONG、-LOADING、-MASTERDOWN三种回复的一种

无效回复:实例返回+PONG、-LOADING、-MASTERDOWN三种回复之外的其他回复

检查客观下线状态:当sentinel将一个主服务器判断为主观下线后,为了确认这个主服务器是否真的下线,它会向同样监视这一主服务器的其他sentinel进行询问,看他们是否也认为主服务器下线了。当sentinel从其他sentinel那里接收到足够数量的下线判断之后,sentinel就会将从将服务器判定为客观下线,并对主服务器执行故障转移操作。

如果实例的相应时间超过down-after-milliseconds设置的时间(毫秒),或者响应无效,那么Sentinel会修改这个实例锁对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来标识这个实例已经进入主观下线状态

redis多机数据库的实现(主从复制,哨兵,集群)

2.7选举领头Sentinel

当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个sentinel会进行协商,选举一个领头sentinel,并由这个sentinel对下线主服务器执行故障转移操作,以下是规则和方法

redis多机数据库的实现(主从复制,哨兵,集群)

2.8故障转移

选举出领头sentinel后,领头sentinel将对已下线的主服务器执行故障转移操作

  1. 在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器

redis多机数据库的实现(主从复制,哨兵,集群)

下图展示了领头sentinel向北选中的从服务器server2发送SLAVEOF no one命令的情形,发送命令之后,sentinel每秒一次的频率,向被升级的从服务器发送INFO命令,当被升级的服务器的role从原来的slave变成master时,领头sentinel就知道这个从服务器变成主服务器了

redis多机数据库的实现(主从复制,哨兵,集群)

  1. 让已下线主服务器属下的所有从服务器改为复制新的主服务器:

    新主服务器出现后,领头sentinel让已下线主服务器属下的所有从服务器改为复制新的主服务器,这一动作通过向服务器发送SLAVEOF,命令来实现

redis多机数据库的实现(主从复制,哨兵,集群)

  1. 将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会变成新的主服务器的从服务器

redis多机数据库的实现(主从复制,哨兵,集群)

2.9小结

​ sentinel只是一个运行在特殊模式下的redis服务器,它使用了和普通模式不同的命令表,所以sentinel模式能够使用的命令换个普通redis服务器能够使用的命令不同

redis多机数据库的实现(主从复制,哨兵,集群)

三、集群

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

3.1节点

一个redis集群通常由多个节点组成,刚开始每个节点都是相互独立的,要建立一个集群,必须将各个独立的节点连接起来。连接各个节点可以使用CLUSTER MEET命令来完成,该命令的格式如下:

CLUSTER MEET

向一个节点node发送CLUSTER MEET命令,可以让node节点与ip和port所指定的节点进行握手,当握手成功时,node节点就会将ip和port所指向的节点添加到node节点当前所在的集群中。以下是一个握手过程例子:

redis多机数据库的实现(主从复制,哨兵,集群)

  • 启动节点: 一个节点就是运行在集群模式下的redis服务器,redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。集群模式会继续使用单机模式中使用的服务器组件,至于集群模式才会使用到的数据,节点将他们保存到了cluster.h/clusterNode结构、cluster.h/clusterLink结构和cluster.h/clusterState结构里

  • 集群数据结构

    1. clusterNode结构保存了一个节点的当前状态,比如节点的创建时间、节点的名字、节点当前的配置纪元、节点的ip地址和端口号等。每个节点都使用一个clusterNode来记录自己的状态,并为集群中的所有其他节点都创建一个clusterNode结构,一次来记录其他节点的状态

redis多机数据库的实现(主从复制,哨兵,集群)

  1. clusterNode结构的link属性是一个clusterLink结构,该结构保存了连接节点所需要的有关信息,比如套接字描述符,输入缓冲区和输出缓冲区

redis多机数据库的实现(主从复制,哨兵,集群)

  1. 每个节点都保存一个clusterState结构,记录了当前节点的视角下,集群目前所处的状态,例如集群是在线还是下线,集群包括多少个节点,集群当前的配置纪元

redis多机数据库的实现(主从复制,哨兵,集群)

从7000的角度记录了集群以及集群包含的三个节点的当前状态

redis多机数据库的实现(主从复制,哨兵,集群)

  • CLUSTER MEET命令的实现:

    通过节点A发送的CLUSTER MEET 命令,客户端可以让接受命令的节点A 将另一个节点B添加到节点A当前所在的集群里面:

redis多机数据库的实现(主从复制,哨兵,集群)

3.2槽指派

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

当数据库中的16384个槽都有节点在处理时,集群处于上线状态;相反地,如果有一个槽没有得到处理,集群处于下线状态

通过向节点发送CLUSTER ADDSLOTS命令,我们可以将一个或者多个槽指派给你节点负责

记录节点中的槽指派: clusterNode结构的slots属性和numslots属性记录了节点负责处理哪些槽。

  • slots属性是一个二进制数组,这个数组的长度是16384/4=2048个字节,共包含16384个二进制位,redis从0到16383进行编号,并根据索引i上的二进制位的值来判断节点是否负责处理槽i。如果slots数组在索引i上的二进制位的值为1,那么表示节点负责处理槽i,如果为0,表示节点不负责处理槽i。numslots属性记录节点负责处理槽的数量,就是slots数组中未1的二进制的数量

redis多机数据库的实现(主从复制,哨兵,集群)

传播节点的槽指派信息: 一个节点还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。

节点A通过消息从节点B那里接受节点B的slots数组时,节点A会在自己的clusterState.node字典中查找节点B对应的clusterNode结构,并对结构中的slots数组进行保存或者更新。集群中的每个节点都会知道数据库中的16384个槽分别被指派给了集群中的哪些节点。

记录集群所有槽的指派信息: clusterState结构中的slots数组记录了集群中所有不16384个槽的指派信息:slots数组包含了16384个项,每个数组项都是一个指向clusterNode结构的指针:如果slots[i]指针指向NULL,那么表示槽i尚未指派给任何节点,反之则指派给了一个clusterNode结构所代表的的节点。
redis多机数据库的实现(主从复制,哨兵,集群)

CLUSTER ADDSLOTS命令的实现: 一个节点的clusterState.slots数组中的所有指针都指向null,并且clusterNode.slots数组中的所有二进制位都为0,说明当前节点没有被指派给任何槽,并且集群中的所有节点都是未指派的。
redis多机数据库的实现(主从复制,哨兵,集群)

将槽1和槽2指派给节点后

redis多机数据库的实现(主从复制,哨兵,集群)

3.3在集群中执行命令

对所有的槽16384都进行了指派后,集群就上线了,客户端发送命令的时候,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己,如果键所在的槽就是指派给了当前节点,那么节点直接执行这个命令,反之,返回给客户端一个MOVED错误,指引客户端去正确的节点,并再次发送之前想要执行的命令。

计算节点属于哪个槽: return CRC16 (key) & 16383

判断槽是否由当前节点负责处理: 节点计算出键所属的槽i之后,节点就会检查自己在clusterState.slots数组中的项i,判断键所在的槽是否由自己负责。如果clusterState.slots[i]等于clusterState.myself,那么说明槽i是当前节点负责,反之就返回MOVED错误。

MOVED错误: 错误的格式为 MOVED : 例如MOVED 10086 127.0.0.1:1002 表示槽10086正在由ip地址为127.0.0.1,端口号为7002的节点负责。

一个集群客户端通常会与多个节点创建套接字连接,而所谓的节点转向实际上就是换一个套接字来发送命令,如果没有连接就先连接再转。

节点数据库的实现: 节点和单机服务器在数据库方面的一个区别就是节点只能使用0号数据库。除了键值对保存在数据库里面之外,节点还会用clusterState结构中的slots_to_keys跳跃表保存槽和键之间的关系。

slots_to_keys跳跃表每一个节点的分值都是一个槽号,而每个节点的成员都是一个数据库键。例如:

redis多机数据库的实现(主从复制,哨兵,集群)

节点7000将创建类似下图的跳跃表:

键“book”所在跳跃表节点的分值为1337.0,表示键“book”所在的槽为1337

键“date”所在跳跃表节点的分值为2020.0,表示键“book”所在的槽为2020

键“lst”所在跳跃表节点的分值为3347.0,表示键“book”所在的槽为3347

redis多机数据库的实现(主从复制,哨兵,集群)

通过在跳跃表中记录各个数据库所属的槽,节点可以很方便地对属于某个或某些槽的所有数据库键进行批量操作。

3.4重新分片

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

实现原理:

redis多机数据库的实现(主从复制,哨兵,集群)

3.5ASK错误

在从新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。

当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽中时:

  1. 源节点会先在自己的数据库里面查找指定的键,如果找得到的话,就直接执行客户端发送的命令。
  2. 相反的,如果源节点没能在自己的数据库里面找到指定的键,那么这个键可能被迁移到目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点。

ASK错误和MOVED错误的区别:

  1. MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于槽i的MOVED错误之后,客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点。
  2. 相反,ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施:在客户端收到关于槽i的ASK错误之后,客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点,但这种转向不会对客户端今后不发送关于槽I的命令请求产生任何影响,客户预订仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点,除非ASK错误再次出现。

3.6复制与故障转移

redis集群中的节点分为主节点和从节点,其中你那个主节点用于处理槽,从节点则用于复制某个主节点,并在被复制的直接点下线时,代替下线主节点继续处理命令

redis多机数据库的实现(主从复制,哨兵,集群)

如果节点7000下线,那么剩下的几个主节点会在7004和7005选提个作为主节点,这个主节点接管原来7000的槽。

redis多机数据库的实现(主从复制,哨兵,集群)

故障处理完毕后,7000重新上线后会成为7004的从节点

设置从节点: 向一个节点发送命令:CLUSTER REPLICATE <node_id> 可以让接受命令的节点成为node_id所指定节点的从节点,并开始对主节点进行复制。

  1. 接受命令的节点首先会在自己的clusterState.nodes字典中找到node_id所对应节点的clusterNode结构,并将自己的clusterState.myself.slaveof指针指向这个结构,以此来记录这个节点正在复制的主节点。redis多机数据库的实现(主从复制,哨兵,集群)

  2. 节点会修改自己的clusterState.myself.flags中的属性,关闭原本的REDIS_NODE_MASTER标识,打开REDIS_NODE_SLAVE标识,表示这个节点已经标称从节点

  3. 最后,节点调用复制代码,并根据clusterState.myself.slaveof指向的clusterNode结构所保存的IP地址和端口号,对主节点进行复制

举例:下图展示7004在复制7000时的clusterState结构

执行第二步,改clusterState.myself.flags。clusterState.myself.slaveof指向7000

redis多机数据库的实现(主从复制,哨兵,集群)

一个节点成为从节点,并开始复制某个主节点这一信息会通过消息发送给集群中其他节点,最终集群中的所有节点都会知道某个从节点正在复制某个主节点。

并且集群中的所有节点都会在代表主节点的clusterNode结构的slaves属性和numslaves属性记录正在复制这个主节点的从节点名单

故障检测: 集群中每个节点都会定期向其他节点发送PING消息,来检测对方是否在线,如果就收PING消息的节点没有在规定时间内,发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线。

集群中的各个节点会通过相互发送消息的方式来交换集群中各个节点的状态信息,例如某个节点是出于在线状态、疑似下线状态,还是已下线状态

如果一个集群里,半数以上负责处理槽的主节点都将某个主节点x报告为疑似下线,那么这个这节点x将被标记为已下线,将主节点x标记为已下线的节点会向集群广播一条关于主节点x的FALL消息,所有收到这个消息的节点会提及将主节点x标记为已下线。

故障转移: 当一个节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移:

  1. 复制下线主节点的所有从节点里面,会有一个从节点被选中
  2. 被选中的从节点会执行SLAVEOF on one命令,成为新的主节点
  3. 新的洗护节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己
  4. 新的主节点向集群中广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点

选举新的主节点:

3.7消息

节点间通过发送和接收消息进行通信,消息分为消息头和消息正文两部分,节点发送的消息主要有五种:

  1. MEET消息:当发送者接收到客户端发送的CLUSTER MEET命令时,发送者会向接受者发送MEET消息,请求接收者加入到发送者当前所处的集群中。
  2. PING消息:集群里面的每一个节点默认每个一秒就会从已知节点列表中随机选出五个节点,然后对五个节点中最长时间没有发生PING消息的节点发送PING消息,以此来检测被选中的节点是否在线
  3. PONG消息:当接收者受到发送者发来的MEET消息或者PING消息时,为了想发送者确认这条消息已到达,接受者会向发送者返回一条PONG消息。
  4. FALL消息:当一个主节点A判断另一个主节点B已经进入FALL状态时,节点A会向集群中广播一条关于B的FALL消息,所有收到这条消息的节点都会立即将节点B标记为已下线
  5. PUBLISH消息:当节点收到一个PUBLISH命令时,节点会执行这个命令,并向集群中广播一条PUBLISH消息,所有接收到这条消息的节点都会执行相同的PUBLISH命令

3.8小结

redis多机数据库的实现(主从复制,哨兵,集群)