完整教程:Redis 集群
文章目录
1.基本介绍
广义的集群:多个机器构成了分布式系统–》可用称为一个 “集群”
狭义的集群:redis提供的集群模式,这个集群下,主要是要解决存储空间不足的问题。(拓展存储空间)
哨兵模式提高了系统的可用性,但本质还是redis主从节点存储数据,要求一个主/从节点存储整个数据的全集。
扩展存储空间的关键点是 引入多台机器,每台机器存储一部分数据。
假设引入3台机器,就是每台机器存储1/3的数据。(存储数据的机器还要搭配若干个从节点)

三种主流的分片方式:
- 哈希求余
- 一致性哈希算法
- 哈希槽分区算法
2.哈希求余
把集群的数据分为3部分,每部分就是一个“分片”
借鉴哈希表的思想,借助hash函数,把key映射到整数,再对数组进行求余,得到一个下标。

MD5是一个非常广泛使用的hash算法。
特点:
1)md5计算结果是定长的。
无论原字符串多长,算出结果都是固定长度
2)计算结果是分散的【哈希函数】
两个字符串,哪怕大部分相同,只有一个小地方不同,差异都很大

3)MD5的计算结果是不可逆的。
给出MD5的值,很难还原出原字符串。(一些MD5破解,是把一些常见的MD5值算好保存下来,后面就找映射,一一对应,能不能查到就随缘了)
缺陷
一旦集群需要扩容,就需要更高的成本了。
分片的主要目的是为了提高存储能力。分片越多—》存储数据越多–》成本越高。
当“扩容”后–》分片变多–》上面哈希求余的“N”就发送变化–》映射关系变化–》重新对数据进行分配。

这些分片是一个主节点带着多个从节点,主节点变了,从节点也要进行搬运。—》开销极大,成本高—》往往不能在生产环境上操作,只能通过“替换”的方式进行扩容。
替换:原来的分片不变,加入新的机器,创建下面四个分片,把原来分片的数据导入新分片中,再用新分片代替旧分片。(不动在线分片,只换机器;离线建好新集群,一次性切换流量。)
依赖的机器更多了,成本高,操作复杂。
3.一致性哈希算法
hash求余的操作中,当前key属于哪个分片,是交替的。

一致性哈希 的设定下,把交替出现,改成了连续出现。
算出哈希值,顺时针最先找到几号分片,就属于几号分片。

虽然搬运的成本低了,但这几个分片上的数据量,可能不均匀(数据倾斜)
如果一次搞多个分片–》避免数据倾斜(搬运数据量比最初的hash求余要少)。但是需要很多的机器。成本大。
4.哈希槽分区算法
redis真正采用的分片算法。

16384 --》16*1024 =2^14 (16K)
把上述算出的哈希槽,分配到不同分片上。

实际的分片非常灵活,分片持有的槽位号可以连续,也可以不连续。
哈希槽分区算法 本质 就是把 哈希一致性 和 哈希求余 两种方式结合一下。
此处,每个分片都会使用“位图”来表示槽位号。
16384个bit位用0/1来区分这个分片当前是否持有该槽位号。
16384/8 ==2048 =2K

4.2 redis集群最多有16384个分片吗?
理论上一个分片上一个槽位就是16384个分片。
但此时就很难保证数据在各个分片上的均衡性。
key先映射到槽位,再映射到分片,如果分片的槽位数比较多,且每个分片的槽位数相当,那么key的数量就是相当的。
如果每个分片的槽位数少,可能就会数据倾斜,槽位的个数就不能反应key的个数了。
实际上,redis作者建议集群的分片数不成功1000
4.3为什么是16384个槽位?
为了确保“够用” 和 “别花太多的网络带宽”
16384足够把几千个分片了,可以把数据散到几千个主机。
节点间ping(心跳)包,包内要带“槽位”,周期性通信,很频繁。
16384–》2KB,对网络带宽的要求比较能接受。换成8Kb的网络带宽的开销就太大了。
5.搭建集群环境
当前只是在一个云服务器上搞的分布式系统,使用docker模拟。工作中是多台主机。

1)创建目录和配置

2)把前面的容器停掉


在Linux上,以.sh为后缀的文件,称为 “shell脚本”
完成批量的操作时,把批量的命令写入shell脚本,–》批量化执行。
需要创建11个redis节点,这些redis的配置文件内容大同小异,此时就可以使用脚本来批量生成。
3)写入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



每个redis下都有配置文件
shell脚本命令解释

seq 1 9 :Linux的命令,生成1-9
\ 是续行符,把下一行的内容和当前行合并成一行。shell默认情况下要求把所以代码写到一行中。

前面设置完1-9的redis实例的目录,下面设置10 11.每个实例文件中都有自己的配置文件,ip地址各不相同。

cluster-enabled yes :开启集群
cluster-config-file nodes.conf : 无需手写,redis自动生成配置文件
cluster-node-timeout 5000 : 多节点间交互,保持连通
cluster-announce-ip 172.30.0.1${port} : redis节点自己主机的IP,当前是docker模拟的主机,此处就是docker容器的IP
cluster-announce-port 6379 :业务端口,用来进行业务数据通信的(响应redis客户端请求)
cluster-announce-bus-port 16379 :管理端口,为了完成管理任务来进行通信(如果某个分片主节点挂了,就让从节点成为主节点,通过此端口完成)
6.创建容器
在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: 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
配置时要保证前后的一致性:redis.conf里的内容要和 yml中的内容对应。
启动服务器
启动服务器之前要把已经运行的redis都干掉。否则可能因为窗口冲突的原因导致启动失败。

docker-compose up -d




7.搭建集群环境,配置集群关系
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


3个分片,并给出了分片的槽位范围。

规定了谁是谁的从节点
主节点对应的槽位号范围也给定了
输入yes 后才真正构建集群
8.使用集群

可以通过容器的ip去连接,也可以通过映射的IP去连接
101-109这九个节点是一个整体。连接任意一个都行。根据你存储的数据映射到不同的槽位–》不同的分区(也就是不同的节点)
查看集群信息
cluster nodes

使用集群来存储数据

报错了。
当我们设置k后,当前数据就分片了,这个k通过hash计算后,得到的槽位号是7629,属于102的分片。
可以在启动redis-cli的时候,加上-c选项。
此时客户端发现k的操作不在当前分片上时,就会自动重定向到对应的分片主机上。
redis-cli -h 172.30.0.101 -p 6379 -c

现在客户端就把请求转发给了102的节点
加上-c选项后,redis客户端会根据当前k实际算出的槽位号,自动找到对应的分片主机。
9.故障处理
redis的基本指令

当存储在不同分区中,就无法一个命令同时操作了。
如果集群中,有节点挂了?
从节点挂了–》没事
主节点挂了–》无法进行写操作了。

可以看到101 102 103是主节点。

当在从节点进行写操作时,会先切换到主节点再写入。
主节点挂了
停掉redis1(主节点)

查看
可以看到101挂了,105晋升成了新的主节点,106成为了105的从节点。
再次重启redis1
再次查看主从结构
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 就彻底被判定为故障节点了。
先通过心跳包,没有pong的话再tcp,如果都没有回复就判定主观下线。
–》和其他节点沟通,如果半数的节点都认为判定节点主观下线
–》判定为客观下线(真下线了)—》消息传递给其他节点
故障迁移
如果挂掉的节点是从节点,那么不用故障迁移(从节点只读,可以用其他从节点代替)
如果挂掉的是主节点,就要故障迁移。
流程:
- 从节点判定自己是否具有参选资格。如果从节点和主节点已经太久没通信(此时认为从节点的数据和主节点差异太大了),时间超过阈值,就失去竞选资格。
- 具有资格的节点,比如 C 和 D,就会先休眠一定时间。休眠时间 = 500ms 基础时间 + [0, 500ms] 随机时间 + 排名 * 1000ms。offset 的值越大,则排名越靠前(越小)。
- 比如 C 的休眠时间到了,C 就会给其他所有集群中的节点,进行拉票操作。但是只有主节点才有投票资格
- 主节点就会把自己的票投给 C(每个主节点只有 1 票)。当 C 收到的票数超过主节点数目的一半,C 就会晋升成主节点。(C 自己负责执行 slaveof no one,并且让 D 执行 slaveof C)。
- 同时,C 还会把自己成为主节点的消息,同步给其他集群的节点。大家也都会更新自己保存的集群结构信息。
少了哨兵故障转移时,先选leader的流程,而是直接投票选出新的主节点。
集群宕机
以下三种情况会出现集群宕机:
- 某个分片,所有的主节点和从节点都挂了。
- 某个分片,主节点挂了,但是没有从节点。
- 超过半数的 master 节点都挂了。
10.集群扩容
引入集群的目的就是为了扩容。
现在有101-109 九个主机。现在把110和111也加入集群中。110为master 111为slave。
110入集群
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379



可以看到,110已经以主节点的形式加入集群中了
分配槽位号

110节点后面并没有槽位号的范围,要给它分配槽位号
redis-cli --cluster reshard 172.30.0.101:6379

现在问你想移动多少slot(槽位)。现在加上110节点一共4个master。16384/4=4096,所以移动4096个slot就行。===》变成4给分片

问你接收slot的节点的ID:

743785f9bdd7fd6d1080cd4dc7611efc6ea7e506

现在选择从哪些节点移动slots
a) 使用all --》所有持有slots的主节点都分配一点出来
b) 手动指定,从某个/些节点来移动slots (以done为结尾)
这里选all

输入yes后,搬运才真正开始,slots重新划分,slots上对应的数据也会搬运到新主机上。

查看

可以看到,此时新节点上分配好了槽位号
在搬运slots/keys的过程中,客户端能否访问redis集群?
搬运时,大部分key是不用搬运的,针对不用搬运的key,是可以正常访问的。
如果是访问正在搬运的key,就可能出现访问出错的情况。(客户端访问k1,集群通过分片算法,得到k1是第一个分片的数据,如果此时k1刚好被搬运走,那就无法访问了)
如果要追求高的可用性,让扩容对客户的影响更小,就要搞一组新的机器,重新搭建集群,把数据导入新集群中,再用新集群代替旧集群。
把从节点也添加到集群中
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id <master-node-id>
我这里的110的节点id是 : 743785f9bdd7fd6d1080cd4dc7611efc6ea7e506

所以我的指令是
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id 743785f9bdd7fd6d1080cd4dc7611efc6ea7e506

加入成功了

集群缩容:
从集群中取出一些节点,减少分片的数量。
不过一般都是扩容,很少缩容
浙公网安备 33010602011771号