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. django学习-20.python3中的特殊方法【__str__】的作用

    目录结构 1.前言 2.[__str__]特殊方法的具体使用 2.1.当使用print打印一个类被实例化后生成的对象的时候,若类里有定义了[__str__]特殊方法,是打印出这样的数据:[__str_ ...

  2. 【转】【机器人学:运动规划】OMPL开源运动规划库的安装和demo

    https://blog.csdn.net/gpeng832/article/details/73736225

  3. UDP编程详解

    目录 报文格式 通信过程 UDP客户端流程 UDP客户端编码 UDP服务器流程 UDP服务器编码 参考文献 UDP与TCP的不同之处是:他的通信不需要建立连接的过程.中文名称用户数据报协议.时OSI参 ...

  4. python实现斑马打印机网络打印

    最近一个礼拜调研了下斑马打印机怎样实现网络打印. 缘起: 之前实现打印方式是直接使用USB接口连接PC,使用串口通讯提供一套打印服务,在系统界面配置相关参数,即可调用打印服务: 后来业务需求变化,现场 ...

  5. pdf转换成文本解决格式不统一问题

    pdf转换成文本解决格式不统一问题 懒得调OCR服务了,所以快速解决的方法是: pdf转png:https://pdf2png.com/zh/ png转统一格式pdf:adobe acrobat自带增 ...

  6. OLAP分析

    OLAP分析 1 视频教程 视频教程 如果对资源下载.分析操作有疑问,直接跟着视频做一遍即可. 2 数据集合说明 FoodMart,其为一家食品连锁店经营产生的数据存放的数据库,包括销售数据.库存数据 ...

  7. 【死磕JVM】五年 整整五年了 该知道JVM加载机制了!

    类加载 Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程 和那些编译时需要连接工作的语言不 ...

  8. Excel技巧—开始菜单之格式刷六大功能

    转: Excel技巧-开始菜单之格式刷六大功能 点赞再看,养成习惯:千里之行,始于足下. 微信搜索[亦心Excel]关注这个不一样的自媒体人. 本文 GitHub https://github.com ...

  9. MySQL之四 存储引擎

    1.介绍 存储引擎MySQL中的"文件系统" MySQL体系结构 InnoDB存储引擎介绍 My1SAM 和InnoDB区别  mysql MariaDB [(none)]> ...

  10. 184. 部门工资最高的员工 + join + in

    184. 部门工资最高的员工 LeetCode_MySql_184 题目描述 题解分析 1.首先需要使用group by找出工资最高的值 2. 然后考虑到最高工资的可能有多位,所以使用in语句找到所有 ...