《Redis设计与实现》读书笔记(二十八) ——Redis集群节点结构与槽分配

《Redis设计与实现》读书笔记(二十八) ——Redis集群节点结构与槽分配

(原创内容,转载请注明来源,谢谢)

一、概述

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

二、集群的节点

1、节点组成

一个redis集群由多个节点组成,每个节点是一个运行在集群模式下的redis服务器。集群还没建立好时,每个节点可以看成是一个独立的集群,将各个节点联系起来,就会形成一个真正有效的集群。

集群的命令是,clustermeet <ip> <port>,向一个节点node发送此命令发送给另一节点,可以让节点node与发送的ip:port进行一次握手,成功后node将记录该ip与port,添加到集群中。

clusternode命令,可以查看当前节点的集群。

2、启动节点

启动节点的时候,服务器会根据redis配置文件中的cluster-enabled选项,来决定该服务器是否开启集群模式。

由于节点也是一个redis服务器,因此其会使用redis服务器的各种特性,包括用文件事件来处理命令和回复、执行serverCron定时函数、键值对保存的方式、rdb与aof持久化、发布订阅、复制、lua脚本环境与执行lua脚本过程、用redisServer保存服务器状态、redisClient保存客户端状态等。

但是,集群的节点也有一些特殊性,包括特有的定时函数clusterCron(用于节点的其他数据发送gossip消息,检查节点是否断线,检查是否需要对下线节点进行故障转移等),另外还有集群节点特殊的数据结构,保存在cluster.h文件中,包括clusterNode、clusterLink、clusterState等。

三、集群节点数据结构

1、clusterNode结构

该结构保存节点的当前状态,包括名字、ip地址、创建时间、配置纪元、端口号等。集群里的每个节点,都会建立一个clusterNode来记录自身状态,并为集群中的其他节点(包括主从节点)建立相应的clusterNode结构。

主要信息如下:

         struct clusterNode{
         mstime_t ctime;//节点创建的时间
         char name[REDIS_CLUSTER_NAMELEN];//节点名称,由40位16进制字符组成
         int flags;//节点标识,标记节点主从、是否下线等状态
         uint64_tconfigEpoch;//节点创建纪元,用于故障转移
         char ip[REDIS_IP_STR_LEN];//节点ip地址
         int port;//节点端口号
         clusterLink *link;//保存节点所需的有关信息
         //….其他内容省略
}

2、clusterLink结构

该结构是保存在clusterNode中的一个属性,其用于保存节点所需有关信息,如套接字描述符、输入缓冲区和输出区缓冲等。

         typedef struct clusterLink{
         mstime_t ctime;//节点连接的创建时间
         int fd;//套接字描述符
         sds sndbuf;//输出缓冲区,保存等待发送给其他节点的信息
         sds rcvbuf;//输入缓冲区,保存从其他节点接收到的的信息
         struct clusterNode*node;//与这个连接相关的节点,没有就是null
}

redisClient和clusterLink结构都有套接字和相应的输入和输出缓冲区,但是区别在于redisClient是用于连接客户端,clusterLink用于连接其他节点。

3、clusterState结构

每个节点都保存一个这个结构,记录当前节点视角下,集群目前所处的状态,例如集群是否在线、当前有几个节点、当前配置纪元等。

         typedef struct clusterState{
         clusterNode  *myself;//指向当前节点的指针
         uint64_tcurrentEpoch;//集群当前配置纪元,用于故障转移
         int state;//集群当前状态,是否在线
         int size;//集群中至少处理一个槽的节点数量
         dict *nodes;//集群节点名单,包括myself节点,键是节点名称,值是对应的cluster Node结构
         //….其他信息
}clusterState;

4、节点结构

三个节点组成的集群,如下图所示:

其中,每个节点都会有一个这样的结构,区别在于myself指针都是指向各自节点自己。

4、cluster meet实现

cluster meet <ip> <port>,对节点a发节点b的ip,则将节点b加入到节点a的集群中,同时节点b也会将jieda加入到集群中。流程如下:

1)节点a为节点b创建一个clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面。

2)节点a根据ip和端口号,给节点b发送一条meet消息。

3)b会为a创建一个clusterNode,也添加到b自己的clusterState.nodes。

4)b向a回复pong信息。

5)a收到后,会再给b发送一个ping信息。

6)b接收到a的ping信息后,握手完成。

握手流程如下:

7)之后,节点a会将节点b的信息,以gossip协议传播给a当前集群中的其他节点,这样其他节点也会与b进行上述操作。

四、槽(slot)指派

1、概述

redis集群通过分片的方式,来保存键值对。集群的整个数据库被分为16384个槽,数据库中的每个键,都属于16384个槽中的一个,集群每个节点可以处理0~16384个槽。

当集群中,每一个槽都有节点在处理时,则这个集群是上线(ok)的状态;任意一个槽没有节点处理,则该集群下线(fail)。

采用命令cluster info,可以查看当前集群的状态,ok是上线,fail是下线。

通过向节点发送clusteraddslots <slot1> [slot2 slot3 ….]命令,可以将槽指派给节点。槽是用数字从0~16383进行编号的。

在哪个节点输入clusteraddslots,则对该节点指派槽。

2、记录节点指派信息

节点指派的槽的信息,记录在clusterNode结构体中:

         struct clusterNode{
         //….其他信息
         unsigned char slots[16384/8];//二进制数组
         int numslots;//节点处理的槽数量
         };

slots是一个二进制位数组,长度是2048个字节,共包含16384个二进制位。每一个下标代表8个槽,用二进制位表示。如果节点负责某个槽,则数组下标对应的二进制位的相应位置的值是1,否则是0。

例如,下图中的数组,表示节点负责的槽是1、3、5、8、9、10这几个。

使用二进制的方式,目的是便于获取、修改节点负责的槽,因为时间复杂度都是O(1)。

3、传播节点槽指派信息

节点被分配了槽,不仅会记录在节点自身的clusterNode结构体,还会将信息传播给集群的其他节点。

节点a收到节点b的槽分配信息,会从自身记录节点b信息clusterNode的结构中,相应的属性slots与numslots记录槽的位置与槽的数量。

4、记录集群所有槽指派信息

指派信息记录在clusterState结构体中:

typedef stuct clusterState{
//….其他信息
clusterNode *slots[16384];
}clusterState;

这个结构体中,数组每个下标表示一个槽,下标的值都是指针,指向负责该槽的节点。如果某个数组下标是null,表示目前没有节点负责该槽。

redis的设计非常巧妙,该slots属性可以快速找到每个槽对于的负责的节点,而节点内部clusterNode结构的slots,可以快速查找、改变某个节点负责的槽,且获取某个节点负责的全部槽的速度比从clusterState的slots中快得多。

5、槽指派的实现

槽指派之前,会先检查槽是否已经有节点负责,如果一个或以上的槽已经有节点负责,则停止指派,并且报错。

如果所有槽都没有节点负责,则修改clusterState的slots数组,将每个槽下标的值指向该节点的clusterNode结构;并修改该clusterNode的slots数组,将槽对应的二进制位置设置成1。

例如,执行命令clusteraddslots 1 2,节点变化如下:

完成上述命令写入后,节点会发消息通知集群的其他节点。

—written by linhxx 2017.09.15

原文发布于微信公众号 - 决胜机器学习(phpthinker)

原文发表时间:2017-09-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏猿人谷

进程控制实验--fork()

进程的控制 实验目的 1、掌握进程另外的创建方法 2、熟悉进程的睡眠、同步、撤消等进程控制方法 实验内容 1、用fork( )创建一个进程,再调用exec( )...

1748
来自专栏前端小叙

vue-cli打包之后的项目在nginx的部署

vue-cli执行 npm run build 进行打包,生成dist文件夹,把该文件夹下的文件直接复制到nginx服务器目录下,就可打开项目,但是只有首页是可...

3468
来自专栏linux运维学习

linux学习第二十七篇:使用w查看系统负载,vmstat,top,sar,nload命令

使用w查看系统负载 w/uptime的命令结果是一样的 ? 1. 第一行从左面开始显示的信息依次为:时间,系统运行时间,登录用户数,平均负载。 ...

1777
来自专栏程序员互动联盟

【记忆卡片】linux网络命令

Linux网络命令是我们比较常用的命令,重要性和使用频度都很高。Linux下网卡命名规律:eth0,eth1。第一块以太网卡,第二块。lo为环回接口,它的IP地...

2954
来自专栏SDNLAB

LINC switch系列之配置与运行

前言: LINC switch是一个由flowforwarding. org主导开发的一款基于Apache2.0协议开源的Openflow交换机软件。本文在安装...

3134
来自专栏用户2442861的专栏

Redis 起步

http://www.cnblogs.com/shanyou/archive/2012/01/28/2330451.html

552
来自专栏张戈的专栏

Linux系统监控、诊断工具之top命令详解

暂时没有写作灵感,就整理一些 Linux 基础知识好了,方便自己查阅,同时也是温故而知新嘛~! 在张戈博客,同样很有用的知识性博文还有以下几篇,也许你也会比较感...

3909
来自专栏性能与架构

为 Redis 添加 JSON 数据类型

1. 简介 Redis 本身有比较丰富的数据类型,例如 String、Hash、Set、List JSON 是我们常用的数据类型,当我们需要在 Redis 中...

3466
来自专栏邹立巍的专栏

Linux进程间通信:共享内存 (下)

使用文件或管道进行进程间通信会有很多局限性,比如效率问题以及数据处理使用文件描述符而不如内存地址访问方便,于是多个进程以共享内存的方式进行通信就成了很自然要实现...

3770
来自专栏前端儿

Web 后端--PHP 与数据库的交互

         用 PHP  操作 MySQL ,实现数据的交换,还要多练练....

511

扫描关注云+社区