提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- [1. 集群](#1. 集群)
-
- [1.1 基本介绍](#1.1 基本介绍)
- [1.2 哈希求余](#1.2 哈希求余)
- [1.3 ⼀致性哈希算法](#1.3 ⼀致性哈希算法)
- [1.4 哈希槽分区算法 (Redis 使⽤)](#1.4 哈希槽分区算法 (Redis 使⽤))
- [1.5 搭建集群环境](#1.5 搭建集群环境)
- [1.6 使用集群](#1.6 使用集群)
- [1.7 故障处理](#1.7 故障处理)
- [1.8 集群扩容](#1.8 集群扩容)
- 总结
前言
1. 集群
1.1 基本介绍

上述的 哨兵 模式, 提⾼了系统的可⽤性. 但是真正⽤来存储数据的还是 master 和 slave 节点. 所有的数据都需要存储在单个 master 和 slave 节点中.
如果数据量很⼤, 接近超出了 master / slave 所在机器的物理内存, 就可能出现严重问题了.
虽然硬件价格在不断降低, ⼀些中⼤⼚的服务器内存已经可以达到 TB 级别了, 但是 1TB 在当前这个 "⼤数据" 时代, 俨然不算什么, 有的时候我们确实需要更⼤的内存空间来保存更多的数据.
Redis 的集群就是在上述的思路之下, 引⼊多组 Master / Slave , 每⼀组 Master / Slave 存储数据全集的⼀部分, 从⽽构成⼀个更⼤的整体, 称为 Redis 集群 (Cluster).
假定整个数据全集是 1 TB, 引⼊三组 Master / Slave 来存储. 那么每⼀组机器只需要存储整个数据全集的 1/3 即可
然后每个机器还要有从节点才可以

在上述图中,
• Master1 和 Slave11 和 Slave12 保存的是同样的数据. 占总数据的 1/3
• Master2 和 Slave21 和 Slave22 保存的是同样的数据. 占总数据的 1/3
• Master3 和 Slave31 和 Slave32 保存的是同样的数据. 占总数据的 1/3
这三组机器存储的数据都是不同的.
每个 Slave 都是对应 Master 的备份(当 Master 挂了, 对应的 Slave 会补位成 Master).
每个红框部分都可以称为是⼀个 分⽚ (Sharding).
如果全量数据进⼀步增加, 只要再增加更多的分⽚, 即可解决.
1.2 哈希求余
设有 N 个分⽚, 使⽤ [0, N-1] 这样序号进⾏编号.
针对某个给定的 key, 先计算 hash 值, 再把得到的结果 % N, 得到的结果即为分⽚编号.
例如, N 为 3. 给定 key 为 hello, 对 hello 计算 hash 值(⽐如使⽤ md5 算法), 得到的结果为
bc4b2a76b9719d91 , 再把这个结果 % 3, 结果为 0, 那么就把 hello 这个 key 放到 0 号分⽚上.
当然, 实际⼯作中涉及到的系统, 计算 hash 的⽅式不⼀定是 md5, 但是思想是⼀致的.
md5就是一个计算哈希值的方案,计算出来的东西是一个数字,可能会有字母---》只是进制不同而已
特点:md5计算结果是定长的,md5计算结果是分散的,不同的字符串,甚至只有一两个字母不一样,生成出来的数字都是差别很大的,md5的计算结果是不可逆的,计算出数字很简单,但是给出md5值,不能得出源字符串,所以md5可以用来加密
md5的可逆----》只能可逆出简单的,因为把简单字符串的md5值存起来了,所以这只是暴力破解

后续如果要取某个 key 的记录, 也是针对 key 计算 hash , 再对 N 求余, 就可以找到对应的分⽚编号了.
优点: 简单⾼效, 数据分配均匀.
缺点: ⼀旦需要进⾏扩容, N 改变了, 原有的映射规则被破坏, 就需要让节点之间的数据相互传输, 重新排列, 以满⾜新的映射规则. 此时需要搬运的数据量是⽐较多的, 开销较⼤.N 为 3 的时候, [100, 120] 这 21 个 hash 值的分布 (此处假定计算出的 hash 值是⼀个简单的整数, ⽅便⾁眼观察)
当引⼊⼀个新的分⽚, N 从 3 => 4 时, ⼤量的 key 都需要重新映射. (某个key % 3 和 % 4 的结果不⼀样,就映射到不同机器上了).

如上图可以看到, 整个扩容⼀共 21 个 key, 只有 3 个 key 没有经过搬运, 其他的 key 都是搬运过的.
1.3 ⼀致性哈希算法
为了降低上述的搬运开销, 能够更⾼效扩容, 业界提出了 "⼀致性哈希算法".
key 映射到分⽚序号的过程不再是简单求余了, ⽽是改成以下过程:
第⼀步, 把 0 -> 2^32-1 这个数据空间, 映射到⼀个圆环上. 数据按照顺时针⽅向增⻓.

第⼆步, 假设当前存在三个分⽚, 就把分⽚放到圆环的某个位置上.

第三步, 假定有⼀个 key, 计算得到 hash 值 H, 那么这个 key 映射到哪个分⽚呢? 规则很简单, 就是从 H所在位置, 顺时针往下找, 找到的第⼀个分⽚, 即为该 key 所从属的分⽚

这就相当于, N 个分⽚的位置, 把整个圆环分成了 N 个管辖区间. Key 的 hash 值落在某个区间内, 就归对应区间管理

在这个情况下, 如果扩容⼀个分⽚, 如何处理呢?
原有分⽚在环上的位置不动, 只要在环上新安排⼀个分⽚位置即可


此时, 只需要把 0 号分⽚上的部分数据, 搬运给 3 号分⽚即可. 1 号分⽚和 2 号分⽚管理的区间都是不变的.
优点: ⼤ 降低了扩容时数据搬运的规模, 提⾼了扩容操作的效率.
缺点: 数据分配不均匀 (有的多有的少, 数据倾斜).
1.4 哈希槽分区算法 (Redis 使⽤)
为了解决上述问题 (搬运成本⾼ 和 数据分配不均匀), Redis cluster 引⼊了哈希槽 (hash slots) 算法.
bash
hash_slot = crc16(key) % 16384
其中 crc16 也是⼀种 hash 算法.
16384 其实是 16 * 1024, 也就是 2^14
相当于是把整个哈希值, 映射到 16384 个槽位上, 也就是 [0, 16383].
然后再把这些槽位⽐较均匀的分配给每个分⽚. 每个分⽚的节点都需要记录⾃⼰持有哪些分⽚.
假设当前有三个分⽚, ⼀种可能的分配⽅式:
• 0 号分⽚: [0, 5461], 共 5462 个槽位
• 1 号分⽚: [5462, 10923], 共 5462 个槽位
• 2 号分⽚: [10924, 16383], 共 5460 个槽位
这⾥的分⽚规则是很灵活的. 每个分⽚持有的槽位也不⼀定连续.
每个分⽚的节点使⽤ 位图 来表⽰⾃⼰持有哪些槽位. 对于 16384 个槽位来说, 需要 2048 个字节(2KB) ⼤⼩的内存空间表⽰.
如果需要进⾏扩容, ⽐如新增⼀个 3 号分⽚, 就可以针对原有的槽位进⾏重新分配.
⽐如可以把之前每个分⽚持有的槽位, 各拿出⼀点, 分给新分⽚.
⼀种可能的分配⽅式:
• 0 号分⽚: [0, 4095], 共 4096 个槽位
• 1 号分⽚: [5462, 9557], 共 4096 个槽位
• 2 号分⽚: [10924, 15019], 共 4096 个槽位
• 3 号分⽚: [4096, 5461] + [9558, 10923] + [15019, 16383], 共 4096 个槽位,只有这些被移动的槽位会进行搬运
我们在实际使⽤ Redis 集群分⽚的时候, 不需要⼿动指定哪些槽位分配给某个分⽚, 只需要告诉某个分⽚应该持有多少个槽位即可, Redis 会⾃动完成后续的槽位分配, 以及对应的 key 搬运的⼯作.
此处还有两个问题:
问题⼀: Redis 集群是最多有 16384 个分⽚吗?如果这么多分片---》几万台机器了
并⾮如此. 如果⼀个分⽚只有⼀个槽位, 这对于集群的数据均匀其实是难以保证的.
实际上 Redis 的作者建议集群分⽚数不应该超过 1000.
⽽且, 16000 这么⼤规模的集群, 本⾝的可⽤性也是⼀个⼤问题. ⼀个系统越复杂, 出现故障的概率是越⾼的
key是先映射到槽位,然后映射到分片

问题⼆: 为什么是 16384 个槽位?
Redis 作者的答案: https://github.com/antirez/redis/issues/2576
翻译过来⼤概意思是:
• 节点之间通过⼼跳包通信. ⼼跳包中包含了该节点持有哪些 slots槽位. 这个是使⽤位图这样的数据结构表⽰的. 表⽰ 16384 (16k) 个 slots, 需要的位图⼤⼩是 2KB. 如果给定的 slots 数更多了, ⽐如 65536个了, 此时就需要消耗更多的空间, 8 KB 位图表⽰了. 8 KB, 对于内存来说不算什么, 但是在频繁的⽹络⼼跳包中, 还是⼀个不⼩的开销的.
• 另⼀⽅⾯, Redis 集群⼀般不建议超过 1000 个分⽚. 所以 16k 对于最⼤ 1000 个分⽚来说是⾜够⽤的, 同时也会使对应的槽位配置位图体积不⾄于很⼤.
1.5 搭建集群环境
生成配置文件

注意!
此处我们先创建出 11 个 redis 节点. 其中前 9 个⽤来演⽰集群的搭建.
后两个⽤来演⽰集群扩容.
创建 redis-cluster ⽬录. 内部创建两个⽂件
1 redis-cluster/
2 ├── docker-compose.yml
3 └── generate.sh

generate.sh 内容如下
这个是shell脚本,就是把命令写在一个文件中,用来创建redis的配置文件
bash
for port in $(seq 1 9); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
# 注意 cluster-announce-ip 的值有变化.
for port in $(seq 10 11); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
$(seq 1 9)的意思就是生成1到9的序列
${port}表示一个变量
do 和done表示代码块的开始和结束
\是续行符
shell默认情况下,要求代码都必须是一行,所以用续行符
redis${port}就是一个字符串的拼接
cluster-enabled yes意思就是开启集群
cluster-config-file nodes.conf这个是指定集群的配置文件,这个文件不需要我们手动来写,这个是redis自动生成的,配置集群的信息
cluster-node-timeout 5000:多个节点交互联络超时时间,心跳包的超时时间
cluster-announce-ip 172.30.0.1${port}:这个是设置的redis节点自己的ip,就是docker容器的ip,外部的端口
cluster-announce-port 6379这个就是节点的ip,就是容器自己内部的ip
cluster-announce-bus-port 16379这个也是容器内部ip
配置说明:
• cluster-enabled yes 开启集群.
• cluster-config-file nodes.conf 集群节点⽣成的配置.
• cluster-node-timeout 5000 节点失联的超时时间.
• cluster-announce-ip 172.30.0.101 节点⾃⾝ ip.
• cluster-announce-port 6379 节点⾃⾝的业务端⼝.
• cluster-announce-bus-port 16379 节点⾃⾝的总线端⼝. 集群管理的信息交互是通过这个端⼝进⾏的

这些都可以在官网上面查,还可以自己去redis配置文件中查看配置

使用 Windows 自带的 WSL就可以执行.sh脚本了,因为wsl就是类似于linux的ubuntu
cmd直接输入wsl
bash
wsl
./generate.sh

编写 docker-compose.yml
先创建 networks, 并分配⽹段为 172.30.0.0/24
• 配置每个节点. 注意配置⽂件映射, 端⼝映射, 以及容器的 ip 地址. 设定成固定 ip ⽅便后续的观察和操作.
此处的端⼝映射不配置也可以, 配置的⽬的是为了可以通过宿主机 ip + 映射的端⼝进⾏访问. 通过 容器⾃⾝ ip:6379 的⽅式也可以访问.
bash
version: '3.7'
networks:
mynet:
ipam:
config:
- subnet: 172.30.0.0/24
services:
redis1:
image: 'redis:5.0.9'
container_name: redis1
restart: always
volumes:
- ./redis1/:/etc/redis/
ports:
- 6371:6379
- 16371:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.101
redis2:
image: 'redis:5.0.9'
container_name: redis2
restart: always
volumes:
- ./redis2/:/etc/redis/
ports:
- 6372:6379
- 16372:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.102
redis3:
image: 'redis:5.0.9'
container_name: redis3
restart: always
volumes:
- ./redis3/:/etc/redis/
ports:
- 6373:6379
- 16373:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.103
redis4:
image: 'redis:5.0.9'
container_name: redis4
restart: always
volumes:
- ./redis4/:/etc/redis/
ports:
- 6374:6379
- 16374:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.104
redis5:
image: 'redis:5.0.9'
container_name: redis5
restart: always
volumes:
- ./redis5/:/etc/redis/
ports:
- 6375:6379
- 16375:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.105
redis6:
image: 'redis:5.0.9'
container_name: redis6
restart: always
volumes:
- ./redis6/:/etc/redis/
ports:
- 6376:6379
- 16376:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.106
redis7:
image: 'redis:5.0.9'
container_name: redis7
restart: always
volumes:
- ./redis7/:/etc/redis/
ports:
- 6377:6379
- 16377:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.107
redis8:
image: 'redis:5.0.9'
container_name: redis8
restart: always
volumes:
- ./redis8/:/etc/redis/
ports:
- 6378:6379
- 16378:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.108
redis9:
image: 'redis:5.0.9'
container_name: redis9
restart: always
volumes:
- ./redis9/:/etc/redis/
ports:
- 6379:6379
- 16379:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.109
redis10:
image: 'redis:5.0.9'
container_name: redis10
restart: always
volumes:
- ./redis10/:/etc/redis/
ports:
- 6380:6379
- 16380:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.110
redis11:
image: 'redis:5.0.9'
container_name: redis11
restart: always
volumes:
- ./redis11/:/etc/redis/
ports:
- 6381:6379
- 16381:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.111
mynet是我们创建的网络名字
subnet是配置网络号,网段
ip地址=网络号+主机号
网络号来标识一个网段,主机号来标识这是哪一台主机
这里使用的是子网掩码的方式来区分主机号和网络号
24就表示子网掩码左边24位是1,右边8位是0
所以子网掩码就是255.255.255.0
172.30.0这个就是网络号了
所以后面配置的每一个节点的ip的网络号都是一样的
还有就是注意配置的这个网络号172.30.0还必须是内网ip
什么是内网ip呢
第一就是10开头的
第二就是172.36~172.31开头
第三就是192.168开头的
第二我们配置的这个网络号,不能和当前主机上现有的其他网段冲突
bash
ifconfig
这个命令就可以查看到当前机器上的网段了

发现有两个网段了
127.0.0.1和172.17.204.157
所以后面配置静态ip的时候,网络号部分要和网段部分一样,主机号部分就是1~255并且不重复就可以了
最后
bash
docker-compose up -d


但是现在这些容器都还不是一个集群的
配置集群
此处是把前 9 个主机构建成集群, 3 主 6 从. 后 2 个主机暂时不⽤
bash
redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379 \
172.30.0.103:6379 172.30.0.104:6379 172.30.0.105:6379 172.30.0.106:6379 \
172.30.0.107:6379 172.30.0.108:6379 172.30.0.109:6379 --cluster-replicas 2
--cluster create 表⽰建⽴集群. 后⾯填写每个节点的 ip 和地址.
--cluster-replicas 2 表⽰每个主节点需要两个从节点备份,这个配置之后,redis就知道了三个节点是一个分片
这样就构造出了三主六从了,这里端口号写的是容器内部的额端口号
但是构建的时候谁是主节点谁在一个分片都是不固定的
这个命令的话,随便进入一个容器里面就可以执行了,因为容器里面安装了redis-cli这个命令的,我的WIndows没有安装

这样就构建出来了
bash
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
这个就是给出了三个分片,然后每个分片的槽位号的范围
bash
Adding replica 172.30.0.105:6379 to 172.30.0.101:6379
Adding replica 172.30.0.106:6379 to 172.30.0.101:6379
Adding replica 172.30.0.107:6379 to 172.30.0.102:6379
Adding replica 172.30.0.108:6379 to 172.30.0.102:6379
Adding replica 172.30.0.109:6379 to 172.30.0.103:6379
Adding replica 172.30.0.104:6379 to 172.30.0.103:6379
这个描述了哪个节点是哪个节点的从节点
所以看出来了101,102,103就是主节点
最后还要输入yes来确认

1.6 使用集群


同一个容器可以用这两个方式来连接
连上一个节点以后,就等价于连接上这个集群了
bash
cluster nodes
这个命令是查看集群信息

我们可以看到ip端口号,主从,分片都显示出来了
bash
> set key1 123
MOVED 9189 172.30.0.102:6379
> set k1 123
MOVED 12706 172.30.0.103:6379
我们这里可以看到输入不同的key就把数据转移到不同的分片上了
bash
redis-cli -h 172.30.0.101 -p 6379 -c
我们这里设置-c表示,就算在101分片上也可以设置到102分片上的数据,而且会自动切换到102的客户端上面
bash
# redis-cli -h 172.30.0.101 -p 6379 -c
172.30.0.101:6379> set k1 111
-> Redirected to slot [12706] located at 172.30.0.103:6379
OK
172.30.0.103:6379> get k1
"111"
172.30.0.103:6379> set key1 123
-> Redirected to slot [9189] located at 172.30.0.102:6379
OK
172.30.0.102:6379> get key1
"123"
172.30.0.102:6379>
redis容器里面安装了redis-cli 这个命令的

但是我用的可视化界面
bash
> set key1 123
MOVED 9189 172.30.0.102:6379
> set k2 222
OK
> set k1 111
MOVED 12706 172.30.0.103:6379
所以它就会让我切换到对应的分片才让我操作
所以不好玩
还是用命令行吧
1.7 故障处理
主节点挂机
使用了集群之后,有些指令可以操作多个key-------------》如果key分散在不同的分片---》就会出问题了
bash
172.30.0.102:6379> mget k1 k2
(error) CROSSSLOT Keys in request don't hash to the same slot
172.30.0.102:6379>
这个也是有解决方案的,hash tag
如果集群中有节点挂了,怎么办,如果挂的是从节点----》没事
如果主节点挂了呢--》不好了
现在任何节点都可以进行set操作了,因为set不同的key就会切换不同主节点的客户端
在从节点上写操作---》会重定向到主节点上
所以如果主节点挂了,集群就会和哨兵一样,把从节点变为对应的主节点

我们这里停掉redis1

我们看到执行cluster nodes
101节点已经fail
然后105变成主节点了
然后106变为105的从节点了

然后又重启101节点

我们看到101已经变为了105的从节点了
具体流程
处理流程
- 故障判定
集群中的所有节点, 都会周期性的使⽤⼼跳包进⾏通信.
- 节点 A 给 节点 B 发送 ping 包, B 就会给 A 返回⼀个 pong 包. ping 和 pong 除了 message type属性之外, 其他部分都是⼀样的. 这⾥包含了集群的配置信息(该节点的id, 该节点从属于哪个分⽚,是主节点还是从节点, 从属于谁, 持有哪些 slots 的位图...).
- 每个节点, 每秒钟, 都会给⼀些随机的节点发起 ping 包, ⽽不是全发⼀遍. 这样设定是为了避免在节点很多的时候, ⼼跳包也⾮常多(⽐如有 9 个节点, 如果全发, 就是 9 * 8 有 72 组⼼跳了, ⽽且这是按照 N^2 这样的级别增⻓的).
- 当节点 A 给节点 B 发起 ping 包, B 不能如期回应的时候, 此时 A 就会尝试重置和 B 的 tcp 连接, 看能否连接成功. 如果仍然连接失败, A 就会把 B 设为 PFAIL 状态(相当于主观下线).
- A 判定 B 为 PFAIL 之后, 会通过 redis 内置的 Gossip 协议, 和其他节点进⾏沟通, 向其他节点确认 B的状态. (每个节点都会维护⼀个⾃⼰的 "下线列表", 由于视⻆不同, 每个节点的下线列表也不⼀定相同).
- 此时 A 发现其他很多节点, 也认为 B 为 PFAIL, 并且数⽬超过总集群个数的⼀半, 那么 A 就会把 B 标记成 FAIL (相当于客观下线), 并且把这个消息同步给其他节点(其他节点收到之后, 也会把 B 标记成FAIL).⾄此, B 就彻底被判定为故障节点了
某个或者某些节点宕机, 有的时候会引起整个集群都宕机 (称为 fail 状态).
以下三种情况会出现集群宕机:
• 某个分⽚, 所有的主节点和从节点都挂了.
• 某个分⽚, 主节点挂了, 但是没有从节点.
• 超过半数的 master 节点都挂了.---》说明集群遇到了非常严重的情况
📌 核⼼原则是保证每个 slots 都能正常⼯作(存取数据).
- 故障迁移
上述例⼦中, B 故障, 并且 A 把 B FAIL 的消息告知集群中的其他节点.
• 如果 B 是从节点, 那么不需要进⾏故障迁移.
• 如果 B 是主节点, 那么就会由 B 的从节点 (⽐如 C 和 D) 触发故障迁移了.
所谓故障迁移, 就是指把从节点提拔成主节点, 继续给整个 redis 集群提供⽀持.
具体流程如下:
- 从节点判定⾃⼰是否具有参选资格. 如果从节点和主节点已经太久没通信(此时认为从节点的数据和主节点差异太⼤了), 时间超过阈值, 就失去竞选资格.
- 具有资格的节点, ⽐如 C 和 D, 就会先休眠⼀定时间. 休眠时间 = 500ms 基础时间 + [0, 500ms] 随机时间 + 排名 * 1000ms. offset 的值越⼤, 则排名越靠前(越⼩),休眠时间就更短.
- ⽐如 C 的休眠时间到了, C 就会给其他所有集群中的节点, 进⾏拉票操作. 但是只有主节点才有投票资格.
- 主节点就会把⾃⼰的票投给 C (每个主节点只有 1 票). 当 C 收到的票数超过主节点数⽬的⼀半, C 就会晋升成主节点. (C ⾃⼰负责执⾏ slaveof no one, 并且让 D 执⾏ slaveof C).
- 同时, C 还会把⾃⼰成为主节点的消息, 同步给其他集群的节点. ⼤家也都会更新⾃⼰保存的集群结构信息
上述选举的过程, 称为 Raft 算法, 是⼀种在分布式系统中⼴泛使⽤的算法.
在随机休眠时间的加持下, 基本上就是谁先唤醒, 谁就能竞选成功.
哨兵中的投票是为了找一个leader
而这里投票是为了选出新的主节点
1.8 集群扩容
扩容是⼀个在开发中⽐较常遇到的场景.
随着业务的发展, 现有集群很可能⽆法容纳⽇益增⻓的数据. 此时给集群中加⼊更多新的机器, 就可以使存储的空间更⼤了
所谓分布式的本质, 就是使⽤更多的机器, 引⼊更多的硬件资源
第⼀步: 把新的主节点加⼊到集群
上⾯已经把 redis1 - redis9 重新构成了集群. 接下来把 redis10 和 redis11 也加⼊集群.
此处我们把 redis10 作为主机, redis11 作为从机.
bash
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
add-node 后的第⼀组地址是新节点的地址. 第⼆组地址是集群中的任意节点地址.


我们看到110已经变为master了
但是我们这个master没有分片啊,没有槽位号,所以要重新分配槽位号
要把之前的master上面的slots拎出来一些分配给新的master
第⼆步: 重新分配 slots
bash
redis-cli --cluster reshard 172.30.0.101:6379
reshard 后的地址是集群中的任意节点地址.
另外, 注意单词拼写, 是 reshard (重新切分), 不是 reshared (重新分享) , 不要多写个

我们看到它提问了,问我们要移动多少个slots,移动四分之一,就是4096个
16384/4=4096

第二个问题就是问让哪个节点接收这4096个,直接输入110主机的id就可以了
第三个问题就是问你从哪里分配这4096个slots
all就是每个分片都分一点
第二种就是手动指定一个或者几个节点的id,最后以done结尾



这个就是搬运的计划,其实还没有开始搬运
最后确认就输入yes
现在就开始真正的搬运了

可以看到现在的master都有slots了
在搬运 key 的过程中, 对于那些不需要搬运的 key, 访问的时候是没有任何问题的. 但是对于需要搬运的 key, 进⾏访问可能会出现短暂的访问错误 (key 的位置出现了变化).所以一般没有用户的时候进行扩容呗,但是最好的方法是新建一个集群,新的机器,数据导过来,这样就不会让用户决定数据有问题了
随着搬运完成, 这样的错误⾃然就恢复了.
第三步: 给新的主节点添加从节点
光有主节点了, 此时扩容的⽬标已经初步达成. 但是为了保证集群可⽤性, 还需要给这个新的主节点添加
从节点, 保证该主节点宕机之后, 有从节点能够顶上
bash
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id [172.30.1.110 节点的 nodeId]
bash
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id 737ffbcfae9dddd906590a95610f98995e9f3730

执⾏完毕后, 从节点就已经被添加完成了

我们看到111已经变为了Slave了,而且也是110的slave
集群缩容
缩容很少,所以了解一下就可以了
接下来演⽰把 110 和 111 这两个节点删除.
第⼀步: 删除从节点
此处删除的节点 nodeId 是 111 节点的 id
bash
# redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
redis-cli --cluster del-node 172.30.0.101:6379
03f4a97806a0d3de2299cc16e6a3559f0c832bc1
bash
4 >>> Removing node 03f4a97806a0d3de2299cc16e6a3559f0c832bc1 from cluster
172.30.0.101:6379
5 >>> Sending CLUSTER FORGET messages to the cluster...
6 >>> SHUTDOWN the node.
第⼆步: 重新分配 slots
bash
redis-cli --cluster reshard 172.30.0.101:6379
执⾏后仍然进⼊交互式操作.
注意!! 此时要删除的主节点, 包含 4096 个 slots. 我们把 110 这个注解上的这 4096 个 slots
分成三份 (1365 + 1365 + 1366), 分别分给其他三个主节点.
这样可以使 reshard 之后的集群各个分⽚ slots 数⽬仍然均匀.
第⼀次重分配: 分配给 101 1365 个 slots
接收 slots 的 nodeId 填写 101 的 nodeId. Source Node 填写 110 的 nodeId
bash
1 How many slots do you want to move (from 1 to 16384)? 1365
2 What is the receiving node ID? 3397c6364b43dd8a8d49057ad37be57760d3a81f
3 Please enter all the source node IDs.
4 Type 'all' to use all the nodes as source nodes for the hash slots.
5 Type 'done' once you entered all the source nodes IDs.
6 Source node #1: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
7 Source node #2: done
第⼆次重分配: 分配给 102 1365 个 slots
接收 slots 的 nodeId 填写 102 的 nodeId. Source Node 填写 110 的 nodeId
bash
1 How many slots do you want to move (from 1 to 16384)? 1365
2 What is the receiving node ID? 98736357a53c85aaebb31fa5ad286ab36b862426
3 Please enter all the source node IDs.
4 Type 'all' to use all the nodes as source nodes for the hash slots.
5 Type 'done' once you entered all the source nodes IDs.
6 Source node #1: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
7 Source node #2: done
第三次重分配: 分配给 103 1366 个 slots
接收 slots 的 nodeId 填写 103 的 nodeId. Source Node 填写 110 的 nodeId
bash
1 How many slots do you want to move (from 1 to 16384)? 1366
2 What is the receiving node ID? 26e98f947b99b3a2a5da5a7c3ed3875ae9cf366c
3 Please enter all the source node IDs.
4 Type 'all' to use all the nodes as source nodes for the hash slots.
5 Type 'done' once you entered all the source nodes IDs.
6 Source node #1: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
7 Source node #2: done
此时查看集群状态, 可以看到 110 节点已经不再持有 slots 了.
bash
1 127.0.0.1:6379> CLUSTER NODES
第三步: 删除主节点
把 110 节点从集群中删除.
bash
1 # redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
2 redis-cli --cluster del-node 172.30.0.101:6379
7c343b7e3f82f2e601ac6b9eba9f846b3065c600
3
4 >>> Removing node 7c343b7e3f82f2e601ac6b9eba9f846b3065c600 from cluster
172.30.0.101:6379
5 >>> Sending CLUSTER FORGET messages to the cluster...
6 >>> SHUTDOWN the node.
再次查看集群节点信息, 110 节点已经不在集群中了.
bash
1 127.0.0.1:6379> CLUSTER nodes
使⽤ Spring Boot 连接 Redis 集群
bash
spring:
redis:
cluster:
nodes:
- 172.30.0.101:6379
- 172.30.0.102:6379
- 172.30.0.103:6379
- 172.30.0.104:6379
- 172.30.0.105:6379
- 172.30.0.106:6379
- 172.30.0.107:6379
- 172.30.0.108:6379
- 172.30.0.109:6379
lettuce:
cluster:
refresh:
adaptive: true
period: 2000
下⽅的 lettuce 系列配置, ⽬的是为了⾃动刷新集群的拓扑结构. 当集群中有节点宕机/加⼊新节点之后, 我们的代码能够⾃动感知到集群的变化.
改完配置之后, 其他代码⽆需做出任何调整, 直接就可以正常运⾏了
注意!
由于上述 ip 都是 docker 容器的 ip
jedis访问集群
使⽤ JedisCluster 类代替 Jedis 类即可.
需要在创建实例的时候, 把多个节点的地址, 都设置进去.
JedisCluster 提供的⽅法和 Jedis 基本⼀致. 都和 Redis 命令是对应的. 具体细节我们不再演⽰了.
bash
public static void main(String[] args) {
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("172.30.0.101", 6379));
nodes.add(new HostAndPort("172.30.0.102", 6379));
nodes.add(new HostAndPort("172.30.0.103", 6379));
nodes.add(new HostAndPort("172.30.0.104", 6379));
nodes.add(new HostAndPort("172.30.0.105", 6379));
nodes.add(new HostAndPort("172.30.0.106", 6379));
nodes.add(new HostAndPort("172.30.0.107", 6379));
nodes.add(new HostAndPort("172.30.0.108", 6379));
nodes.add(new HostAndPort("172.30.0.109", 6379));
try (JedisCluster jedisCluster = new JedisCluster(nodes)) {
jedisCluster.set("k1", "111");
String value = jedisCluster.get("k1");
System.out.println(value);
}
}