概述
上一篇中记录了传统的Redis主从架构
和哨兵架构
。其中哨兵架构是为了提高主从架构的可用性。但是两者都会存在一定的弊端:
- 所有的数据全部存放在一个主从上,造成单机数据量过大。而Redis单机内存不宜设置过大,否则生成的持久化文件会很大,对于性能也有一定的影响。
- 哨兵集群,虽然能自动恢复,但是有一个选举,重新生成主节点,其他从节点成为新主节点的从节点的过程,会有一段时间的延迟,并且延迟不低。
所以对于高并发的场景,使用Redis的cluster集群架构更加合适,Redis的集群本身就是由多个主从节点构成的:
图片来源:图灵学院
不同的主从节点,可以都搭建在一台机器上,也可以搭建在不同的机器上,生产环境通常是后者。相比于主从-哨兵
将数据存放在一组节点中,cluster
通过hash算法将数据进行分片,存储在不同的主从组
上,如果有10个G的数据:
- 哨兵模式:都放在主节点,从节点持有一份同步。假设我有一主一从,主节点在63上,从节点在64上:
- 63:10G
- 64:同步10G
- 集群模式:每个主节点存一部分,数据不重叠。(Hash分片算法),假设整个集群是三主三从,63,64,62各一主一从:
- 63:2G + 2G
- 64:3G + 3G
- 62:5G + 5G
可以看出数据分片存储的方式,极大地缓解了每台服务器的内存压力。
同时cluster模式也有选举机制。无法完全解决哨兵模式下的访问瞬断问题,但是可以缓解。因为集群中有多个主从,一个主从发生了访问瞬断,不会影响其他节点的数据访问。
Redis-cluster至少三个主节点,并且读写都是走主节点,从节点就是数据备份,在构建cluster时,会将当前内存划分为16384个hash逻辑分片。整个逻辑存储区域,有16384个小格子,将格子映射到对应的节点。
一、集群搭建
首先需要准备三个不同IP地址的虚拟机,Redis的集群至少要有三个节点,在三台机器上搭建三个主-从架构
的集群,每个节点一主一从。
启动三台虚拟机,查看ip地址,直接使用ip addr
命令可能展示的内容过多,关键信息无法查看
可以通过ip addr > ip.txt
命令,将查询出的信息导入到指定目录下的txt文件中,然后vim ip.txt
进行查看。
在第一台机器上的usr/local
目录下mkdir redis‐cluster
:
然后在redis‐cluster
文件夹中再创建两个文件夹:
将redis.conf
文件复制到8001下,并且修改:
daemonize yes
port 8001
pidfile /var/run/redis_8001.pid
dir /usr/local/redis‐cluster/8001/
cluster‐enabled yes
cluster‐config‐file nodes‐8001.conf
cluster‐node‐timeout 10000
protected‐mode no
appendonly yes
requirepass 123456 # 设置密码
masterauth 123456 # 设置集群间访问密码
#如果之前有 replicaof 192.168.101.128 6381 也要注释掉
同样地将该文件复制到本机下的8004文件夹
,修改其中关于端口的信息:
在另外两台机器上都是同样的操作,第二台机器:8002和8005,第三台机器8003和8006
然后启动8001到8006的六个实例,并且查看是否启动成功:
第一台机器上的8002、8005
第二台机器上的8001、8004
第三台机器上的8003、8006
用redis‐cli
创建整个redis集群:
# -a 后的参数是密码
# 1 代表每个主节点后有一个从节点
# ip端口的对应关系要注意
# 关闭防火墙
# systemctl stop firewalld # 临时关闭防火墙
# systemctl disable firewalld # 禁止开机启动
./redis-cli -a 123456 --cluster create --cluster-replicas 1 \
192.168.101.129:8001 192.168.101.128:8002 192.168.101.130:8003 \
192.168.101.129:8004 192.168.101.128:8005 192.168.101.130:8006
输入yes
当前集群架构:
主节点1:192.168.101.129:8001 从节点1:192.168.101.129:8005 占有0-5460 共 5431 个hash槽位
主节点2:192.168.101.128:8002 从节点2:192.168.101.128:8006 占有5461-10922 共 5462个hash槽位
主节点3:192.168.101.130:8003 从节点2:192.168.101.130:8004 占有10923-16383 共 5461个hash槽位
可以看出并非是同一台机器上的节点作为一组主从,而是主,从节点在不同的机器上,为了保证同一时刻内集群中主节点的个数都是一致的。
后续连接客户端,需要使用./redis‐cli ‐c ‐h ‐p
命令:
# -c 代表集群
# -h -p ip和端口
# -a 密码
./redis-cli -a 123456 -c -h 192.168.101.128 -p 8002
cluster info
命令查询集群信息:
cluster nodes
命令查询节点列表:
二、Hash槽位
当从Redis客户端发起向集群中存储元素的命令时,客户端底层会执行对key进行hash确定槽位的操作:
实际上就是通过对存入的key进行hash运算的结果 % 16384
的操作,确定key的逻辑槽位:
public class CRC16 {
public static void main(String[] args){
String str="test";
System.out.println(JedisClusterCRC16.getCRC16(str)%16384);
System.out.println(JedisClusterCRC16.getCRC16(str)& (16384 - 1));
}
}
6918
6918
是在当前集群中的8002-8006主从组
中,如果我在8001中发起set操作,那么对应的请求会被重定向
到8002上:
如果进行集群节点的迁移,也是同样的道理。客户端发送set命令,实际上是通过socket通信,利用resp协议,将数据发送给服务端,服务端再进行解析。进行集群节点迁移,比如将63上的主从迁移到了64上,如果客户端发送命令还是到旧的机器63上,服务端会发起一个重定向的命令,客户端向迁移后的节点发起连接。
因为hash槽的机制,对于一些批量操作的命令,需要进行一定的特殊处理,例如mset
:
如果直接设置,是设置不成功的,因为mset
等批量操作的命令需要保证原子性
。而key可能被路由到不同范围的hash槽上,例如zhangsan可能被路由到8001,而age可能被路由到8002。两者不在同一个服务器上,如果8002挂了,那无法保证整条命令的原子性。所以需要保证批量的所有key都在同一个hash槽的范围内。解决方式是加入{XX}
的前缀,这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去:
三、集群选举和脑裂问题
3.1、集群选举
假设集群中的某个主从组的主节点挂了,那么从节点会通知集群开始选主。
- 如果从节点发现自己的主节点挂了,并且超过了超时时间,就会将集群的选举周期 + 1 ,并广播错误转移的请求。(并非是主节点挂了之后从节点立刻就发起选举,而是有一个延迟时间,便于其他主节点知道当前集群中有主节点挂了,延迟计算公式中有一个随机时间,就是为了减小从节点平票的概率)
- 其他的主从都会接收到消息,但是只有主节点会响应,并判断合法性,然后发送ack。每个主节点,只会给第一个给它发送请求的从节点ack。
- 主节点宕机的从节点会收集所有的ack并且计算,如果某个从节点获取到了超过主节点一半的ack,就会成为新的主节点(如果出现票数相同的情况?进入下一轮重新选举。)
- 然后向其他节点发送ping 命令,通知其他节点自己成为了主节点。
在这个过程中,有一个关键的指标是超时时间
,可以在配置文件中进行设置:
3.2、脑裂问题
超时时间不宜设置过短,是为了避免接下来提到的脑裂问题。如果超时时间设置过短,那么发生了网络波动,从节点会误认为主节点已经宕机了,从而发起选举,有可能会导致同一个主从组中存在一个以上的主节点,这就称之为脑裂问题
。
如果一个主从组中存在一个以上的主节点,都可以接受客户端的写请求。网络分区恢复后,其中一个主节点会成为另一个主节点的从节点,主节点会向该从节点同步自己的数据(前篇中提到,从节点在接收到主节点数据同步之前,要先清理自己的缓存),那么在分区过程中的该主节点接受客户端的所有数据都会被覆盖丢失
。
可以通过min‐replicas‐to‐write
配置,避免该问题:
min‐replicas‐to‐write
配置的含义是,集群中n个节点写成功,服务端才会通知客户端成功。
假设我有一个集群,其中的一个主从组
:主,从1,从2。发生了脑裂:
- 主(网络分区1)
- 从1->主 从2(网络分区2)
将min‐replicas‐to‐write
配置为1,只有网络分区2才可以写入。
这种方案的缺点,**为了保证发生脑裂问题时的一致性,牺牲了集群的可用性。**假设某个主从的所有从节点都挂了,主节点也就无法对外提供服务了。Redis是AP架构,需要优先保证可用性,保证高并发时数据不会让数据库宕机。
3.3、集群节点个数选择
集群至少要三个主节点,并且推荐个数为奇数(并非一定要奇数)。为何强制要求三个及以上主节点?假设集群设置两个主节点,然后master1宕机了,那么master1的两个从节点要发起投票,salve1最先获取到了master2的ack,获得了一票,但是成为主节点的条件是获取到了超过主节点一半的ack,所以slave1无法成为主节点,整个集群都无法对外提供服务。
为何推荐集群中节点的个数为奇数?
- 如果集群中有三个节点,那么最多只能有一个主节点宕机,否则从节点投票不能满足获取到了超过主节点一半的ack的条件。
假设两个主节点宕机,slave1最先得到master3的ack,但是至少要有2票才能成为新的主节点
- 如果集群中有四个节点,那么同样是最多只能有一个主节点宕机,否则从节点投票不能满足获取到了超过主节点一半的ack的条件。
假设两个主节点宕机,slave2最先得到master3和master4的ack,但是至少要有4 / 2 + 1 = 3票才能成为新的主节点
实际上偶数个数的节点,和奇数个数的节点,允许的主节点宕机数量是相同的,如果上面的案例,设置5个节点,那么同一时刻最多允许2个主节点宕机。