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. ubuntu ARM换国内源和国内源安装ROS

    ubuntu arm换国内源: https://www.cnblogs.com/yongy1030/p/10315569.html 国内源安装ROS: https://blog.csdn.net/ch ...

  2. 04、数组与Arrays工具类

    目录 前言 一.一维数组 基本认识 内存空间 二.二维数组 基本认识 三.工具类Arrays 前言 去年四月份大一下半学期正式开始学习Java,一路从java基础.数据库.jdbc.javaweb.s ...

  3. Python学习笔记_类

    class Animal(object): # 定义父类animal def __init__(self,name,sound): # 初始化属性 name sound self.name = nam ...

  4. Spark和Spring整合处理离线数据

    如果你比较熟悉JavaWeb应用开发,那么对Spring框架一定不陌生,并且JavaWeb通常是基于SSM搭起的架构,主要用Java语言开发.但是开发Spark程序,Scala语言往往必不可少. 众所 ...

  5. ctf.show_web13(文件上传之.user.ini)

    这是一道文件上传题,先二话不说丢个图片码,显示为 先考虑文件太小,用burp抓包,添加了一堆无用的东西后显示仍然是error file zise,直到上传正常图片依旧如此,考虑文件太大.将一句话木马修 ...

  6. Spring中的依赖查找和依赖注入

    作者:Grey 原文地址: 语雀 博客园 依赖查找 Spring IoC 依赖查找分为以下几种方式 根据 Bean 名称查找 实时查找 延迟查找 根据 Bean 类型查找 单个 Bean 对象 集合 ...

  7. Power BI成功的背后

    Power BI成功的背后 魔力象限 又是一年Gartner数据分析与BI魔力象限报告的发布,Power BI毫无悬念的第一,并且拉开与其他产品的差距越来越大.在Power BI dataflows( ...

  8. HDOJ-1754(线段树+单点更新)

    I Hate It HDOJ-1754 这道题是线段树简单的入门题,只是简单考察了线段树的基本使用,建树等操作. 这里需要注意的是输入要不使用scanf要不使用快速输入. 这里的maxs数组需要开大一 ...

  9. 使用jsoup十分钟内掌握爬虫技术

    对,就是十分钟,没有接触过爬虫的你,肯定一脸懵逼,感觉好高深的样子,一开始我也有点懵,但用了以后发现还是很简单的,java爬虫框架有很多,让我有种选择困难症,通过权衡比较还是感觉jsoup比较好用些, ...

  10. Azure Front Door(一)为基于.net core 开发的Azure App Service 提供流量转发

    一,引言 之前我们讲解到使用 Azure Traffic Manager.Azure LoadBalancer.Azure Application Gateway,作为项目的负载均衡器来分发流量,转发 ...