Redis 的哨兵模式,提高了系统的可用性,但是正在用来存储数据的还是 master 和 slave 节点,所有的数据都需要存储在单个 master 和 salve 节点中。
如果数据量很大,接近超出了 master / slave 所在机器的物理内存,就可能出现严重问题了。
虽然硬件价格在不断降低,一些中大厂的服务器内存已经可以达到 TB 级别了,但是1TB 在当前这个“大数据”时代,俨然不算什么,有的时候我们确实需要更大的内存空间来保存更多的数据。
Redis 的集群就是在上述的思路之下,引入多组 master / slave,每一组 master / slave 存储数据全集的一部分,从而构成一个更大的整体,称为 Redis 集群(Cluster)。
假定整个数据全集是1TB,引入三组 master / slave来存储。那么每⼀组机器只需要存储整个数据全集的 1/3 即可。
这三组机器存储的数据都是不同的
每个 Slave 都是对应 Master 的备份(当 Master 挂了,对应的 Slave 会补位成 Master)。
每个红框部分都可以称为是一个分片(Sharding)。
如果全量数据进一步增加,只要再增加更多的分片,即可解决。
可能有的人认为,数据量多了,使用硬盘来保存不就行了?不要忘了硬盘只是存储多了,但是访问速度是比内存慢很多的。但是事实上,还是存在很多的应用场景,既希望存储较多的数据,又希望有非常高的读写速度。
Redis cluster 的核心思路是用多组机器来存数据的每个部分,那么接下来的核心问题就是,给定一个数据(一个具体的 key)那么这个数据应该存储在哪个分片上?读取的时候又应该去哪个分片读取?
围绕这个问题,业界有三种比较主流的实现方式
设有N个分片,使用 [0,N-1] 这样序号进行编号
针对某个给定的 key,先计算 hash 值,再把得到的结果%N,得到的结果即为分片编号。
例如,N为3。给定 key 为 hello,对 hello 计算 hash 值(比如使用 md5 算法),得到的结果为
bc4b2a76b9719d91
,再把这个结果 %3,结果为 0,那么就把 hello 这个 key 放到 0 号分片上。当然,实际工作中涉及到的系统,计算 hash 的方式不一定是 md5,但是思想是一致的。
后续如果要取某个 key 的记录,也是针对 key 计算 hash,再对 N 求余,就可以找到对应的分片编号了
优缺点:
优点:简单高效,数据分配均匀。
缺点:一旦需要进行扩容,N 改变了,原有的映射规则被破坏,就需要让节点之间的数据相互传输,重新排列,以满足新的映射规则此时需要搬运的数据量是比较多的,开销较大
为了降低上述的搬运开销,能够更高效扩容,业界提出了“一致性哈希算法”。key 映射到分片序号的过程不再是简单求余了,而是改成以下过程:
第一步:把 0->2^32-1 这个数据空间,映射到⼀个圆环上。数据按照顺时针方向增长。
第二步:假设当前存在三个分片,就把分片放到圆环的某个位置上。
第三步:假定有⼀个 key,计算得到 hash 值 H,那么这个key映射到哪个分片呢?规则很简单,就是从 H 所在位置,顺时针往下找,找到的第⼀个分片,即为该 key 所从属的分片。
这就相当于,N个分片的位置,把整个圆环分成了 N 个管辖区间 key 的 hash 值落在某个区间内,就归对应区间管理。
在这个情况下,如果扩容一个分片,如何处理呢?
原有分片在环上的位置不动,只要在环上新安排一个分片位置即可。
优缺点:
优点:大大降低了扩容时数据搬运的规模,提高了扩容操作的效率。
缺点:数据分配不均匀(有的多有的少,数据倾斜)
为了解决上述问题(搬运成本高和数据分配不均),Redis cluster 引入了哈希槽(hash slots)算法。
hash_slot = crc16(key) % 16384 #crc16也是⼀种hash算法,16384其实是16*1024,也就是2^14
相当于是把整个哈希值,映射到 16384 个槽位上,也就是 [0,16383]。
然后再把这些槽位比较均匀的分配给每个分片,每个分片的节点都需要记录自己持有哪些槽位。
假设当前有三个分片,一种可能的分配方式:
这里的分片规则是很灵活的,每个分片持有的槽位也不一定连续。
每个分片的节点使用位图来表示自己持有哪些槽位,对于 16384 个槽位来说,需要 2048 个字节(2KB)大小的内存空间表示
如果需要进行扩容比如新增一个3 号分片就可以针对原有的槽位进行重新分配。
比如可以把之前每个分片持有的槽位,各拿出一点,分给新分片
一种可能的分配方式:
我们在实际使用 Redis 集群分片的时候,不需要手动指定哪些槽位分配给某个分片,只需要告诉某个分片应该持有多少个槽位即可,Redis 会自动完成后续的槽位分配。以及对应的 key 搬运的工作
此处还有两个问题:
问题一:Redis集群是最多有16384个分片吗?
并非如此,如果一个分片只有一个槽位,这对于集群的数据均匀其实是难以保证的。
实际上 Redis 的作者建议集群分片数不应该超过1000。
而且,16000 这么大规模的集群,本身的可用性也是一个大问题,一个系统越复杂,出现故障的概率是越高的。
问题二:为什么是16384个槽位?
接下来基于 docker,搭建一个集群每个节点都是一个容器
此处我们先创建出 11 个 redis 节点,其中前 9 个用来演示集群的搭建,后两个用来演示集群扩容
创建 redis-cluster 目录内部创建两个文件
redis-cluster/
├── docker-compose.yml
└── generate.sh
generate.sh 内容如下
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
执行命令
bash generate.sh
生成目录如下:
.
├── docker-compose.yml
├── generate.sh
├── redis1
│ └── redis.conf
├── redis10
│ └── redis.conf
├── redis11
│ └── redis.conf
├── redis2
│ └── redis.conf
├── redis3
│ └── redis.conf
├── redis4
│ └── redis.conf
├── redis5
│ └── redis.conf
├── redis6
│ └── redis.conf
├── redis7
│ └── redis.conf
├── redis8
│ └── redis.conf
└── redis9
└── redis.conf
其中 redis.conf 每个都不同. 以redis1为例:
区别在于每个配置中配置的 cluster-announce-ip 是不同的,其他部分都相同
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.101
cluster-announce-port 6379
cluster-announce-bus-port 16379
后续会给每个节点分配不同的ip地址。
配置说明:
编写 docker-compose.yml
先创建 networks,并分配网段为 172.30.0.0/24
配置每个节点注意配置文件映射,端口映射,以及容器的 ip 地址设定成固定 ip 方便后续的观察和操作。
此处的端口映射不配置也可以,配置的目的是为了可以通过宿主机 ip + 映射的端口进行访问。通过容器自身 ip:6379 的方式也可以访问。
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
docker-compose up -d
启动⼀个 docker 客户端.
此处是把前 9 个主机构建成集群,3 主 6 从。后 2 个主机暂时不用。
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
执行之后,容器之间会进行加入集群操作。
日志中会描述哪些是主节点,哪些从节点跟随哪个主节点。
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
>>> Performing hash slots allocation on 9 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
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
M: 2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379
slots:[0-5460] (5461 slots) master
M: 8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379
slots:[5461-10922] (5462 slots) master
M: 82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379
slots:[10923-16383] (5461 slots) master
S: f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379
replicates 82fee6c30afece1b01752436f73e81790a4cd2b2
S: cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379
replicates 2a8e025a472d3e76474890c2f2799b61cbdf5e05
S: 3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379
replicates 2a8e025a472d3e76474890c2f2799b61cbdf5e05
S: 9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379
replicates 8ca9590e458d3c02ea3260774818402ad372334c
S: 3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379
replicates 8ca9590e458d3c02ea3260774818402ad372334c
S: 06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379
replicates 82fee6c30afece1b01752436f73e81790a4cd2b2
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.30.0.101:6379)
M: 2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379
slots:[0-5460] (5461 slots) master
2 additional replica(s)
S: cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379
slots: (0 slots) slave
replicates 2a8e025a472d3e76474890c2f2799b61cbdf5e05
M: 82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379
slots:[10923-16383] (5461 slots) master
2 additional replica(s)
M: 8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379
slots:[5461-10922] (5462 slots) master
2 additional replica(s)
S: 3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379
slots: (0 slots) slave
replicates 8ca9590e458d3c02ea3260774818402ad372334c
S: 06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379
slots: (0 slots) slave
replicates 82fee6c30afece1b01752436f73e81790a4cd2b2
S: 3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379
slots: (0 slots) slave
replicates 2a8e025a472d3e76474890c2f2799b61cbdf5e05
S: f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379
slots: (0 slots) slave
replicates 82fee6c30afece1b01752436f73e81790a4cd2b2
S: 9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379
slots: (0 slots) slave
replicates 8ca9590e458d3c02ea3260774818402ad372334c
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
见到下方的[OK]说明集群建立完成。
此时,使用客户端连上集群中的任何一个节点,都相当于连上了整个集群。
# redis-cli -h 172.30.0.101 -p 6379 -c
172.30.0.101:6379> cluster nodes
8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379@16379 master - 0 1708256015120 2 connected 5461-10922
2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379@16379 myself,master - 0 1708256015000 1 connected 0-5460
3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379@16379 slave 2a8e025a472d3e76474890c2f2799b61cbdf5e05 0 1708256014000 6 connected
82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379@16379 master - 0 1708256014215 3 connected 10923-16383
cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379@16379 slave 2a8e025a472d3e76474890c2f2799b61cbdf5e05 0 1708256015000 5 connected
9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708256015523 7 connected
f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708256014000 4 connected
3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708256014517 8 connected
06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708256015221 9 connected
172.30.0.101:6379> set k1 1
-> Redirected to slot [12706] located at 172.30.0.103:6379
OK
172.30.0.103:6379> get k1
"1"
172.30.0.103:6379>
如果需要接触集群关系,可以使用
redis-cli -h 172.30.0.101 -p 6379 CLUSTER RESET
如果希望同时清空数据,可以先执行 FLUSHALL
,然后执行 CLUSTER RESET
。对于只读副本节点,如果出现 (error) READONLY You can't write against a read only replica
错误,先执行 SLAVEOF NO ONE
命令。
redis-cli -h <节点IP> -p <端口> FLUSHALL
redis-cli -h <节点IP> -p <端口> CLUSTER RESET
演示效果
手动停止一个 master 节点,观察效果
比如上述拓扑结构中可以看到 redis1 redis2 redis3 是主节点随便挑一个停掉
docker stop redis1
连上 redis2,观察结果
172.30.0.102:6379> cluster nodes
f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708256555717 4 connected
8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379@16379 myself,master - 0 1708256555000 2 connected 5461-10922
3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708256556725 8 connected
06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708256556000 9 connected
3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379@16379 master - 0 1708256555000 10 connected 0-5460
9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708256556523 7 connected
2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379@16379 master,fail - 1708256499896 1708256498000 1 connected
82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379@16379 master - 0 1708256556523 3 connected 10923-16383
cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379@16379 slave 3ad82ae0bb237288e7a24f14fe72915dd162e0d1 0 1708256554511 10 connected
可以看到 101 已经提示 fail,然后原本是 slave 的106 成了新的 master.
重新启动 redis1
docker start redis1
再次观察结果可以看到 101 启动了仍然是 slave。
172.30.0.101:6379> cluster nodes
8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379@16379 master - 0 1708256777442 2 connected 5461-10922
3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379@16379 master - 0 1708256778000 10 connected 0-5460
3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708256778046 8 connected
2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379@16379 myself,slave 3ad82ae0bb237288e7a24f14fe72915dd162e0d1 0 1708256776000 1 connected
9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708256777000 7 connected
06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708256778046 9 connected
82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379@16379 master - 0 1708256779000 3 connected 10923-16383
cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379@16379 slave 3ad82ae0bb237288e7a24f14fe72915dd162e0d1 0 1708256778000 10 connected
f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708256778449 4 connected
可以使用 cluster failover
进行集群恢复也就是把 101 重新设定成 master.(登录到 101上执行)
172.30.0.101:6379> cluster failover
OK
172.30.0.101:6379> cluster nodes
8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379@16379 master - 0 1708256839000 2 connected 5461-10922
3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379@16379 slave 2a8e025a472d3e76474890c2f2799b61cbdf5e05 0 1708256838000 11 connected
3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708256838000 8 connected
2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379@16379 myself,master - 0 1708256838000 11 connected 0-5460
9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708256839531 7 connected
06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708256838524 9 connected
82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379@16379 master - 0 1708256838525 3 connected 10923-16383
cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379@16379 slave 2a8e025a472d3e76474890c2f2799b61cbdf5e05 0 1708256839000 11 connected
f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708256838000 4 connected
此时可以看到 101 重新变成了 master,而 106 又变回了 slave。
集群中的所有节点都会周期性的使用心跳包进行通信
message type
属性之外,其他部分都是一样的。这里包含了集群的配置信息(该节点的id,该节点从属于哪个分片,是主节点还是从节点,从属于谁,持有哪些 slots 的位图…).至此,B 就彻底被判定为故障节点了。
某个或者某些节点宕机有的时候会引起整个集群都宕机(称为 fail 状态)
以下三种情况会出现集群宕机:
某个分片,所有的主节点和从节点都挂了(该分片无法提供数据服务了)
某个分片,主节点挂了,但是没有从节点(该分片无法提供数据服务了)
超过半数的 master 节点都挂了(如果突然一系列的 master 都挂了,此时说明集群遇到了非常严重的情况)
核心原则是保证每个 slots 都能正常工作(存取数据)
上述例子中 B 故障。并且 A 把 B FAIL的消息告知集群中的其他节点。
所谓故障迁移就是指把从节点提拔成主节点继续给整个 redis 集群提供支持
具体流程如下:
上述选举的过程称为 Raft 算法是一种在分布式系统中广泛使用的算法。
在随机休眠时间的加持下,基本上就是谁先唤醒,谁就能竞选成功
扩容是一个在开发中比较常遇到的场景
随着业务的发展,现有集群很可能无法容纳日益增长的数据此时给集群中加入更多新的机器,就可以使存储的空间更大了。
所谓分布式的本质,就是使用更多的机器引入更多的硬件资源
第一步:把新的主节点加入到集群
上面已经把 redis1-redis9 重新构成了集群接下来把 redis10 和 redis11也加入集群
此处我们把 redis10 作为主机,redis11 作为从机
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
add-node 后的第一组地址是新节点的地址,第二组地址是集群中的任意节点地址
执行结果
# redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
>>> Adding node 172.30.0.110:6379 to cluster 172.30.0.101:6379
>>> Performing Cluster Check (using node 172.30.0.101:6379)
M: 2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379
slots:[0-5460] (5461 slots) master
2 additional replica(s)
M: 8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379
slots:[5461-10922] (5462 slots) master
2 additional replica(s)
S: 3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379
slots: (0 slots) slave
replicates 2a8e025a472d3e76474890c2f2799b61cbdf5e05
S: 3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379
slots: (0 slots) slave
replicates 8ca9590e458d3c02ea3260774818402ad372334c
S: 9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379
slots: (0 slots) slave
replicates 8ca9590e458d3c02ea3260774818402ad372334c
S: 06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379
slots: (0 slots) slave
replicates 82fee6c30afece1b01752436f73e81790a4cd2b2
M: 82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379
slots:[10923-16383] (5461 slots) master
2 additional replica(s)
S: cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379
slots: (0 slots) slave
replicates 2a8e025a472d3e76474890c2f2799b61cbdf5e05
S: f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379
slots: (0 slots) slave
replicates 82fee6c30afece1b01752436f73e81790a4cd2b2
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 172.30.0.110:6379 to make it join the cluster.
[OK] New node added correctly.
此时的集群状态如下,可以看到 172.30.0.110
这个节点已经成为了集群中的主节点
172.30.0.101:6379> cluster nodes
8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379@16379 master - 0 1708262523584 2 connected 5461-10922
3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379@16379 slave 2a8e025a472d3e76474890c2f2799b61cbdf5e05 0 1708262523584 11 connected
3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708262523082 8 connected
2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379@16379 myself,master - 0 1708262522000 11 connected 0-5460
9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379@16379 slave 8ca9590e458d3c02ea3260774818402ad372334c 0 1708262523081 7 connected
06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708262522000 9 connected
82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379@16379 master - 0 1708262523584 3 connected 10923-16383
cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379@16379 slave 2a8e025a472d3e76474890c2f2799b61cbdf5e05 0 1708262522075 11 connected
83ec14c895dc09da1c89e1bf7dd91490c07cc0af 172.30.0.110:6379@16379 master - 0 1708262522577 0 connected
f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379@16379 slave 82fee6c30afece1b01752436f73e81790a4cd2b2 0 1708262523000 4 connected
第二步:重新分配 slots
redis-cli --cluster reshard 172.30.0.101:6379
reshard 后的地址是集群中的任意节点地址
另外,注意单词拼写,是 reshard(重新切分),不是 reshared(重新分享),不要多写个 e。
执行之后,会进入交互式操作,redis 会提示用户输入以下内容:
172.30.0.110
这个节点的集群节点id)执行结果如下:
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? 522a1bd88a1a9084e6919fa88f4bf1c3655ad837
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1: all
确定之后,会初步打印出搬运方案,让用户确认。
之后就会进行集群的 key 搬运工作这个过程涉及到数据搬运可能需要消耗一定的时间
在搬运 key 的过程中,对于那些不需要搬运的 key,访问的时候是没有任何问题的。但是对于需要搬运的 key,进行访问可能会出现短暂的访问错误(key 的位置出现了变化)。随着搬运完成,这样的错误自然就恢复了。因此,在进行扩容的时候,尽量选择访问量少的时候进行访问。
第三步:给新的主节点添加从节点
光有主节点了。此时扩容的目标已经初步达成但是为了保证集群可用性。还需要给这个新的主节点添加从节点,保证该主节点宕机之后,有从节点能够顶上。
# redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave
>>> Adding node 172.30.0.111:6379 to cluster 172.30.0.101:6379
>>> Performing Cluster Check (using node 172.30.0.101:6379)
M: 2a8e025a472d3e76474890c2f2799b61cbdf5e05 172.30.0.101:6379
slots:[1365-5460] (4096 slots) master
2 additional replica(s)
M: 8ca9590e458d3c02ea3260774818402ad372334c 172.30.0.102:6379
slots:[6827-10922] (4096 slots) master
2 additional replica(s)
S: 3ad82ae0bb237288e7a24f14fe72915dd162e0d1 172.30.0.106:6379
slots: (0 slots) slave
replicates 2a8e025a472d3e76474890c2f2799b61cbdf5e05
S: 3a03ff84b90a34ca8096fe0ef25fcd70b80b0d4f 172.30.0.108:6379
slots: (0 slots) slave
replicates 8ca9590e458d3c02ea3260774818402ad372334c
S: 9f703db38eafbd960c3f2b0d5352032996083d89 172.30.0.107:6379
slots: (0 slots) slave
replicates 8ca9590e458d3c02ea3260774818402ad372334c
S: 06c9b48a137544a19fab1d4be4ed2dd39a06c2ce 172.30.0.109:6379
slots: (0 slots) slave
replicates 83ec14c895dc09da1c89e1bf7dd91490c07cc0af
M: 82fee6c30afece1b01752436f73e81790a4cd2b2 172.30.0.103:6379
slots:[12288-16383] (4096 slots) master
1 additional replica(s)
S: cef6952599c380559ff9eb008cd30354fbe89b3a 172.30.0.105:6379
slots: (0 slots) slave
replicates 2a8e025a472d3e76474890c2f2799b61cbdf5e05
M: 83ec14c895dc09da1c89e1bf7dd91490c07cc0af 172.30.0.110:6379
slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
1 additional replica(s)
S: f5f343a11c911c67c276f3cf3ebc2ca58d936d6c 172.30.0.104:6379
slots: (0 slots) slave
replicates 82fee6c30afece1b01752436f73e81790a4cd2b2
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Automatically selected master 172.30.0.103:6379
>>> Send CLUSTER MEET to node 172.30.0.111:6379 to make it join the cluster.
Waiting for the cluster to join
>>> Configure node as replica of 172.30.0.103:6379.
[OK] New node added correctly.
执行完毕后,从节点就已经被添加完成了。