17.1 节点

启动节点

Redis服务器启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式

节点会继续使用redisServer结构来保存服务器的状态,使用redisClient结构来保存客户端的状态,至于那些集群模式下才会用到的数据结构,节点将它们保存到了cluster.h/clusterNode结构、cluster.h/clusterLink结构,以及cluster.h/clusterState结构里面

// 节点状态
struct clusterNode { // 创建节点的时间
mstime_t ctime; /* Node object creation time. */ // 节点的名字,由 40 个十六进制字符组成
// 例如 68eef66df23420a5862208ef5b1a7005b806f2ff
char name[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */ // 节点标识
// 使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
// 以及节点目前所处的状态(比如在线或者下线)。
int flags; /* REDIS_NODE_... */ // 节点当前的配置纪元,用于实现故障转移
uint64_t configEpoch; /* Last configEpoch observed for this node */ // 由这个节点负责处理的槽
// 一共有 REDIS_CLUSTER_SLOTS / 8 个字节长
// 每个字节的每个位记录了一个槽的保存状态
// 位的值为 1 表示槽正由本节点处理,值为 0 则表示槽并非本节点处理
// 比如 slots[0] 的第一个位保存了槽 0 的保存情况
// slots[0] 的第二个位保存了槽 1 的保存情况,以此类推
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 */ // 最后一次发送 PING 命令的时间
mstime_t ping_sent; /* Unix time we sent latest ping */ // 最后一次接收 PONG 回复的时间戳
mstime_t pong_received; /* Unix time we received the pong */ // 最后一次被设置为 FAIL 状态的时间
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. */ // 节点的 IP 地址
char ip[REDIS_IP_STR_LEN]; /* Latest known IP address of this node */ // 节点的端口号
int port; /* Latest known port of this node */ // 保存连接节点所需的有关信息
clusterLink *link; /* TCP/IP link with this node */ // 一个链表,记录了所有其他节点对该节点的下线报告
list *fail_reports; /* List of nodes signaling this as failing */ };
typedef struct clusterNode clusterNode; /* clusterLink encapsulates everything needed to talk with a remote node. */
// clusterLink 包含了与其他节点进行通讯所需的全部信息
typedef struct clusterLink { // 连接的创建时间
mstime_t ctime; /* Link creation time */ // TCP 套接字描述符
int fd; /* TCP socket file descriptor */ // 输出缓冲区,保存着等待发送给其他节点的消息(message)。
sds sndbuf; /* Packet send buffer */ // 输入缓冲区,保存着从其他节点接收到的消息。
sds rcvbuf; /* Packet reception buffer */ // 与这个连接相关联的节点,如果没有的话就为 NULL
struct clusterNode *node; /* Node related to this link if any, or NULL */ } clusterLink; // 集群状态,每个节点都保存着一个这样的状态,记录了它们眼中的集群的样子。
// 另外,虽然这个结构主要用于记录集群的属性,但是为了节约资源,
// 有些与节点有关的属性,比如 slots_to_keys 、 failover_auth_count
// 也被放到了这个结构里面。
typedef struct clusterState { // 指向当前节点的指针
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 */ // 集群节点名单(包括 myself 节点)
// 字典的键为节点的名字,字典的值为 clusterNode 结构
dict *nodes; /* Hash table of name -> clusterNode structures */ // 节点黑名单,用于 CLUSTER FORGET 命令
// 防止被 FORGET 的命令重新被添加到集群里面
// (不过现在似乎没有在使用的样子,已废弃?还是尚未实现?)
dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */ // 记录要从当前节点迁移到目标节点的槽,以及迁移的目标节点
// migrating_slots_to[i] = NULL 表示槽 i 未被迁移
// migrating_slots_to[i] = clusterNode_A 表示槽 i 要从本节点迁移至节点 A
clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS]; // 记录要从源节点迁移到本节点的槽,以及进行迁移的源节点
// importing_slots_from[i] = NULL 表示槽 i 未进行导入
// importing_slots_from[i] = clusterNode_A 表示正从节点 A 中导入槽 i
clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS]; // 负责处理各个槽的节点
// 例如 slots[i] = clusterNode_A 表示槽 i 由节点 A 处理
clusterNode *slots[REDIS_CLUSTER_SLOTS]; // 跳跃表,表中以槽作为分值,键作为成员,对槽进行有序排序
// 当需要对某些槽进行区间(range)操作时,这个跳跃表可以提供方便
// 具体操作定义在 db.c 里面
zskiplist *slots_to_keys; /* The following fields are used to take the slave state on elections. */
// 以下这些域被用于进行故障转移选举 // 上次执行选举或者下次执行选举的时间
mstime_t failover_auth_time; /* Time of previous or next election. */ // 节点获得的投票数量
int failover_auth_count; /* Number of votes received so far. */ // 如果值为 1 ,表示本节点已经向其他节点发送了投票请求
int failover_auth_sent; /* True if we already asked for votes. */ int failover_auth_rank; /* This slave rank for current auth request. */ uint64_t failover_auth_epoch; /* Epoch of the current election. */ /* Manual failover state in common. */
/* 共用的手动故障转移状态 */ // 手动故障转移执行的时间限制
mstime_t mf_end; /* Manual failover time limit (ms unixtime).
It is zero if there is no MF in progress. */
/* Manual failover state of master. */
/* 主服务器的手动故障转移状态 */
clusterNode *mf_slave; /* Slave performing the manual failover. */
/* Manual failover state of slave. */
/* 从服务器的手动故障转移状态 */
long long mf_master_offset; /* Master offset the slave needs to start MF
or zero if stil not received. */
// 指示手动故障转移是否可以开始的标志值
// 值为非 0 时表示各个主服务器可以开始投票
int mf_can_start; /* If non-zero signal that the manual failover
can start requesting masters vote. */ /* The followign fields are uesd by masters to take state on elections. */
/* 以下这些域由主服务器使用,用于记录选举时的状态 */ // 集群最后一次进行投票的纪元
uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */ // 在进入下个事件循环之前要做的事情,以各个 flag 来记录
int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */ // 通过 cluster 连接发送的消息数量
long long stats_bus_messages_sent; /* Num of msg sent via cluster bus. */ // 通过 cluster 接收到的消息数量
long long stats_bus_messages_received; /* Num of msg rcvd via cluster bus.*/ } clusterState;

CLUSTER MEET 命令的实现

17.2 槽指派

Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot)

数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽

当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)

记录节点的指派信息

struct clusterNode{
//... unsigned char slots[16384/8];// 用于刻画节点保存状态的位图,一共16384个位 int numclots; //...
};

传播节点的槽指派信息

记录集群中所有槽的指派信息

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

CLUSTER ADDSLOTS命令的实现

17.3 在集群中执行命令

计算键属于哪个槽

def slot_number(key):
return CRC16(key) : 16383

验证clusterState.clots[slot_number(key)]是否等于clusterState.myself

  • 如果相等,说明该槽点由本节点负责,直接执行key对应命令
  • 如果不相等,取出clusterState.clots[slot_number(key)]的clusterNode结构中的ip和port, 向客户端返回MOVED <slot> <ip> <port>错误,指引节点指向正在负责处理key的节点

MOVED 错误

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

节点数据库的实现

typedef struct clusterState{
//... // 跳跃表的分值为槽点值,跳跃表的键为键值对的键
zskiplist *slots_to_keys; //...
}

17.4 重新分片

17.5 ASK错误

17.6 复制与故障转移

设置从节点

向一个节点发送命令CLUSTER REPLICATE <note_id>

struct clusterNode{
//... // 如果这是一个从节点,那么指向主节点
struct clusterNode *slaveof; //...
};

故障检测

故障转移

选举新的主节点

17.7 消息

  • MEET消息,发送者接到客户端发送的CLUSTER MEET命令时,发送者会向接收者发送MEET消息,请求接收者加入发送者当前的集群
  • PING消息,集群的每个节点每隔一秒就会从已知节点列表选出5个节点,然后对这5个节点最长时间没有发送过PING消息的节点发送PING消息,对距离上次收到PONG消息时间超过cluster-node-timeout选项设置时长的一半的节点也会发送PING消息
  • PONG消息,向发送者确认这条MEET消息或者PING消息已到达,接收者会向发送者返回一条PONG消息,一个节点可以通过发送PONG消息通知其它节点更新对本节点的认知
  • FALL消息,当一个节点A判断另一个主节点B已经进入FALL状态时,节点A会向集群广播一条关于节点B的FALL消息,所有收到这条消息的节点都会立即将节点B标记为已下线
  • PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令

消息头

// 用来表示集群消息的结构(消息头,header)
typedef struct {
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
// 消息的长度(包括这个消息头的长度和消息正文的长度)
uint32_t totlen; /* Total length of this message */
uint16_t ver; /* Protocol version, currently set to 0. */
uint16_t notused0; /* 2 bytes not used. */ // 消息的类型
uint16_t type; /* Message type */ // 消息正文包含的节点信息数量
// 只在发送 MEET 、 PING 和 PONG 这三种 Gossip 协议消息时使用
uint16_t count; /* Only used for some kind of messages. */ // 消息发送者的配置纪元
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */ // 如果消息发送者是一个主节点,那么这里记录的是消息发送者的配置纪元
// 如果消息发送者是一个从节点,那么这里记录的是消息发送者正在复制的主节点的配置纪元
uint64_t configEpoch; /* The config epoch if it's a master, or the last
epoch advertised by its master if it is a
slave. */ // 节点的复制偏移量
uint64_t offset; /* Master replication offset if node is a master or
processed replication offset if node is a slave. */ // 消息发送者的名字(ID)
char sender[REDIS_CLUSTER_NAMELEN]; /* Name of the sender node */ // 消息发送者目前的槽指派信息
unsigned char myslots[REDIS_CLUSTER_SLOTS/8]; // 如果消息发送者是一个从节点,那么这里记录的是消息发送者正在复制的主节点的名字
// 如果消息发送者是一个主节点,那么这里记录的是 REDIS_NODE_NULL_NAME
// (一个 40 字节长,值全为 0 的字节数组)
char slaveof[REDIS_CLUSTER_NAMELEN]; char notused1[32]; /* 32 bytes reserved for future usage. */ // 消息发送者的端口号
uint16_t port; /* Sender TCP base port */ // 消息发送者的标识值
uint16_t flags; /* Sender node flags */ // 消息发送者所处集群的状态
unsigned char state; /* Cluster state from the POV of the sender */ // 消息标志
unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */ // 消息的正文(或者说,内容)
union clusterMsgData data; } clusterMsg;

MEET、PING、PONG 消息的实现

union clusterMsgData {

    /* PING, MEET and PONG */
struct {
/* Array of N clusterMsgDataGossip structures */
// 每条消息都包含两个 clusterMsgDataGossip 结构
clusterMsgDataGossip gossip[1];
} ping; /* FAIL */
struct {
clusterMsgDataFail about;
} fail; /* PUBLISH */
struct {
clusterMsgDataPublish msg;
} publish; /* UPDATE */
struct {
clusterMsgDataUpdate nodecfg;
} update; };

FAIL消息的实现

typedef struct {

    // 下线节点的名字
char nodename[REDIS_CLUSTER_NAMELEN]; } clusterMsgDataFail;

集群中的节点通过发送消息来将一个节点标记为下线的过程。

PUBLISH 消息的实现

当客户端向集群中的某个节点发送命令:

PUBLISH <channel> <message>

typedef struct {

    // 频道名长度
uint32_t channel_len; // 消息长度
uint32_t message_len; // 消息内容,格式为 频道名+消息
// bulk_data[0:channel_len-1] 为频道名
// bulk_data[channel_len:channel_len+message_len-1] 为消息
unsigned char bulk_data[8]; /* defined as 8 just for alignment concerns. */ } clusterMsgDataPublish;

17.8 重点回顾

【笔记】《Redis设计与实现》chapter17 集群的更多相关文章

  1. SpringBoot学习笔记(13)----使用Spring Session+redis实现一个简单的集群

    session集群的解决方案: 1.扩展指定server 利用Servlet容器提供的插件功能,自定义HttpSession的创建和管理策略,并通过配置的方式替换掉默认的策略.缺点:耦合Tomcat/ ...

  2. Redis哨兵、复制、集群的设计原理与区别

    一 前言 谈到Redis服务器的高可用,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制. 哨兵(Sentinel):可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转 ...

  3. Redis的高可用详解:Redis哨兵、复制、集群的设计原理,以及区别

    谈到Redis服务器的高可用,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制. 哨兵(Sentinel):可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能. ...

  4. Redis哨兵、复制、集群的设计原理,以及区别

    广西SEO:谈到Redis服务器的高可用,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制. **哨兵(Sentinel):**可以管理多个Redis服务器,它提供了监控,提醒以及自 ...

  5. 深入学习Redis(5):集群

    前言 在前面的文章中,已经介绍了Redis的几种高可用技术:持久化.主从复制和哨兵,但这些方案仍有不足,其中最主要的问题是存储能力受单机限制,以及无法实现写操作的负载均衡. Redis集群解决了上述问 ...

  6. redis主从架构,分片集群详解

    写在前面:这篇笔记有点长,如果你认真看完,收获会不少,如果你只是忘记了相关命令,请翻到末尾. redis的简单介绍: 一个提供多种数据类类型储存,整个系统都在内存中运行的, 定期通过异步的方式把数据刷 ...

  7. Redis之高可用、集群、云平台搭建

    原文:Redis之高可用.集群.云平台搭建 文章大纲 一.基础知识学习二.Redis常见的几种架构及优缺点总结三.Redis之Redis Sentinel(哨兵)实战四.Redis之Redis Clu ...

  8. 关于redis主从|哨兵|集群模式

    关于redis主从.哨兵.集群的介绍网上很多,这里就不赘述了. 一.主从 通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重 ...

  9. Redis 实战篇之搭建集群

    Redis 集群简介# Redis Cluster 即 Redis 集群,是 Redis 官方在 3.0 版本推出的一套分布式存储方案.完全去中心化,由多个节点组成,所有节点彼此互联.Redis 客户 ...

  10. Redis.之.环境搭建(集群)

    Redis.之.环境搭建(集群) 现有环境: /u01/app/ |- redis # 单机版 |- redis-3.2.12    # redis源件 所需软件:redis-3.0.0.gem -- ...

随机推荐

  1. Simple: SQLite3 中文结巴分词插件

    一年前开发 simple 分词器,实现了微信在两篇文章中描述的,基于 SQLite 支持中文和拼音的搜索方案.具体背景参见这篇文章.项目发布后受到了一些朋友的关注,后续也发布了一些改进,提升了项目易用 ...

  2. TERSUS无代码开发(笔记02)-简单实例加法

    简单实例加法 1.用户端元件(显示元件)(40个) 图标 英文名称 元件名称 使用说明 服务器端 客户端 Pane 显示块 是一个显示块,是HTML的div标签   √ Row 行 行元件中的显示元件 ...

  3. 微信小程序(二十)-UI组件(Vant Weapp)-02使用

    1.按钮使用 https://vant-contrib.gitee.io/vant-weapp/#/button 1.全局引入,在app.json中引入组件 "usingComponents ...

  4. JS判断对象是否包含某个属性

    1.使用hasOwnProperty()判断 hasOwnProperty方法的参数就是要判断的属性名称,当对象的属性存在时返回true,否则返回false. var obj = { name:'ja ...

  5. Spring-04 Bean的自动装配

    Spring-04 Bean的自动装配 Bean的自动装配 1.自动装配说明 自动装配是使用spring满足bean依赖的一种方法. spring会在应用上下文中为某个bean寻找其依赖的bean. ...

  6. 后端程序员之路 57、go json

    go自带json处理库,位于encoding/json,里面的test很具参考意义,特别是example_test.go json - The Go Programming Languagehttps ...

  7. 微信小程序onReachBottom第二次失效

    当整个页面就是一个view包着一个轮播.一个横向scroll-view和一个纵向scroll-view onReachBottom方法只执行一次 解决方法:

  8. XUPT-D

    /*     泰泰学长又来玩数字了,泰泰学长想让你帮他求1-n的和,但是这次的求和可不是简单的1+2+...+n. 这次的求和是这样的,如果加到一个数字是2的指数倍,那就不加,反而减掉这个数.    ...

  9. linux安装mysql8.0

    linux 上安装mysql8.0 mysql版本8.0.16 MySQL Community 操作系统centos7 准备工作: mysql8.0 rpm文件 安装步骤: 1. 下载mysql的re ...

  10. Linux内核模块驱动加载与dmesg调试

    因为近期用到了Linux内核的相关知识,下面随笔将给出内核模块的编写记录,供大家参考. 1.运行环境 Ubuntu 版本:20.04 Linux内核版本:5.4.0-42-generic gcc版本: ...