《Redis》集群
文章目录
- 集群概念
- 哈希槽分区算法
- 基于Docker搭建集群环境
- 搭建集群关系
- 故障处理机制
- 集群扩容
集群概念
哈希槽分区算法
hash_slot = crc16(key) % 16384
16384 其实是 16 * 1024, 也就是 2^14.
相当于是把整个哈希值, 映射到 16384 个槽位上, 也就是 [0, 16383].
然后再把这些槽位⽐较均匀的分配给每个分⽚. 每个分⽚的节点都需要记录⾃⼰持有哪些分⽚
假设当前有三个分⽚, ⼀种可能的分配⽅式:
• 0 号分⽚: [0, 5461], 共 5462 个槽位
• 1 号分⽚: [5462, 10923], 共 5462 个槽位
• 2 号分⽚: [10924, 16383], 共 5460 个槽位
这里的分片规则是很灵活的,每个分片持有的槽位也不一定是连续的。
每个分片的节点使用位图来表示自己持有哪些槽位,对于16384个槽位来说,需要2048字节(2KB)大小的空间来表示。
如果需要扩容,比如新增3号分片,就可以针对原来的槽位进行重新分配,比如把之前分片持有的槽位,各自拿出一点来分给3号分片。
一种可能的分配方式:
• 0 号分⽚: [0, 4095], 共 4096 个槽位
• 1 号分⽚: [5462, 9557], 共 4096 个槽位
• 2 号分⽚: [10924, 15019], 共 4096 个槽位
• 3 号分⽚: [4096, 5461] + [9558, 10923] + [15019, 16383], 共 4096 个槽位
我们在实际使⽤ Redis 集群分⽚的时候, 不需要⼿动指定哪些槽位分配给某个分⽚, 只需要告
诉某个分⽚应该持有多少个槽位即可, Redis 会⾃动完成后续的槽位分配, 以及对应的 key 搬
运的⼯作
问题1::Redis 集群是最多有 16384 个分⽚吗?
并⾮如此. 如果⼀个分⽚只有⼀个槽位, 这对于集群的数据均匀其实是难以保证的.
实际上 Redis 的作者建议集群分⽚数不应该超过 1000.
分片数量越多,系统越复杂,出故障的概率也越高。
问题2:为什么是 16384 个槽位?
节点之间通过⼼跳包通信. ⼼跳包中包含了该节点持有哪些槽位, 这个是使⽤位图这样的数据结构
表⽰的. 表⽰ 16384 (16k) 个 槽位, 需要的位图⼤⼩是 2KB. 如果给定的槽位数更多了, ⽐如 65536
个了, 此时就需要消耗更多的空间, 8 KB 位图表⽰了. 8 KB, 对于内存来说不算什么, 但是在频繁的⽹
络⼼跳包中, 还是⼀个不⼩的开销的。
• 另⼀⽅⾯, Redis 集群⼀般不建议超过 1000 个分⽚. 所以 16k 对于最⼤ 1000 个分⽚来说是⾜够⽤
的, 同时也会使对应的槽位配置位图体积不⾄于很⼤。
所以,哈希槽分片算法就解决了搬运成本⾼ 和 数据分配不均匀的问题。
这两个问题分别是哈希求余和一致性哈希算法无法解决的问题。
基于Docker搭建集群环境
要搭建的集群如上,由于机器有限,这里就使用docker来模拟多台机器实现redis集群的搭建。
创建目录:
mkdir -p redis-cluster/
cd redis-cluster
touch docker-compose.yml generate.sh
后面这个文件是一个shell脚本文件,为了同时执行多条命令,同时还能加入像条件,循环,函数等机制,这样这个shell脚本就像一门编程语言一样了。因为要批量生成11个redis节点(9个是集群,2个是扩容用的,使用脚本会很快)
注意!在此之间一定要关闭之前开启的redis服务器。
进入对应的redis目录,如redis-data/ ,redis-sentinel/
执行:docker-compose down
因为我这些redis的数据节点,redis的哨兵节点都是在一个个docker容器中启动的,所以要用docker-compose命令来停止。
docker ps -a,就能看到没有正在运行的容器了。
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命令后,就生成了多个配置文件
接下来创建redis容器
在docker-compose.yml文件中内容如下
version: '3.7'
networks:mynet:ipam:config:- subnet: 172.30.0.0/24
services:redis1:image: 'redis:5.0.9'container_name: redis1restart: alwaysvolumes:- ./redis1/:/etc/redis/ports:- 6371:6379- 16371:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.101redis2:image: 'redis:5.0.9'container_name: redis2restart: alwaysvolumes:- ./redis2/:/etc/redis/ports:- 6372:6379- 16372:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.102redis3:image: 'redis:5.0.9'container_name: redis3restart: alwaysvolumes:- ./redis3/:/etc/redis/ports:- 6373:6379- 16373:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.103redis4:image: 'redis:5.0.9'container_name: redis4restart: alwaysvolumes:- ./redis4/:/etc/redis/ports:- 6374:6379- 16374:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.104redis5:image: 'redis:5.0.9'container_name: redis5restart: alwaysvolumes:- ./redis5/:/etc/redis/ports:- 6375:6379- 16375:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.105redis6:image: 'redis:5.0.9'container_name: redis6restart: alwaysvolumes:- ./redis6/:/etc/redis/ports:- 6376:6379- 16376:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.106redis7:image: 'redis:5.0.9'container_name: redis7restart: alwaysvolumes:- ./redis7/:/etc/redis/ports:- 6377:6379- 16377:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.107redis8:image: 'redis:5.0.9'container_name: redis8restart: alwaysvolumes:- ./redis8/:/etc/redis/ports:- 6378:6379- 16378:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.108redis9:image: 'redis:5.0.9'container_name: redis9restart: alwaysvolumes:- ./redis9/:/etc/redis/ports:- 6379:6379- 16379:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.109redis10:image: 'redis:5.0.9'container_name: redis10restart: alwaysvolumes:- ./redis10/:/etc/redis/ports:- 6380:6379- 16380:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.110redis11:image: 'redis:5.0.9'container_name: redis11restart: alwaysvolumes:- ./redis11/:/etc/redis/ports:- 6381:6379- 16381:16379command:redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.111
随后执行命令:
docker-compose up -a
记住,生成新的容器前,一定要看看之前的redis容器是否关闭了,否则可能造成端口冲突!
搭建集群关系
接下来搭建集群关系:
此处是把前 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
- –cluster create 表⽰建⽴集群. 后⾯填写每个节点的 ip 和端口.
–cluster-replicas 2 表⽰每个主节点需要两个从节点备份
这里就是为了建立三主六从的集群结构。
但是这里,谁是主节点,谁是谁的从节点,不一定的。谁和谁是一组也不确定的。
因为节点之间没有优劣之分。
然后输入yes,才会真正构建集群。
从101-109这九个节点,此时是一个整体,使用客户端操作任意一个节点,本质上都是等价的。
redis-cli -c -h 172.30.0.101 -p 6379 # 使用静态IP连接redis1容器的redis服务器。
redis-cli -c -p 6371 # 直接连接映射到容器外的端口6372(也就是宿主机的端口)来操作redis1容器。
上面两者都是一样的。
-c选项是,如果连接了该容器的redis服务器,并且在里面新增数据(set key1 11)
可能会出现这个key1的哈希映射槽不属于我当前的redis1服务器,导致新增数据失败。
-c选项就是为了就算我新增数据不在当前槽,这个数据也会自动通过上面讲的哈希槽分片算法,自动映射到正确的槽上,也就是直接新增进持有key1对应的哈希槽的redis服务器中了。
使用redis-cli客户端连接上某一个服务器节点后,
使用
cluster nodes命令,即可查看整个集群的节点信息。
接下来就可以使用集群存储数据了
故障处理机制
docker stop redis1
模拟主节点redis1故障
docker start redis1后,发现它不再是主节点了。
可以执行cluster failover命令进行集群恢复,也可以主动登录到redis1服务器,执行slaveof no one命令, 直接把自己升级成主节点。
集群机制,也能处理故障处理。(不需要哨兵的情况下)
但是这里集群的故障处理和哨兵的故障处理流程不太一样:
1.先进行故障判定:
2.进行故障处理
集群扩容
集群的扩容,是一件成本高,风险大的工作。
第⼀步: 把新的主节点加⼊到集群
上⾯已经把 redis1 - redis9 重新构成了集群. 接下来把 redis10 和 redis11 也加⼊集群.
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
第一个IP+端口表示要新增的节点,第二个IP+端口表示集群中的任意一个节点(哪个都行,这里我挑了第一个),表示要将新增的节点加入第二个节点所在的集群中。
第二步:重新分配哈希槽(slots)
redis-cli --cluster reshard 172.30.0.101:6379
resard后的地址是集群中的任意节点地址.
另外, 注意单词拼写, 是 reshard (重新切分), 不是 reshared (重新分享) , 不要多写个 e.
执⾏之后, 会进⼊交互式操作, redis 会提⽰⽤⼾输⼊以下内容:
• 多少个 slots 要进⾏ reshard ? (此处我们填写 4096,其实也就是本来是一个主节点占1/3的哈希槽的,现在有4个主节点了,那就每个主节点占有1/4哈希槽吧,16384/4 那就是4096了)
• 哪个节点来接收这些 slots ? (此处我们填写 172.30.0.110 这个节点的集群节点对应的id,进入任意一个节点,然后执行cluster nodes命令查看)
• 这些 slots 从哪些节点搬运过来? (此处我们填写 all, 表⽰从其他所有的节点都进⾏搬运)
之后会询问你上面的搬运方案是否合适,然后输入yes。
之后就会进⾏集群的 key 搬运⼯作. 这个过程涉及到数据搬运. 可能需要消耗⼀定的时间。
📌 在搬运 key 的过程中, 对于那些不需要搬运的 key, 访问的时候是没有任何问题的. 但是对于需
要搬运的 key, 进⾏访问可能会出现短暂的访问错误 (key 的位置出现了变化)
随着搬运完成, 这样的错误⾃然就恢复了
不过,搬运的过程不仅仅是对key的重新划分,还有对数据的重新搬运,是比较重量的操作。
第三步: 给新的主节点添加从节点
光有主节点了, 此时扩容的⽬标已经初步达成. 但是为了保证集群可⽤性, 还需要给这个新的主节点添加从节点, 保证该主节点宕机之后, 有从节点能够顶上.
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]
执⾏完毕后, 从节点就已经被添加完成了.