《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 条评论
登录 后参与评论

相关文章

来自专栏C/C++基础

内存池介绍与经典内存池的实现

利用默认的内存管理函数new/delete或malloc/free在堆上分配和释放内存会有一些额外的开销。

681
来自专栏Golang语言社区

Go语言并发与并行学习笔记

Go语言的并发和并行 不知道你有没有注意到一个现象,还是这段代码,如果我跑在两个goroutines里面的话: var quit chan int = make...

3086
来自专栏云计算与大数据

Linux CPU 如何判断忙

摘录自:http://www.ruanyifeng.com/blog/2016/12/user_space_vs_kernel_space.html

892
来自专栏好好学java的技术栈

java大公司后端多线程面试题最强分享

抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

811
来自专栏iOS开发攻城狮的集散地

堆和栈

1475
来自专栏用户画像

3.1.3连续分配管理方式

连续分配方式,是指为一个用户程序分配一个连续的内存空间。它主要包括单一连续分配、固定分区分配和动态分区分配。

732
来自专栏同步博客

Memcache存储机制与指令汇总

  memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

622
来自专栏禹都一只猫博客

Linux 性能检测常用的 10 个基本命令

792
来自专栏解决发现

CPU占用率100%的解决方法

图:优化前(我的电脑是四核cpu,所以单线程无限无阻塞循环占用率不会达到100%)

1080
来自专栏北京马哥教育

MongoDB多纬度监控方法详解

一、mongostat工具方法 mongostat是mongdb自带的状态检测工具,在命令行下使用。它会间隔固定时间获取mongodb的当前运行状态,并输出。如...

4015

扫码关注云+社区