在redis3.0之前,redis实现集群一般是借助哨兵sentinel工具来监控master节点的状态。当master节点异常,则会主从切换,将某一台slave作为master。哨兵的配置略微复杂,并且性能和高可用性等各方面表现一般,特别是主从切换的瞬间存在访问瞬断的情况。
而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发,且单个主节点内存也不宜设置过大,否则会导致持久化文件过大,影响数据恢复和主从同步的效率。
redis集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用、分片的特性。
redis集群不需要哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的 性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。
redis集群至少需要三个master节点。
这里使用了三台虚拟机,三个master节点,并给每个master节点配备一个slave节点,共6个redis节点。
###关闭防火墙###
# 执行这条命令需要确认三台机器之间的redis实例要能相互访问
# 可以先简单把所有机器防火墙关掉
# 如果不关闭防火墙则需要打开redis服务端口
# 和集群节点gossip通信端口16379(默认是在redis端口号上加1W)
# systemctl stop firewalld //临时关闭防火墙
# systemctl disable firewalld // 禁止开机启动
# (bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,
# 代表允许客户端通 过机器的哪些网卡ip去访问,
# 内网一般可以不配置bind,注释掉即可)
# bind 127.0.0.1
# 关闭保护模式
protected‐mode no
# 后台运行开启
daemonize yes
# 端口
port 8001
# 把pid进程号写入pidfile配置的文件
pidfile /usr/local/redis-cluster/8001/redis_8001.pid
# 日志
logfile "8001.log"
# rdb文件名
dbfilename dump-8001.rdb
# 指定数据文件存放位置
dir /usr/local/redis‐cluster/8001/
# 开启AOF (也可以开启混合持久化)
appendonly yes
# AOF文件名
appendfilename "appendonly-8002.aof"
###集群配置###
# 启动集群
cluster‐enabled yes
# 集群节点信息文件,最好和端口对应上
cluster‐config‐file nodes‐8001.conf
# 集群节点超时时间
cluster‐node‐timeout 10000
###密码设置(选配)###
# 设置redis访问密码
requirepass root
# 设置集群节点间访问密码
masterauth root
将修改后的配置文件,复制到其他节点中,将800x里的内容根据实际服务器情况进行替换。
最后,分别启动6个节点的redis服务。
ps -ef|grep redis //查看启动情况
redis5以前,集群是依靠ruby脚本redis‐trib.rb实现
# 只在集群第一次创建时,使用此命令。创建以后,下次直接启动节点就可以了。
# --cluster-replicas 1, 其中1代表为每个主节点创建一个从节点
# --cluster create 会默认将前三个输入的节点作为主节点
./redis-cli -a root --cluster create --cluster-replicas 1 192.168.74.121:8001
192.168.74.122:8002 192.168.74.123:8003 192.168.74.121:8004 192.168.74.122:8005
192.168.74.123:8006
验证集群:
# 连接任意一个客户端即可:
./redis‐cli ‐c ‐h ‐p (‐a访问服务端密码,‐c表示集群模式,-h指定ip地址 -p指定端口号)
如:/usr/local/bing/redis‐cli ‐a root ‐c ‐h 192.168.74.121 ‐p 8001
###验证###
# 可以查看nodes-800x.conf文件
# 查看集群信息
cluster info
# 查看节点列表
cluster nodes
注意:redis集群会给主节点配置不同服务器的从节点,保证如果一台服务器挂了,由于这台服务器上主节点的从节点在另一个服务器上,选举后可以保持可用。
关闭集群需要逐个进行关闭。
./redis‐cli ‐a root ‐c ‐h 192.168.0.60 ‐p 800*
--> shutdown
Redis Cluster 将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。
当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。
HASH_SLOT = CRC16(key) mod 16384
当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。
客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有key 将使用新的槽位映射表。
维护集群的元数据(集群节点信息、主从角色、节点数量、各节点共享的数据等),主要有两种方式:集中式和gossip
集中式:
集中式的优点在于对于元数据的读取和更新的时效性很好,一旦元数据出现变更就会立即更新到集中式的存储中,其他节点读取的时候立即就可以感知到。
集中式的不足在于所有的元数据集中在一个地方,元数据的存储压力增。很多中间件都会借助zookeeper集中式储存元数据。
gossip:
redis cluster采取的就是gossip协议进行通信
gossip协议包含多种消息:ping、pong、meet、fail等等。
gossip协议优点在于元数据的更新比较分散,不是集中在某个地方,更新请求会陆续发送到所有节点上,其他节点进行更新,所有有一定的延迟性,降低了压力;
gossip协议缺点在于元数据的延迟更新可能导致集群的一些操作会有一些滞后。
gossip通信的端口
redis每个节点都有一个专门用于节点间gossip通信的端口,默认是节点端口+10000。每个节点每隔一段时间都会往另外几个节点发送ping消息,同时其他节点接收到ping消息后,返回pong消息。
由于网络的不稳定行,网络抖动是非常常见的。Redis Cluster提供了
cluster-node-timeout
表示当某个节点持续timeout时间失联时,才会认定该节点宕机。如果没有这个选项,网络抖动会导致主从频繁切换(数据的重新复制)。
当slave发现自己的master节点变为fail状态时,便会尝试进行Failover,尝试成为新的master。由于宕机的master可能存在多个slave,所以多个slave会竞争成为master:
1、slave发现master变为fail
2、将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息
3、其他节点收到该信息,只有master节点响应,判断请求合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch请求只发送一次ack
4、尝试failover的slave收集master放回的FAILOVER_AUTH_ACK
5、slave收到超过半数master的ack后变成Master(这就是为什么集群至少需要三个节点,如果只有两个节点,其中一个挂了,只剩一个master是不能选举成功的)
6、slave广播pong消息通知其他集群节点
从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票。
•延迟计算公式:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
•SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。
redis集群没有过半机制会有脑裂问题,网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复,会将其中一个主节点变为从节点,这时会有大量数据丢失。
通俗来说,就是可能因为网络原因,主节点没有宕机,但是由于网络延迟超时没有gossip请求。其他节点误认为主节点宕机,重新选举出新的主节点。但是此时对之前的主节点的写操作是可以成功的,当网络恢复,新旧主节点中有一个会变为从节点,重新复制数据,导致期间的数据丢失。
规避方法:
可以在redis配置里加上参数(这种方法不可能百分百避免数据丢失,参考集群leader选举机制):
# 如果配置了min-replicas-to-write,健康的slave的个数小于N,mater就禁止写入。
# master最少得有多少个健康的slave存活才能执行写命令。
# 这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,
# master不能写入来避免数据丢失,设置为0是关闭该功能。
# 比如 集群总共三个节点可以配置1,加上leader就是2,超过了半数。
min‐replicas‐to‐write 1
注意: 这个配置在一定程度上会影响集群的可用性,比如slave要是少于1个,这个集群就算leader正常也不能
提供服务了,需要具体场景权衡选择。
当redis.conf的配置cluster-require-full-coverage
# 为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用。
# 为yes则集群不可用。
cluster-require-full-coverage no
因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。
奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的集群相比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的
对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上{XX},这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去,示例如下:
mset {user1}:1:name z {user1}:1:age 18
假设name和age计算的hash slot值不一样,但是这条命令在集群下执行,redis只会用大括号里的 user1 做hash slot计算,所以算出来的slot值肯定相同,最后都能落在同一slot。
传送门:https://blog.csdn.net/weixin_40955398/article/details/122666132