上篇文章我们了解Redis哨兵的相关操作,使用哨兵只是解决了主节点瘫痪,从节点不能自动变为主节点的问题,并没有解决“单点问题”,(写操作只能由主节点完成)。 为了能存储更多的数据,分担主从节点的读/写压力,于是Redis引入了集群的相关操作, 引⼊多组 Master / Slave ,每⼀组 Master / Slave 存储数据全集的 ⼀部分,从⽽构成⼀个更⼤的整体,称为 Redis 集群 (Cluster)。

如上图所示,在把全集数据平均分成多个部分,交给多个Redis主从服务器存储数据,这就是Redis集群,其中每个master与其对应的slave保存的是同样的数据,占总数据的1/3。
每个 Slave 都是对应 Master 的备份(当 Master 挂了, 对应的 Slave 会补位成 Master)。每个Redis主节点与其对应的从节点称为是⼀个 分⽚ (Sharding)。如果全量数据进⼀步增加, 只要再增加更多的分⽚, 即可解决。我们需要把全量数据尽可能的平均分给每个分片,以免造成Redis服务器压力不均衡,这个时候就需要用到分片算法了。Redis在设计时使用的便是哈希槽分区算法。
设有 N 个分⽚, 使⽤ [0, N-1] 这样序号进行编号。
针对某个给定的 key, 先计算 hash 值, 再把得到的结果 % N, 得到的结果即为分⽚编号。
例如, N 为 3. 给定 key 为 hello, 对 hello 计算 hash 值(⽐如使⽤ md5 算法), 得到的结果为bc4b2a76b9719d91 , 再把这个结果 % 3, 结果为 0, 那么就把 hello 这个 key 放到 0 号分片上。
后续如果要取某个 key 的记录, 也是针对 key 计算 hash , 再对 N 求余, 就可以找到对应的分⽚编号了。
优点: 简单高效, 数据分配均匀。
缺点: ⼀旦需要进行扩容, N 改变了, 原有的映射规则被破坏, 就需要让节点之间的数据相互传输, 重新排列, 以满足新的映射规则。此时需要搬运的数据量是比较多的, 开销较⼤。
为了降低上述的搬运开销, 能够更⾼效扩容, 业界提出了 "⼀致性哈希算法"。
key 映射到分⽚序号的过程不再是简单求余了, ⽽是改成以下过程:
这就相当于, N 个分⽚的位置, 把整个圆环分成了 N 个管辖区间. Key 的 hash 值落在某个区间内, 就归对应区间管理.

在这种情况下,扩容只需要原有分片在环上的位置不动, 只要在环上新安排⼀个分⽚位置即可。

优点: 大大降低了扩容时数据搬运的规模, 提⾼了扩容操作的效率。
缺点: 数据分配不均匀 (有的多有的少, 数据倾斜)。
为了解决上述问题 (搬运成本⾼ 和 数据分配不均匀), Redis cluster 引⼊了哈希槽 (hash slots) 算法。
hash_slot = crc16(key) % 16384crc16是一种哈希算法,16384其实就是2^14。
相当于是把整个哈希值, 映射到 16384 个槽位上, 也就是 [0, 16383].
然后再把这些槽位⽐较均匀的分配给每个分⽚. 每个分⽚的节点都需要记录⾃⼰持有哪些分⽚.
假设当前有三个分⽚, ⼀种可能的分配方式:
这⾥的分⽚规则是很灵活的. 每个分⽚持有的槽位也不⼀定连续. 每个分⽚的节点使⽤ 位图 来表⽰⾃⼰持有哪些槽位. 对于 16384 个槽位来说, 需要 2048 个字节(2KB) 大小的内存空间表⽰.
如果需要进⾏扩容, ⽐如新增⼀个 3 号分⽚, 就可以针对原有的槽位进⾏重新分配。
比如可以把之前每个分⽚持有的槽位, 各拿出⼀点, 分给新分片。
⼀种可能的分配方式:
接下来给大家教一种基于docker搭建集群的方法,只需要一台云服务器,每个节点都是一个容器。
具体步骤分为以下四步:
1.创建目录和配置
创建 redis-cluster ⽬录. 内部创建两个⽂件
redis-cluster/
├── docker-compose.yml
└── generate.shgenerate.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配置说明:
2.编写docker-compose.yml
此处的端⼝映射不配置也可以, 配置的⽬的是为了可以通过宿主机 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.1113.启动容器
使用docker-compose up -d命令启动容器。
4.构建集群
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执⾏之后, 容器之间会进⾏加⼊集群操作.
此时, 使⽤客⼾端连上集群中的任何⼀个节点, 都相当于连上了整个集群.
故障判定
集群中的所有节点, 都会周期性的使⽤⼼跳包进⾏通信。
⾄此, B 就彻底被判定为故障节点了.
某个或者某些节点宕机, 有的时候会引起整个集群都宕机 (称为 fail 状态). 以下三种情况会出现集群宕机:
故障迁移
一个节点A发生了故障,如果A是从节点不会发生故障迁移,但是如果A是主节点,那么便会触发故障迁移,所谓故障迁移,就是指把从节点提拔成主节点,继续给整个 redis 集群提供⽀持。
具体流程如下:
上述选举过程被称为Raft算法,是一种在分布式系统中广泛使用的算法。在随机休眠时间的加持下, 基本上就是谁先唤醒, 谁就能竞选成功。
扩容是⼀个在开发中⽐较常遇到的场景。
随着业务的发展, 现有集群很可能⽆法容纳⽇益增⻓的数据. 此时给集群中加⼊更多新的机器, 就可以使存储的空间更⼤了。
所谓分布式的本质, 就是使⽤更多的机器, 引⼊更多的硬件资源。
集群扩容我们可以分为三个步骤:
第一步:把新的主节点加⼊到集群
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379第⼆步: 重新分配 slots
redis-cli --cluster reshard 172.30.0.101:6379执⾏之后, 会进⼊交互式操作, redis 会提⽰⽤⼾输⼊以下内容:
第三步: 给新的主节点添加从节点
光有主节点了, 此时扩容的⽬标已经初步达成. 但是为了保证集群可⽤性, 还需要给这个新的主节点添加从节点, 保证该主节点宕机之后, 有从节点能够顶上。
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --clusterslave --cluster-master-id [172.30.1.110 节点的 nodeId]❤️😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍 🍔我是小皮侠,谢谢大家都能看到这里!! 🦚主页已更新Java基础内容,数据结构基础,数据库,算法,Redis相关内容。 🚕未来会更新Java项目,SpringBoot,docker,mq,微服务以及各种Java路线会用到的技术。 🎃求点赞!求收藏!求评论!求关注! 🤷♀️谢谢大家!!!!!!!!