Redis的设计与实现(6):Redis如何实现高可用

十、Sentinel

Sentinel(哨兵)是Redis的高可用性(Hign Availability,HA)的解决方案,由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器以及属下的所有从服务器。当主服务器下线时,自动将下线的某个主服务器属下的某个从服务器升级为新的主服务器,从而实现故障转移,当原来的主服务器重新上线时,会被降级为从服务器。
下面展示了哨兵监视主从的状态:
image.png

10.1启动并初始化Sentinel

启动Sentinel有两种方式:

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

俩命令效果相同,启动时需要执行以下步骤:

  1. 初始化服务器。
  2. 将普通Redis服务器使用的代码替换成Sentinel专用代码。
  3. 初始化Sentinel状态。
  4. 根据配置文件,初始化Sentinel的监视主服务器列表。
  5. 创建连向主服务器的网络连接。

初始化服务器
Sentinel实际上是一个特殊的Redis服务器,所以很多地方和Redis服务器的初始化有些类似。但是它并不适用数据库,所以少了RDB或AOF文件的载入等操作。
image.png
使用Sentinel专用代码
将使用的代码如加载的常量,命令表(决定了Sentinel可以执行哪些命令)等替换为Sentinel专用的代码,端口为26379,命令表中没有set、get等命令。
初始化Sentinel状态
初始化一个sentinel.c/sentinelState结构,记录Sentinel的状态,保存了服务器中所有与Sentinel相关的状态:

struct sentinelState{     
    //当前纪元,选举计数器,用于实现故障转移   
    uint64_ t current_ epoch;    
    
    //(重点)保存了所有被这个sentinel监视的主服务器    
    //字典的键是主服务器的名字,值是一个指向sentinelRedisInstance结构的指针    
    dict *masters;   
    
    //是否进入了TILT模式     
    int tilt;     
    
    //目前正在执行的脚本的数量     
    int running_ scripts;   
    
    //进入TILT模式的时间    
    mstime_ _t tilt_ start_ time;  
    
    //最后一次执行时间处理器的时间    
    mstime_ t previous_ time ;   
   
    //一个FIFO队列,包含了所有需要执行的用户脚本     
    list *scripts_ queue;
    
}sentinel;

初始化master属性
masters字典的键为被监视主服务器的名字,值是被监视服务器对应的sentinelRedisInstance结构。这个结构在很多地方都用到了如,主服务器、从服务器和另一个sentinel。

typedef struct sentinelRedisInstance {     
    //标识值,记录了实例的类型,以及该实例的当前状态.     
    int flags;    
    
    //实例的名字.     
    //主服务器的名字由用户在配置文件中设置    
    //从服务器以及Sentinel 的名字由Sentinel 自动设置    
    //格式为ip:port     
    char *name;     
    
    //实例的运行ID     
    char *runid;
    
    //配置纪元,用于实现故障转移    
    uint64_t config_epoch; 
    
    //实例的地址     
    sentinelAddr *addr;  
    
    // SENTINEL down-after-milliseconds 选项设定的值     
    //实例无响应多少毫秒之后才会被判断为主观下线    
    mstime_t down_after_period;     
    
    //判断这个实例为客观下线所需的支持投票数量     
    int quorum;    
    
    //在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量     
    int paral1el_syncs;  
    
    //刷新故障迁移状态的最大时限    
    mstime_t failover_timeout;  
    
    // ... 
} sentinelRedisInstance; 

image.png

创建连向主服务器的网络连接
Sentinel会为监视的主服务器创建两个异步网络连接:

  • 命令连接:专用于向主服务器发送命令,接收命令回复。
  • 订阅连接:专用于订阅主服务器__sentinel__:hello频道。(由于Redis的发布订阅消息不会保存,客户端断线就会丢失,为了不丢失,必须使用专门的频道连接)

image.png

10.2获取主服务器信息

Sentinel默认10秒一次通过命令连接被监视的主服务器并发送INFO命令,获取主服务器信息。主要获取主服务器本身信息(如服务器运行ID),下属从服务器信息(如ip,port,offset)。根据返回信息对主服务器的结构进行更新,如没有某个从服务器新信息就会创建一个实例结构,放到主服务器的slaves字典中,键为ip+端口,值为sentinelRedisInstance结构。
image.png

10.3获取从服务器信息

当Sentinel发现主服务器有新的从服务器时,除了创建新实例,还会创建连接到从服务器的命令连接订阅连接。每10S一次的频率向从服务器发送INFO命令,获取从服务器的相关信息,对实例结构进行相应更新。
image.png

10.4向主服务器和从服务器发送消息

sentinel默认以两秒一次,向服务器的__sentinel__:hello频道发送消息,命令:
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
参数包含sentinel本身(s__…)和主服务器(m…)的运行ID,ip,端口号,配置纪元等参数。

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

Sentinel与一个主服务器或从服务器建立订阅连接后,会发送SUBSCRIBE _sentinel_:hello命令订阅频道。
也就是Sentinel通过命令连接发送信息到频道,又通过订阅连接接收频道中的信息。一个Sentinel向_sentinel_:hello频道发的信息也会被其他订阅了_sentinel_:hello频道的Sentinel接收,根据信息记录的Sentinel运行id和接收信息的Sentinel运行id是否相同,来决定是否处理这条消息。通过这种透明的沟通机制,Sentinel可以对各自监听的服务器信息进行更新。
image.png

更新sentinels字典
根据从__sentinel__:hello频道接收而来的消息,Sentinel会更新实例结构中sentinels字典保存的所有Sentinel实例的信息。键为Sentinel的ip+端口,值为某个Sentinel的实例。消息接收者会检查发送消息的Sentinel(源sentinel)结构是否在sentinels字典存在则更新,没有则创建实例。
通过这种发布订阅的方式,Sentinel不需要提供各个Sentinel地址,监视同一个主服务器的多个Sentinel会自动发现对方。

创建连向其他Sentinel的命令连接
Sentinel也会为对方互相创建命令连接,最终监视同一主服务器的多个Sentinel会形成一个网络。Sentinel之间不会创建订阅连接,Sentinel与主或从服务器之间的订阅连接是用来发现未知的Sentinel,而相互已知的Sentinel使用命令连接通信就足够了。
image.png

10.6检测主观下线状态

Sentinel默认每秒与创建命令连接的实例(主服务器,从服务器,其他sentinel)发送PING命令,通过回复判断是否在线。回复可以分为两种:

  • +PONG,-LOADING,-MASTERRDOWN中任意一种为有效回复
  • 除上面三种回复外都是无效回复

根据配置文件的down-after-milliseconds指定的主观下线所需时长内是否一直返回无效回复,来判断实例是否已经主观下线。如果下线了就将实例的的flags标识属性打开SRI_S_DOWN标识。由于每个Sentinel中的主观下线时间配置都可以不同,所有有可能某个Sentinel判断主观下线时,另一个Sentinel认为在线状态
down-after-milliseconds配置不仅对主服务器有效,对判断主服务器的从服务器及监视该主服务器的其他Sentinel都有效。

到这里可以发现共有三个定时发送命令

频率命令/作用发送者接收者
10秒一次INFO/更新主从服务器信息sentinel主从服务器
2秒一次PUBLISH sentinel:hello/获取其他sentinel信息sentinel服务器的__sentinel__:hello频道,订阅该频道的所有sentinel都能收到
1秒一次PING/判断对方是否在线sentinel主服务器,从服务器,其他sentinel

10.7检查客观下线状态

当Sentinel判断主服务器为主观下线时,还会向其他Sentinel询问,得到足量数量已下线(可以是主观下线也可以是客观下线)判断后,就会判定服务器为客观下线。

Sentinel发送sentinel is-master-down-by-addr命令
Sentinel使用:SENTINEL is-master-down-by-addr <ip> <port> <current. epoch> <runid>命令询问其他Sentinel是否同意主服务器下线。
这些参数的意义如下:
image.png
目标Sentinel接收sentinel is-master-down-by-addr命令
其他接收并返回三个参数的Multi Bulk回复:<down_state>、<leader_runid>、<leader_epoch>
image.png

Sentinel接收sentinel is-master-down-by-addr命令的回复
统计其他Sentinel同意主服务器已下线数量,当数量超过配置值(quorum参数)时,sentinel会将主服务器实例的flags属性的SRI_O_DOWN属性打开,表示已进入客观下线状态。
不同的Sentinel由于配置的quorum不同,对于判断一个服务器是否客观下线的结果是不同的。

10.8选举领头Sentinel

当主服务器被判断为客观下线时,监视这个主服务器的sentinel会协商选举领头sentinel,并由领头sentinel对下线主服务器执行故障转移操作。
SENTINEL is-master-down-by-addr命令已经确认主服务器客观下线时,Sentinel还会再发送带有选举性质的该命令,并且带上自己的运行ID。如果接收命令的Sentinel还没设置局部领头时,就会将这个运行ID作为自己的Multi Bulk回复参数,根据回复参数来判断多少Sentinel将自己设置为局部领头。因为网络延迟,有的Sentinel命令比其他Sentinel都先到达,并且胜出(必须有半数以上的票),那么就由它负责故障转移。一次选举没有产生,一段时间后再次选举,直到选出,每次选举结束,配置纪元+1。

10.9故障转移

故障转移包括3步:

  1. 在已下线的主服务器属下从服务器里选出一个将其转为主服务器。
  2. 让其他从服务器都复制新主服务器。
  3. 当原来的主服务器再次上线时,让他成为新主服务器的从服务器。

选出新主服务器
如何选新的主服务器?Sentinel会将所有从服务器放入列表,一项一项过滤:

  • 删除处于下线或断线状态的从服务器。
  • 删除最近5秒没有回复过领头sentinel INFO命令的从服务器。
  • 删除与已下线服务器断开时间超过down-after-milliseconds*10毫秒的从服务器。
  • 然后根据优先级排序,相同则选偏移量最大的,仍相同则选运行ID最小的。

选出来之后,对这个从服务器发送SLAVEOF no one命令,然后以每秒一次的频率向它发送INFO命令,观察返回的role属性如果变成master,就表示顺利升级为主服务器了。
image.png
修改从服务器的复制目标
向所有其他从服务器发送SLAVEOF命令,让他们都去复制新的主服务器。
image.png
将旧的主服务器变为从服务器
当原来的主服务器上线时,Sentinel就会向它发送SLAVEOF命令,让他成为新主服务器的从服务器。
image.png
实际使用时,客户端是通过连接sentinel来连接master的。Sentinel主要用于在单实例模式(单主机/一个或多个从机)中使用Redis的管理和高可用。在集群模式下使用Redis时,不需要Sentinel,而是使用下面介绍的cluster。
image.png

十一、集群

集群是Redis提供的分布式数据库方案,通过分片来进行数据共享并提供复制和故障转移的功能。主要对集群的节点,槽指派,命令执行,重新分片,转向,故障转移,消息进行介绍。
集群的作用:

  • 分散单台服务器的访问压力,实现负载均衡
  • 分散单台服务器的存储压力,实现可扩展性
  • 降低单台服务器宕机带来的业务灾难

11.1节点

集群由多个节点组成,一开始每个节点都是独立的,每个节点都只处在一个只包含自己的集群里,要组件一个真正的集群需要通过CLUSTER MEET 将节点连接起来。这个命令主要是将目标节点加入到当前Redis所在的集群中。
image.png
启动节点
Redis服务器在启动时会根据cluster-enable配置决定是否开启集群模式。集群相关的数据保存在cluster.h/clusterNode结构、cluster.h/clusterLink结构和cluster.h/clusterState结构。
image.png
集群数据结构
集群的每个节点都会用clusterNode来保存当前状态,比如节点的名字、IP和端口等。

struct clusterNode {
    //创建节点的时间
    mstime_t ctime; .
    //节点的名字,由40个十六进制字符组成
    char name [REDIS_CLUSTER_NAMELEN] ; 
    //节点标识
    //使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
    //以及节点目前所处的状态(比如在线或者下线)。
    int flags;
    //节点当前的配置纪元,用于实现故障转移
    uint64_t configEpoch;
    //节点的IP地址
    char ip[REDIS_IP_STR_LEN] ;
    //节点的端口号
    int port;
    //保存连接节点所需的有关信息
    clusterLink *link;
    ...
};

其中的link属性结构为clusterLink,保存着集群相关的数据,它与redisClient结构的区别是它的套接字和缓冲区是用来连接节点的,而redisClient是用来连接客户端的。

typedef struct clusterLink {
    //连接的创建时间
    mstime_t ctime ;
    // TCP 套接字描述符
    int fd;
    //输出缓冲区,保存着等待发送给其他节点的消息( message )。
    sds sndbuf;
    //输入缓冲区,保存着从其他节点接收到的消息。
    sds rcvbuf ;
    //与这个连接相关联的节点,如果没有的话就为NULL
    struct clusterNode *node;
} clusterLink;

可以发现,集群中的每个节点都保存在和其他节点的数据,也就是集群中的每个节点都是互知的。此外,每个节点还保存这一个clusterstate结构,用来记录当前视角下集群的状态。

typedef struct clusterState {
    //指向当前节点的指针
    clusterNode *myself;
    //集群当前的配置纪元,用于实现故障转移
    uint64_t currentEpoch;
    //集群当前的状态:是在线还是下线
    int state;
    //集群中至少处理着一个槽的节点的数量(下一节谈)
    int size;
    //集群节点名单(包括myself节点)
    //键为节点名字,值为节点对应的clusterNode结构
    dict *nodes;
    ...
} clusterState;

clusterState中的nodes属性也保存着集群中每个节点的信息。
image.png
CLUSTER MEET命令的实现
通过向节点发送CLUSTER MEET命令,让目标节点加入集群,进行三次握手,执行过程如下:

  1. 客户端发送该命令给节点A,节点A会创建一个节点B的clusterNode结构,添加到clusterState.nodes中。
  2. 解析IP地址和端口号,向节点B发送MEET消息。
  3. 同理,节点B收到后,会为A创建clusterNode结构并添加到nodes。
  4. 节点B向A发送PONG消息。
  5. 节点A收到后向B发送一条PING消息。
  6. 节点B收到后知道A成功感知到B,握手完成

image.png
之后,节点A会将节点B的信息通过Gossip协议传播给集群中其他节点,让其他节点也与节点B进行握手。

11.2槽指派

Redis通过分片方式保存键值对,集群的整个数据库被分为16384(214个槽(slot),数据库的每个键都属于某一个槽,每个节点可处理0~16384个槽。当集群中的每个槽都有节点管理时,集群处于上线状态;有一个槽没有节点管理,集群处于下线状态。
发送CLUSTER ADDSLOTS <slot> [slot...]命令,可以将槽委派给某个节点负责。

记录节点的槽指派信息
clusterNode结构的slots属性和numslot属性记录了节点负责哪些槽以及总共负责多少槽。

struct clusterNode{
    //二进制位数组,长度为16384/8=2048字节,
    //每个索引8位,根据0和1判断槽是否被该节点负责
    unsigned char slot[16384/8];
    //负责处理的槽数量
    int numslots;
    ...
}

slots属性是一个二进制数组,长度为2048字节也就是16384位。如果节点负责管理那个槽,那么数组中的对应的那一位值为1。
image.png
如上图,该节点负责管理0-7这8个槽。检查节点是否管理某个槽的时间复杂度是O(1)。

传播节点的槽指派信息
每个节点都会把自己的slots数组数据通过消息发送给其他节点,其他节点接收到消息后就会更新clusterState结构中nodes字典中对应的clusterNode结构。
因此,集群中的每个节点都知道其他节点负责的槽范围。

记录集群所有槽的指派信息
clusterState结构中的slots数组维护了集群中所有槽的指派信息

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

如果slot[i]指向空,说明该槽没有被节点管理;如果指向具体某个clusterNode的指针,说明由这个节点管理。
clusterState.slots数组记录集群中所有槽的指派信息,用于快速查找某个槽是否被指派或指派给了哪个节点,是从槽的角度;
clusterNode.slots数组记录当前节点负责槽的信息,用于查找当前节点管理了哪些槽,是从节点的角度。
两者缺少任意一个,都导致在实现相应功能时要执行遍历操作。

CLUSTER ADDSLOTS命令的实现
cluster addslots <slot>用来指派槽给节点负责的,接收该命令后,首先会遍历所有传入的槽是否都是未指派,如果有一个被指派了就报错。如果都未指派,将这些槽委派给当前节点,更新clusterState.slots数组指向当前节点的clusterNode;然后将clusterNode.slots数组中对应的索引二进制位设置为1。最后,发送消息告诉集群中其他节点,自己负责这些槽。
image.png

11.3在集群中执行命令

当客户端对节点发送与数据库键有关的命令时,接收命令的节点会计算属于哪个槽,检查这个槽是否指派给自己(根据CRC-16(key)&16383的结果来确定槽号i,再根据clusterState.slots[i]是否指向当前节点的clusterNode判断是否自己负责的)。如果不是指派给自己的,将clusterState.slots[i]指向节点的IP和端口通过MOVED错误(move :)向客户端返回,指引客户端指向正确的节点并再次发送命令。
image.png
集群模式的客户端不会打印move错误,而是根据move错误自动转向,打印转向信息。单机模式的客户端会直接打印move错误。
image.png
节点数据库的实现
节点对数据的存储和单机Redis的实现是一样的,只不过节点只能使用0号库。此外,节点在clusterState中维护了一个slots_to_keys跳跃表关联槽号和键。分数是槽号,节点就是键。当节点往数据库添加新键时,节点就会在slots_to_keys中进行
关联
,反之则删除关联。
image.png

11.4重新分片

Redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点。
重新分片时集群不下线,源节点和目标节点都可以继续处理命令请求。
重新分片通过集群管理软件redis-trib执行,步骤如下:

  1. redis-trib对目标节点发送CLUSTER SETSLOT <slot> IMPORTING <source_id>命令,让目标节点准备好从源节点导入键值对。
  2. redis-trib对源节点发送CLUSTER SETSLOT <slot> MIGRATING <target_id> 命令,让源节点准备好转移键值对。
  3. redis-trib 向源节点发送CLUSTER GETKEYSINSLOT <slot> <count>命令, 获得count个属于槽slot的键值对的键名( key name ),这实际上就是个批量迁移的意思。
  4. 对于步骤3获得的每个键名,redis-trib都向源节点发送一个MIGRATE <target_ip> <target_ port> <key_name> 0 <timeout>命令,将被选中的键原子的从源节点迁移至目标节点。
  5. 重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点。
  6. redis-trib向集群中的任意一个节点发送CLUSTER SETSLOT <slot> NODE <target_ id>命令,将槽slot指派给目标节点,通过消息发送至整个集群,让所有节点感知。

image.png

11.5ASK错误

在重新分片期间,当客户端向源节点发送与数据库键相关的命令,可能存在被迁移槽中的一部分键在源节点一部分键在目标节点的情况,因此,源节点会先查自己有没有,有就返回;没有则返回ASK错误,指引客户端向正在导入槽的目标节点发送命令。这个命令和MOVED类似,不会直接打印错误。
image.png
CLUSTER SETSLOT IMPORTING的实现
clusterState中维护了一个clusterState.importing_slots_from数组,记录当前节点正在从其他节点导入的槽

typedef struct clusterState{
    clusterNode *importing_slots_from[16384];
    ...
}

如果该数组某个索引指向一个cluserNode节点,那么表示该节点此时正作为重新分片中的目标节点,且源节点就是索引指向的那个节点。

CLUSTER SETSLOT MIGRATING的实现
clusterState中维护了一个migrating_slot_to数组,记录了当前节点正在迁移至其他节点的槽

typedef struct clusterState{
    clusterNode *migrating_slots_to[16384];
}

如果该数组某个索引指向一个cluserNode节点,那么表示当前节点正作为重新分片中的源节点,且目标节点就是索引指向的那个节点。

ASKING命令
客户端接收到ASK错误后,根据IP和端口,转向目标节点,然后先向目标节点发送ASKING命令,再重新发送要执行的命令。
这个命令的唯一作用就是打开发送该命令客户端的REDIS_ASKING标识。有了这个标识后,节点会为正在导入的键执行命令。这个标识是一次性的,如果再对刚才的key执行相关操作,该节点会返回MOVED错误(因为重分片未结束,它不是负责该槽的节点)。
image.png
ASK错误与MOVED错误的区别
这两个错误都会让客户端转向:

  • MOVED错误代表槽的负责权已经从一个节点到了另一个节点
  • ASK错误只是两个节点再迁移槽过程中使用的临时措施

11.6复制与故障转移

Redis集群中的节点分主节点和从节点,主节点用于处理槽,从节点复制于某个主节点,并在被复制节点下线时,代替下线主节点继续处理命令请求。

设置从节点
向节点发送命令CLUSTER REPLICATE <node_id>可以让接收命令的节点成为指定节点的从节点并对主节点开始复制。主要过程是:

  1. 接收命令节点在clusterState.node字典中找到node_id对应节点的clusterNode,然后将clusterState.myself.slaveof指向这个节点。
  2. 修改clusterState.myself.flags属性,关闭REDIS_NODE_MASTER标识,打开REDIS_NODE_SLAVE标识,标识该节点成为从节点。
  3. 调用复制代码,对主节点复制。

当节点成为从节点并开始复制时,这个信息会通过消息发送给集群中其他节点。

故障检测
集群中每个节点都会定期向其他节点发送PING消息,如果没有在规定时间返回PONG消息,就会被标记位疑似下线。集群中各个节点会互相发送消息来交换各个节点的状态,当一个主节点A通过消息得知主节点B认为主节点C进入疑似下线状态,A会在自己的clusterState.nodes中C对应的clusterNode结构的fail_reports链表添加B的下线报告
链表中每个元素都由clusterNodeFailReport组成,其实这个链表就是认为该节点下线的名单:

struct clusterNodeFailReport{
    //报告目标节点已经下线的节点
    struct clusterNode *node;
    //最后一次从node节点收到下线报告的时间
    //程序使用这个时间戳来检查下线报告是否过期
    // (与当前时间相差太久的下线报告会被删除)
    mstime_t time;
} typedef clusterNodeFailReport;

比如主节点7001收到主节点7002和主节点7003认为主节点7000下线的通知后,会修改主节点7001的clusterState.Nodes字典中的7000节点对应的clusterNode结构的fail_reports链表
image.png

在一个集群中,半数以上负责处理槽的主节点将某个主节点报告为疑似下线后,这个主节点将被标记为已下线。并向集群广播一条关于该主节点FAIL的消息,所有收到消息的节点都会将其标记为已下线。

故障转移
当一个从发现主节点下线后,开始故障转移。具体步骤:

  1. 下线的主节点的所有从节点里面,会有一个从节点被选中。
  2. 被选中的从节点会执行SLAVEOF no one命令,成为新的主节点。
  3. 新的主节点会撤销并指派给自己对已下线主节点的槽指派。
  4. 新的主节点向集群广播一条PONG消息,让其他节点立即知道新的主节点。
  5. 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。

选举新的主节点
集群选举新主节点的具体过程:

  1. 通过集群的配置纪元确定是哪一次选举,它是一个自增计数器,初始值为0。
  2. 当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被加一。
  3. 集群里每个负责处理槽的主节点都有一次投票的机会,第一个向主节点要求投票的从节点将获得主节点的投票。
  4. 当从节点发现自己正在复制的主节点进入已下线状态时,从节点会向集群广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息并且具有投票权的主节点向这个从节点投票。
  5. 如果一个主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点。
  6. 每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自己收到消息的条数来统计自己获得多少主节点的支持。
  7. 如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1支持票时,这个从节点就会当选为新的主节点。
  8. 配置纪元确定每个具有投票权的主节点只能投一次,所以如果有N个主节点进行投票,那么具有大于等于N/2+1张支持票的从节点只会有一个,这确保了新的主节点只会有一个。
  9. 如果在一个配置纪元里没有从节点得到足够的票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。

cluster中的故障转移和选举与sentinel里的用的都是raft算法,所以大致流程都是一样的。

11.7消息

集群中节点主要通过发送消息来传递信息,主要有5种:

  • MEET消息:发送者接收到客户端的MEET消息时,发送者向接收者发送MEET消息,请求加入发送者所在集群。
  • PING消息:集群每个节点每隔一秒从已知节点随机选出5个节点,然后对最长时间没发PING消息的节点进行发送。除此之外,还会将最后一次收到PONG消息的节点中,如果时间超过配置的cluster-node-timeout选项的一半时,也会发送PING消息。
  • PONG消息:应答MEET或PING消息。还可以通过PONG,告诉其他节点,刷新该节点的相关信息。
  • FAIL消息:当某个主节点判断另一个主节点已经进入FAIL状态时,当前主节点会向集群广播一条关于已下线节点的FAIL消息。
  • PUBLISH消息:当节点接收到PUBLISH命令时,执行这个命令,并向集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令。

一条消息由消息头和消息正文组成。

关于redis的sentinel和cluster的使用及注意事项可参考:https://blog.csdn.net/angjunqiang/article/details/81190562
水平扩展实践:https://www.cnblogs.com/shihuc/p/8186370.html

十一、发布和订阅

redis的发布和订阅功能由publish、subscribe、psubscribe和unsubscribe等命令组成。psubscribe用来订阅模式,一个模式可以对应多个频道。假设某个客户端发布一条命令,效果如图。
image.png
redis把所有频道的订阅关系保存在服务器状态的pubsub_channels里,结构是字典,键为频道名,值为订阅频道客户端的链表

struct redisSServer{
    //.....
    
    //保存所有频道的订阅关系
    dict *pubsub_channels;
    
    ....
};

redis把所有模式的订阅关系保存在pubsub_patterns,结构是个链表

struct redisSServer{
    //.....
    
    //保存所有频道的订阅关系
    list *pubsub_patterns;
    
    ....
};

注:内容是从语雀上的学习笔记迁移过来的,主要参考自《Redis的设计与实现》有些参考来源已经无法追溯,侵权私删。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值