到 目前 为止,我们所学习的 Redis 都是 单机版 的,这也就意味着一旦我们所依赖的 Redis 服务宕机了,我们的主流程也会受到一定的影响,这当然是我们不能够接受的。
所以一开始我们的想法是:搞一台备用机。这样我们就可以在一台服务器出现问题的时候切换动态地到另一台去:
幸运的是,两个节点数据的同步我们可以使用 Redis 的 主从同步 功能帮助到我们,这样一来,有个备份,心里就踏实多了。
后来因为某种神秘力量,Redis 老会在莫名其妙的时间点出问题 (比如半夜 2 点),我总不能 24 小时时刻守在电脑旁边切换节点吧,于是另一个想法又开始了:给所有的节点找一个 "管家",自动帮我监听照顾节点的状态并切换:
这大概就是 Redis 哨兵 (Sentinel) 的简单理解啦。什么?管家宕机了怎么办?相较于有大量请求的 Redis 服务来说,管家宕机的概率就要小得多啦.. 如果真的宕机了,我们也可以直接切换成当前可用的节点保证可用..
好了,通过上面的一些解决方案我们对 Redis 的 稳定性 稍微有了一些底气了,但单台节点的计算能力始终有限,所谓人多力量大,如果我们把 多个节点组合 成 一个可用的工作节点,那就大大增加了 Redis 的 高可用、可扩展、分布式、容错 等特性:
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为 主节点(master),后者称为 从节点(slave)。且数据的复制是 单向 的,只能由主节点到从节点。Redis 主从复制支持 主从同步 和 从从同步 两种,后者是 Redis 后续版本新增的功能,以减轻主节点的同步负担。
在 Redis 中,用户可以通过执行 SLAVEOF
命令或者设置 slaveof
选项,让一个服务器去复制另一个服务器,以下三种方式是 完全等效 的:
slaveof <masterip> <masterport>
--slaveof <masterip> <masterport>
slaveof <masterip> <masterport>
,让该 Redis 实例成为从节点。需要注意的是:主从复制的开启,完全是在从节点发起的,不需要我们在主节点做任何事情。
在正确安装好 Redis 之后,我们可以使用 redis-server --port <port>
的方式指定创建两个不同端口的 Redis 实例,例如,下方我分别创建了一个 6379
和 6380
的两个 Redis 实例:
# 创建一个端口为 6379 的 Redis 实例
redis-server --port 6379
# 创建一个端口为 6380 的 Redis 实例
redis-server --port 6380
此时两个 Redis 节点启动后,都默认为 主节点。
我们在 6380
端口的节点中执行 slaveof
命令,使之变为从节点:
# 在 6380 端口的 Redis 实例中使用控制台
redis-cli -p 6380
# 成为本地 6379 端口实例的从节点
127.0.0.1:6380> SLAVEOF 127.0.0.1ø 6379
OK
下面我们来验证一下,主节点的数据是否会复制到从节点之中:
127.0.0.1:6380> GET mykey
(nil)
127.0.0.1:6379> SET mykey myvalue
OK
127.0.0.1:6380> GET mykey
"myvalue"
通过 slaveof <masterip> <masterport>
命令建立主从复制关系以后,可以通过 slaveof no one
断开。需要注意的是,从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。
从节点执行 slaveof no one
之后,从节点和主节点分别打印日志如下:、
# 从节点打印日志
61496:M 17 Mar 2020 08:10:22.749 # Connection with master lost.
61496:M 17 Mar 2020 08:10:22.749 * Caching the disconnected master state.
61496:M 17 Mar 2020 08:10:22.749 * Discarding previously cached master state.
61496:M 17 Mar 2020 08:10:22.749 * MASTER MODE enabled (user request from 'id=4 addr=127.0.0.1:55096 fd=8 name= age=1664 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=34 qbuf-free=32734 obl=0 oll=0 omem=0 events=r cmd=slaveof')
# 主节点打印日志
61467:M 17 Mar 2020 08:10:22.749 # Connection with replica 127.0.0.1:6380 lost.
为了节省篇幅,我把主要的步骤都 浓缩 在了上图中,其实也可以 简化成三个阶段:准备阶段-数据同步阶段-命令传播阶段。下面我们来进行一些必要的说明。
在上面的 快速体验 过程中,你会发现 slaveof
这个命令居然不需要验证?这意味着只要知道了 ip 和端口就可以随意拷贝服务器上的数据了?
那当然不能够了,我们可以通过在 主节点 配置 requirepass
来设置密码,这样就必须在 从节点 中对应配置好 masterauth
参数 (与主节点 requirepass
保持一致) 才能够进行正常复制了。
每次执行 SYNC
命令,主从服务器需要执行如下动作:
BGSAVE
命令来生成 RDB 文件,这个生成操作会 消耗 主服务器大量的 CPU、内存和磁盘 I/O 的资源;特别是当出现 断线重复制 的情况是时,为了让从服务器补足断线时确实的那一小部分数据,却要执行一次如此耗资源的 SYNC
命令,显然是不合理的。
所以在 Redis 2.8 中引入了 PSYNC
命令来代替 SYNC
,它具有两种模式:
部分复制的原理主要是靠主从节点分别维护一个 复制偏移量,有了这个偏移量之后断线重连之后一比较,之后就可以仅仅把从服务器断线之后确实的这部分数据给补回来了。
更多的详细内容可以参考下方 参考资料 3
上图 展示了一个典型的哨兵架构图,它由两部分组成,哨兵节点和数据节点:
在复制的基础上,哨兵实现了 自动化的故障恢复 功能,下方是官方对于哨兵功能的描述:
其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移。而配置提供者和通知功能,则需要在与客户端的交互中才能体现。
正确安装好 Redis 之后,我们去到 Redis 的安装目录 (mac 默认在 /usr/local/
),找到 redis.conf
文件复制三份分别命名为 redis-master.conf
/redis-slave1.conf
/redis-slave2.conf
,分别作为 1
个主节点和 2
个从节点的配置文件 (下图演示了我本机的 redis.conf
文件的位置)
打开可以看到这个 .conf
后缀的文件里面有很多说明的内容,全部删除然后分别改成下面的样子:
#redis-master.conf
port 6379
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"
#redis-slave1.conf
port 6380
daemonize yes
logfile "6380.log"
dbfilename "dump-6380.rdb"
slaveof 127.0.0.1 6379
#redis-slave2.conf
port 6381
daemonize yes
logfile "6381.log"
dbfilename "dump-6381.rdb"
slaveof 127.0.0.1 6379
然后我们可以执行 redis-server <config file path>
来根据配置文件启动不同的 Redis 实例,依次启动主从节点:
redis-server /usr/local/redis-5.0.3/redis-master.conf
redis-server /usr/local/redis-5.0.3/redis-slave1.conf
redis-server /usr/local/redis-5.0.3/redis-slave2.conf
节点启动后,我们执行 redis-cli
默认连接到我们端口为 6379
的主节点执行 info Replication
检查一下主从状态是否正常:(可以看到下方正确地显示了两个从节点)
按照上面同样的方法,我们给哨兵节点也创建三个配置文件。(哨兵节点本质上是特殊的 Redis 节点,所以配置几乎没什么差别,只是在端口上做区分就好)
# redis-sentinel-1.conf
port 26379
daemonize yes
logfile "26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2
# redis-sentinel-2.conf
port 26380
daemonize yes
logfile "26380.log"
sentinel monitor mymaster 127.0.0.1 6379 2
# redis-sentinel-3.conf
port 26381
daemonize yes
logfile "26381.log"
sentinel monitor mymaster 127.0.0.1 6379 2
其中,sentinel monitor mymaster 127.0.0.1 6379 2
配置的含义是:该哨兵节点监控 127.0.0.1:6379
这个主节点,该主节点的名称是 mymaster
,最后的 2
的含义与主节点的故障判定有关:至少需要 2
个哨兵节点同意,才能判定主节点故障并进行故障转移。
执行下方命令将哨兵节点启动起来:
redis-server /usr/local/redis-5.0.3/redis-sentinel-1.conf --sentinel
redis-server /usr/local/redis-5.0.3/redis-sentinel-2.conf --sentinel
redis-server /usr/local/redis-5.0.3/redis-sentinel-3.conf --sentinel
使用 redis-cil
工具连接哨兵节点,并执行 info Sentinel
命令来查看是否已经在监视主节点了:
# 连接端口为 26379 的 Redis 节点
➜ ~ redis-cli -p 26379
127.0.0.1:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3
此时你打开刚才写好的哨兵配置文件,你还会发现出现了一些变化:
首先,我们使用 kill -9
命令来杀掉主节点,同时 在哨兵节点中执行 info Sentinel
命令来观察故障节点的过程:
➜ ~ ps aux | grep 6379
longtao 74529 0.3 0.0 4346936 2132 ?? Ss 10:30上午 0:03.09 redis-server *:26379 [sentinel]
longtao 73541 0.2 0.0 4348072 2292 ?? Ss 10:18上午 0:04.79 redis-server *:6379
longtao 75521 0.0 0.0 4286728 728 s008 S+ 10:39上午 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn 6379
longtao 74836 0.0 0.0 4289844 944 s006 S+ 10:32上午 0:00.01 redis-cli -p 26379
➜ ~ kill -9 73541
如果 刚杀掉瞬间 在哨兵节点中执行 info
命令来查看,会发现主节点还没有切换过来,因为哨兵发现主节点故障并转移需要一段时间:
# 第一时间查看哨兵节点发现并未转移,还在 6379 端口
127.0.0.1:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3
一段时间之后你再执行 info
命令,查看,你就会发现主节点已经切换成了 6381
端口的从节点:
# 过一段时间之后在执行,发现已经切换了 6381 端口
127.0.0.1:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3
但同时还可以发现,哨兵节点认为新的主节点仍然有两个从节点 (上方 slaves=2),这是因为哨兵在将 6381
切换成主节点的同时,将 6379
节点置为其从节点。虽然 6379
从节点已经挂掉,但是由于 哨兵并不会对从节点进行客观下线,因此认为该从节点一直存在。当 6379
节点重新启动后,会自动变成 6381
节点的从节点。
另外,在故障转移的阶段,哨兵和主从节点的配置文件都会被改写:
slaveof
配置的变化,新的主节点没有了 slaveof
配置,其从节点则 slaveof
新的主节点。上面我们在 快速体验 中主要感受到了服务端自己对于当前主从节点的自动化治理,下面我们以 Java 代码为例,来演示一下客户端如何访问我们的哨兵系统:
public static void testSentinel() throws Exception {
String masterName = "mymaster";
Set<String> sentinels = new HashSet<>();
sentinels.add("127.0.0.1:26379");
sentinels.add("127.0.0.1:26380");
sentinels.add("127.0.0.1:26381");
// 初始化过程做了很多工作
JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels);
Jedis jedis = pool.getResource();
jedis.set("key1", "value1");
pool.close();
}
Jedis 客户端对哨兵提供了很好的支持。如上述代码所示,我们只需要向 Jedis 提供哨兵节点集合和 masterName
,构造 JedisSentinelPool
对象,然后便可以像使用普通 Redis 连接池一样来使用了:通过 pool.getResource()
获取连接,执行具体的命令。
在整个过程中,我们的代码不需要显式的指定主节点的地址,就可以连接到主节点;代码中对故障转移没有任何体现,就可以在哨兵完成故障转移后自动的切换主节点。之所以可以做到这一点,是因为在 JedisSentinelPool
的构造器中,进行了相关的工作;主要包括以下两点:
masterName
获得主节点的信息;该功能是通过调用哨兵节点的 sentinel get-master-addr-by-name
命令实现;故障转移操作的第一步 要做的就是在已下线主服务器属下的所有从服务器中,挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送 slaveof no one
命令,将这个从服务器转换为主服务器。但是这个从服务器是怎么样被挑选出来的呢?
简单来说 Sentinel 使用以下规则来选择新的主服务器:
上图 展示了 Redis Cluster 典型的架构图,集群中的每一个 Redis 节点都 互相两两相连,客户端任意 直连 到集群中的 任意一台,就可以对其他 Redis 节点进行 读写 的操作。
Redis 集群中内置了 16384
个哈希槽。当客户端连接到 Redis 集群之后,会同时得到一份关于这个 集群的配置信息,当客户端具体对某一个 key
值进行操作时,会计算出它的一个 Hash 值,然后把结果对 16384
求余数,这样每个 key
都会对应一个编号在 0-16383
之间的哈希槽,Redis 会根据节点数量 大致均等 的将哈希槽映射到不同的节点。
再结合集群的配置信息就能够知道这个 key
值应该存储在哪一个具体的 Redis 节点中,如果不属于自己管,那么就会使用一个特殊的 MOVED
命令来进行一个跳转,告诉客户端去连接这个节点以获取数据:
GET x
-MOVED 3999 127.0.0.1:6381
MOVED
指令第一个参数 3999
是 key
对应的槽位编号,后面是目标节点地址,MOVED
命令前面有一个减号,表示这是一个错误的消息。客户端在收到 MOVED
指令后,就立即纠正本地的 槽位映射表,那么下一次再访问 key
时就能够到正确的地方去获取了。
bgsave
和 bgrewriteaof
的 fork
操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……首先我们找一个地方创建一个名为 redis-cluster
的目录:
mkdir -p ~/Desktop/redis-cluster
然后按照上面的方法,创建六个配置文件,分别命名为:redis_7000.conf
/redis_7001.conf
.....redis_7005.conf
,然后根据不同的端口号修改对应的端口值就好了:
# 后台执行
daemonize yes
# 端口号
port 7000
# 为每一个集群节点指定一个 pid_file
pidfile ~/Desktop/redis-cluster/redis_7000.pid
# 启动集群模式
cluster-enabled yes
# 每一个集群节点都有一个配置文件,这个文件是不能手动编辑的。确保每一个集群节点的配置文件不通
cluster-config-file nodes-7000.conf
# 集群节点的超时时间,单位:ms,超时后集群会认为该节点失败
cluster-node-timeout 5000
# 最后将 appendonly 改成 yes(AOF 持久化)
appendonly yes
记得把对应上述配置文件中根端口对应的配置都修改掉 (port/ pidfile/ cluster-config-file)。
redis-server ~/Desktop/redis-cluster/redis_7000.conf
redis-server ~/Desktop/redis-cluster/redis_7001.conf
redis-server ~/Desktop/redis-cluster/redis_7002.conf
redis-server ~/Desktop/redis-cluster/redis_7003.conf
redis-server ~/Desktop/redis-cluster/redis_7004.conf
redis-server ~/Desktop/redis-cluster/redis_7005.conf
然后执行 ps -ef | grep redis
查看是否启动成功:
可以看到 6
个 Redis 节点都以集群的方式成功启动了,但是现在每个节点还处于独立的状态,也就是说它们每一个都各自成了一个集群,还没有互相联系起来,我们需要手动地把他们之间建立起联系。
执行下列命令:
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
--replicas 1
的意思是:我们希望为集群中的每个主节点创建一个从节点。观察控制台输出:
看到 [OK]
的信息之后,就表示集群已经搭建成功了,可以看到,这里我们正确地创建了三主三从的集群。
我们先使用 redic-cli
任意连接一个节点:
redis-cli -c -h 127.0.0.1 -p 7000
127.0.0.1:7000>
-c
表示集群模式;-h
指定 ip 地址;-p
指定端口。然后随便 set
一些值观察控制台输入:
127.0.0.1:7000> SET name wmyskxz
-> Redirected to slot [5798] located at 127.0.0.1:7001
OK
127.0.0.1:7001>
可以看到这里 Redis 自动帮我们进行了 Redirected
操作跳转到了 7001
这个实例上。
我们再使用 cluster info
(查看集群信息) 和 cluster nodes
(查看节点列表) 来分别看看:(任意节点输入均可)
127.0.0.1:7001> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:2
cluster_stats_messages_ping_sent:1365
cluster_stats_messages_pong_sent:1358
cluster_stats_messages_meet_sent:4
cluster_stats_messages_sent:2727
cluster_stats_messages_ping_received:1357
cluster_stats_messages_pong_received:1369
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:2727
127.0.0.1:7001> CLUSTER NODES
56a04742f36c6e84968cae871cd438935081e86f 127.0.0.1:7003@17003 slave 4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 0 1584428884000 4 connected
4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 127.0.0.1:7000@17000 master - 0 1584428884000 1 connected 0-5460
e2539c4398b8258d3f9ffa714bd778da107cb2cd 127.0.0.1:7005@17005 slave a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 0 1584428885222 6 connected
d31cd1f423ab1e1849cac01ae927e4b6950f55d9 127.0.0.1:7004@17004 slave 236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 0 1584428884209 5 connected
236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 127.0.0.1:7001@17001 myself,master - 0 1584428882000 2 connected 5461-10922
a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 127.0.0.1:7002@17002 master - 0 1584428884000 3 connected 10923-16383
127.0.0.1:7001>
哈希取余分区思路非常简单:计算 key
的 hash 值,然后对节点数量进行取余,从而决定数据映射到哪个节点上。
不过该方案最大的问题是,当新增或删减节点时,节点数量发生变化,系统中所有的数据都需要 重新计算映射关系,引发大规模数据迁移。
一致性哈希算法将 整个哈希值空间 组织成一个虚拟的圆环,范围是 [0 , 232-1],对于每一个数据,根据 key
计算 hash 值,确数据在环上的位置,然后从此位置沿顺时针行走,找到的第一台服务器就是其应该映射到的服务器:
与哈希取余分区相比,一致性哈希分区将 增减节点的影响限制在相邻节点。以上图为例,如果在 node1
和 node2
之间增加 node5
,则只有 node2
中的一部分数据会迁移到 node5
;如果去掉 node2
,则原 node2
中的数据只会迁移到 node4
中,只有 node4
会受影响。
一致性哈希分区的主要问题在于,当 节点数量较少 时,增加或删减节点,对单个节点的影响可能很大,造成数据的严重不平衡。还是以上图为例,如果去掉 node2
,node4
中的数据由总数据的 1/4
左右变为 1/2
左右,与其他节点相比负载过高。
该方案在 一致性哈希分区的基础上,引入了 虚拟节点 的概念。Redis 集群使用的便是该方案,其中的虚拟节点称为 槽(slot)。槽是介于数据和实际节点之间的虚拟概念,每个实际节点包含一定数量的槽,每个槽包含哈希值在一定范围内的数据。
在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽 解耦 了 数据和实际节点 之间的关系,增加或删除节点对系统的影响很小。仍以上图为例,系统中有 4
个实际节点,假设为其分配 16
个槽(0-15);
如果此时删除 node2
,只需要将槽 4-7 重新分配即可,例如槽 4-5 分配给 node1
,槽 6 分配给 node3
,槽 7 分配给 node4
;可以看出删除 node2
后,数据在其他节点的分布仍然较为均衡。
集群的建立离不开节点之间的通信,例如我们上访在 快速体验 中刚启动六个集群节点之后通过 redis-cli
命令帮助我们搭建起来了集群,实际上背后每个集群之间的两两连接是通过了 CLUSTER MEET <ip> <port>
命令发送 MEET
消息完成的,下面我们展开详细说说。
在 哨兵系统 中,节点分为 数据节点 和 哨兵节点:前者存储数据,后者实现额外的控制功能。在 集群 中,没有数据节点与非数据节点之分:所有的节点都存储数据,也都参与集群状态的维护。为此,集群中的每个节点,都提供了两个 TCP 端口:
7000
节点的集群端口为 17000
。集群端口只用于节点之间的通信,如搭建集群、增减节点、故障转移等操作时节点间的通信;不要使用客户端连接集群接口。为了保证集群可以正常工作,在配置防火墙时,要同时开启普通端口和集群端口。节点间通信,按照通信协议可以分为几种类型:单对单、广播、Gossip 协议等。重点是广播和 Gossip 的对比。
集群中的节点采用 固定频率(每秒10次) 的 定时任务 进行通信相关的工作:判断是否需要发送消息及消息类型、确定接收节点、发送消息等。如果集群状态发生了变化,如增减节点、槽状态变更,通过节点间的通信,所有节点会很快得知整个集群的状态,使集群收敛。
节点间发送的消息主要分为 5
种:meet 消息
、ping 消息
、pong 消息
、fail 消息
、publish 消息
。不同的消息类型,通信协议、发送的频率和时机、接收节点的选择等是不同的:
CLUSTER MEET
命令时,会向新加入的节点发送 MEET
消息,请求新节点加入到当前集群;新节点收到 MEET 消息后会回复一个 PONG
消息。PING
消息,接收者收到消息后会回复一个 PONG
消息。PING 消息的内容是自身节点和部分其他节点的状态信息,作用是彼此交换信息,以及检测节点是否在线。PING
消息使用 Gossip 协议发送,接收节点的选择兼顾了收敛速度和带宽成本,具体规则如下:(1)随机找 5 个节点,在其中选择最久没有通信的 1 个节点;(2)扫描节点列表,选择最近一次收到 PONG
消息时间大于 cluster_node_timeout / 2
的所有节点,防止这些节点长时间未更新。PONG
消息封装了自身状态数据。可以分为两种:第一种 是在接到 MEET/PING
消息后回复的 PONG
消息;第二种 是指节点向集群广播 PONG
消息,这样其他节点可以获知该节点的最新信息,例如故障恢复后新的主节点会广播 PONG
消息。FAIL
状态时,会向集群广播这一 FAIL
消息;接收节点会将这一 FAIL
消息保存起来,便于后续的判断。PUBLISH
命令后,会先执行该命令,然后向集群广播这一消息,接收节点也会执行该 PUBLISH
命令。节点需要专门的数据结构来存储集群的状态。所谓集群的状态,是一个比较大的概念,包括:集群是否处于上线状态、集群中有哪些节点、节点是否可达、节点的主从状态、槽的分布……
节点为了存储集群状态而提供的数据结构中,最关键的是 clusterNode
和 clusterState
结构:前者记录了一个节点的状态,后者记录了集群作为一个整体的状态。
clusterNode
结构保存了 一个节点的当前状态,包括创建时间、节点 id、ip 和端口号等。每个节点都会用一个 clusterNode
结构记录自己的状态,并为集群内所有其他节点都创建一个 clusterNode
结构来记录节点状态。
下面列举了 clusterNode
的部分字段,并说明了字段的含义和作用:
typedef struct clusterNode {
//节点创建时间
mstime_t ctime;
//节点id
char name[REDIS_CLUSTER_NAMELEN];
//节点的ip和端口号
char ip[REDIS_IP_STR_LEN];
int port;
//节点标识:整型,每个bit都代表了不同状态,如节点的主从状态、是否在线、是否在握手等
int flags;
//配置纪元:故障转移时起作用,类似于哨兵的配置纪元
uint64_t configEpoch;
//槽在该节点中的分布:占用16384/8个字节,16384个比特;每个比特对应一个槽:比特值为1,则该比特对应的槽在节点中;比特值为0,则该比特对应的槽不在节点中
unsigned char slots[16384/8];
//节点中槽的数量
int numslots;
…………
} clusterNode;
除了上述字段,clusterNode
还包含节点连接、主从复制、故障发现和转移需要的信息等。
clusterState
结构保存了在当前节点视角下,集群所处的状态。主要字段包括:
typedef struct clusterState {
//自身节点
clusterNode *myself;
//配置纪元
uint64_t currentEpoch;
//集群状态:在线还是下线
int state;
//集群中至少包含一个槽的节点数量
int size;
//哈希表,节点名称->clusterNode节点指针
dict *nodes;
//槽分布信息:数组的每个元素都是一个指向clusterNode结构的指针;如果槽还没有分配给任何节点,则为NULL
clusterNode *slots[16384];
…………
} clusterState;
除此之外,clusterState
还包括故障转移、槽迁移等需要的信息。
更多关于集群内容请自行阅读《Redis 设计与实现》,其中有更多细节方面的介绍 - http://redisbook.com/
- 本文已收录至我的 Github 程序员成长系列 【More Than Java】,学习,不止 Code,欢迎 star:https://github.com/wmyskxz/MoreThanJava
- 个人公众号 :wmyskxz,个人独立域名博客:wmyskxz.com,坚持原创输出,下方扫码关注,2020,与您共同成长!
非常感谢各位人才能 看到这里,如果觉得本篇文章写得不错,觉得 「我没有三颗心脏」有点东西 的话,求点赞,求关注,求分享,求留言!
创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。