【Redis集群】集群原理最全解析

主从集群

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。

数据同步概念

Replication Id和offset

在从节点发起数据同步的请求中,有两个重要的属性:

  • Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid。
  • offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。

因此slave做数据同步,必须向master声明自己的replication id和offset,master才可以判断到底需要同步哪些数据。 当从节点发起主从同步请求时,主节点会判断从节点的replid是否一致,如果不一致,说明是第一次请求数据同步。

repl_baklog缓冲区

repl_baklog缓冲区是主从同步的重要机制,主节点在生成RDB文件期间会将命令记录到这个环形缓冲区中。

该缓冲区用于增量同步,确保从节点的偏移量与主节点保持一致。然而,如果从节点宕机且重启时缓冲区的数据已被覆盖,从节点就无法通过缓冲区恢复全部数据,导致数据不一致。

第一次数据同步

  1. 从节点发起数据同步请求,请求中携带replidoffset两个属性。
  2. 主节点需要判断replid是否与自己一致,如果不一致,说明从节点是第一次申请数据同步,主节点需要生成RDB文件并将RDB文件发送给从节点。由于生成RDB文件的过程是异步的,主节点同时会持续记录在生成RDB文件期间产生的所有命令。
  3. 从节点获取RDB文件后,清空本地数据并加载RDB文件进行数据同步。加载完成后,从节点获取缓存区的命令,执行命令同步数据。

全量同步

全量同步:是指主节点生成RDB文件并发送给从节点,从节点清空本地数据并加载RDB文件的过程。它通常发生在从节点首次连接到主节点或数据不一致的情况下。

全量同步的发生场景有两种:

  1. 从节点首次连接主节点: 当一个新的从节点第一次连接到主节点时,它没有任何数据副本。因此,需要进行全量同步来获取主节点的完整数据集。全量同步过程:
    1. 从节点发起数据同步请求,请求中携带replidoffset两个属性。
    2. 主节点需要判断replid是否与自己一致,如果不一致,说明从节点是第一次申请数据同步,主节点需要生成RDB文件并将RDB文件发送给从节点
    3. 从节点获取RDB文件后,清空本地数据并加载RDB文件进行数据同步。
  2. 主节点缓冲区超出容量: 主节点的repl_baklog缓冲区大小有上限,写满后会覆盖最早的数据。如果从节点断开时间过久,导致尚未备份的数据被覆盖,则主节点不能基于缓冲区做数据同步,只能再次使用全量同步获取RDB的完整数据集。

全量同步过程:

  1. 从节点发起数据同步请求,请求中携带replidoffset两个属性。
  2. 主节点需要判断replid是否与自己一致,如果不一致,说明从节点是第一次申请数据同步,主节点需要生成RDB文件并将RDB文件发送给从节点
  3. 从节点获取RDB文件后,清空本地数据并加载RDB文件进行数据同步。

增量同步

增量同步:是指主节点在RDB文件生成期间记录的所有命令(写操作)被存储在replication backlog缓冲区中,并在全量同步完成后发送给从节点,从节点执行这些命令的过程。增量同步用于保持主从节点之间的数据一致性。

增量同步的发生场景有两种:

  1. 全量同步后的持续增量同步: 在从节点完成初次的全量同步之后,主节点和从节点之间需要保持数据一致性。从节点会不断接收主节点的增量数据以更新其自身的数据状态。

  2. 从节点宕机重启后的增量同步: 当从节点因为故障、宕机或其他原因暂时失联,然后重新启动并重新连接到主节点时,主节点会尝试通过增量同步来恢复数据同步的状态。

增量同步过程:

  1. 从节点发起数据同步请求,请求中携带replidoffset两个属性。
  2. 主节点需要判断replid是否与自己一致,如果一致,说明从节点不是第一次申请数据同步了(即从节点之前进行了全量同步),主节点返回continue,允许从节点获取repl_baklog缓冲区的命令
  3. 从节点持续获取缓存区的命令,执行命令同步数据。

优化策略

可以从以下几个方面来优化Redis主从集群:

  • 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘I/O
  • Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘I/O
  • 适当提高repl_baklog缓存区的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
  • 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力

哨兵机制

在主从集群中,slave节点即使宕机,也可以从master节点上恢复数据。然而,如果master节点宕机,即使master节点做了持久化处理,在其重启后虽然能够恢复部分数据,但在重启和故障恢复的过程中,仍然可能会丢失大量数据,这对系统来说是不可接受的。

因此,为了解决上述问题,在主从集群的基础上引入了哨兵机制。哨兵机制的核心作用是监控主从集群中的各个节点,并在检测到master节点宕机时,自动从slave节点中选举一个新的master节点。

哨兵(Sentinel)机制的作用:

  • 服务状态监控: Sentinel会不断检查集群中的master和slave节点是否按预期工作
  • 自动故障恢复: 如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
  • 通知Redis客户端: Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

服务状态监控

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

  • 主观下线: 如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
  • 客观下线: 若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

自动故障恢复

选举新master节点

当sentinel检测到master节点客观下线时,需要在集群中选择一个slave节点作为新的master,选择依据是这样的:

  • 节点断开时间长短:首先会判断slave节点与master节点断开时间长短,如果超过指定值down-after-milliseconds * 10则会排除该slave节点
  • 优先级判断:slave从节点有slave-priority参数,越小优先级越高,如果是0则永不参与选举
  • 数据同步状态:如果从节点优先级一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
  • 节点 ID 大小:最后是判断slave节点的运行id大小,越小优先级越高,
进行故障转移

当节点2(master节点)故障后,sentinel选举节点1为新的master节点,故障转移步骤如下:

  • sentinel给备选的节点1发送slaveof no one命令,让节点1成为master
  • sentinel给其它所有的slave节点发送slaveof 192.168.150.101 7002命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
  • 最后,sentinel将故障节点标记为slave,当节点2恢复后会自动成为新的master的slave节点

通知Redis客户端

在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。

  1. 在pom文件中引入redis的starter依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 然后在配置文件application.yml中指定sentinel相关信息
spring:
 redis:
  sentinel:
   master: mymaster #指定master名称
   nodes: #指定redis-sentinel集群信息
    - 192.168.150.101:27001
    - 192.168.150.101:27002
    - 192.168.150.101:27003
  1. 配置读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
    return configBuilder -> configBuilder.readFrom(ReadFroM.REPLICA_PREFERRED);
}

这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择:

  • MASTER: 从主节点读取
  • MASTER_PREFERRED: 优先从master节点读取,master不可用才读取replica
  • REPLICA: 从slave(replica)节点读取
  • REPLICA_PREFERRED: 优先从slave(replica)节点读取,所有的slave都不可用才读取master

分片集群

主从复制和哨兵机制虽然解决了Redis的高可用性和高并发读的问题,但仍然面临以下两个挑战:

  1. 海量数据存储的问题:单个Redis实例的内存和存储容量有限,无法处理海量数据。
  2. 高并发写入的问题:单个主节点在高并发写入的场景下容易成为性能瓶颈。

Redis中的分片集群(Sharded Cluster)是一种将数据分布在多个Redis节点上的方式。通过将数据水平分片,分片集群能够在数据量增加时提升集群的存储容量,同时将写入压力分散到多个master节点上,提升整体性能。

Redis 分片集群的核心作用:

  • 数据水平扩展: 通过将数据分片存储在多个节点上,Redis 集群能够扩展到多个实例,以应对大规模数据存储和高并发请求。
  • 负载均衡: 将请求均匀分布到不同的分片节点上,避免单点压力过大,确保系统性能的稳定性。
  • 高可用性: 通过主从复制和自动故障恢复机制,Redis 集群能够在某个节点发生故障时,继续提供服务,确保系统的高可用性。

重要概念

散列插槽

Redis 集群通过哈希槽(Hash Slot)机制来分配数据到不同的分片节点上。整个哈希空间分为 16384 个槽,每个键根据其哈希值被分配到一个特定的槽中,而槽则由集群中的各个matser节点持有。

数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:

  • 如果key中包含{},且{}中至少包含1个字符,{}中的部分是有效部分
  • 如果key中不包含{},整个key都是有效部分

例如:key是num,那么就根据num计算,如果是{modox}num,则根据modox计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是插槽的slot值。

Redis客户端如何进行数据访问? 根据键的哈希值确定数据所在的分片节点,然后直接与该节点通信。

如何将同一类数据保存在同一个Redis节点上? 只需设置一个统一的有效部分,如{shopId}

配置纪元

配置纪元的作用是标识和跟踪集群配置的版本,确保集群中的所有节点在主节点故障转移和配置变更时保持一致

1.配置纪元是只增不减的整数

  • 每个主节点都有一个自身维护的配置纪元 (clusterNode.configEpoch),表示该主节点的版本。这个配置纪元是集群变更时用于标识和协调的关键因素。
  • 每个主节点的配置纪元都不同,以确保集群内的节点可以正确识别和处理最新的配置变更。

2.从节点会复制主节点的配置纪元

  • 当从节点与其对应的主节点同步时,它会复制该主节点的配置纪元。这样在主节点发生故障时,从节点可以使用这个配置纪元参与选举并成为新的主节点。

3.全局配置纪元

  • 整个集群维护一个全局的配置纪元 (clusterState.currentEpoch),记录集群内所有主节点的配置纪元中的最大版本号。这个全局纪元会在集群发生关键事件(如故障转移、添加/删除节点)时增加,以确保集群状态的一致性。

4.选举时选择纪元数最大的从节点

  • 在故障转移过程中,集群会优先选择配置纪元最大的从节点作为新的主节点。因为这个从节点的数据更可能是最新的,并且它在选举中更有可能获得其他主节点的支持。

服务状态监控

Redis分片集群的各个节点通过ping/pong进行消息通信,转播槽的信息和节点状态信息,故障发现也是通过这个动作实现的,类似于sentinel,有主观下线和客观下线。

  • 主观下线(PFAIL): 集群中的每个节点都会定期通过 PING-PONG 消息与其他节点通信。如果一个节点在指定时间内没有响应其他节点的 PING 请求,该节点会被标记为主观下线。
  • 客观下线(FAIL): 如果多个节点都将同一个节点标记为 PFAIL,那么通过投票机制,该节点将被标记为客观下线(FAIL)。这个状态会在集群中广播,所有节点都认同该节点已不可用。

故障恢复

选举新的master节点

Redis 分片集群和 Sentinel 机制在选举新的 master 节点时规则基本相同,唯一的区别在于节点断开时间的处理方式不同。

  • 节点断开时间长短:每个从节点检查与故障主节点的断线时间,断开时间超过cluster-node-timeout * cluster-slave-validity-factor则取消资格。cluster-slave-validity-factor : 默认是10
  • 优先级判断:slave从节点有slave-priority参数,越小优先级越高,如果是0则永不参与选举
  • 数据同步状态:如果从节点优先级一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
  • 配置纪元: 在故障转移过程中,集群会优先选择配置纪元最大的从节点作为新的主节点。因为配置纪元越大的从节点,数据更可能是新的。
进行故障转移

当新的 master 节点选举完成后,Redis 集群会自动进行故障转移,具体包括以下步骤:

  1. 提升新的 master 节点:Redis 集群通过内部命令 SLAVEOF NO ONE 将选中的从节点提升为新的 master 节点。
  2. 更新哈希槽映射:Redis 集群会自动更新哈希槽与节点的映射关系,新的 master 节点将执行 CLUSTER DELSLOTS 操作撤销故障主节点负责的槽,并执行 CLUSTER ADDSLOTS 把这些槽委派给自己。
  3. 重新配置和广播:Redis 集群将剩余的从节点重新配置为新 master 节点的从节点,并广播新的 master 信息给所有节点,确保集群内所有节点都更新哈希槽映射,并将新 master 的信息同步到其他节点。
  4. 节点重连:如果故障的 master 节点恢复上线,它通常会被重新配置为新的 master 的从节点,并同步数据以确保与新 master 保持数据一致性。

通知Redis客户端

RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致。

  1. 引入redis的starter依赖
  2. 配置分片集群地址
  3. 配置读写分离

与哨兵模式相比,只有yaml配置文件的配置方式存在差异,如下:

spring:
 redis:
  cluster:
   nodes: # 指定分片集群的每一个节点信息
    - 192.168.150.101:7001
    - 192.168.150.101:7002
    - 192.168.150.101:7003
    - 192.168.150.101:8001
    - 192.168.150.101:8002
    - 192.168.150.101:8003

集群伸缩

Redis 集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容,对节点进行灵活上下线控制,原理可抽象为槽和对应数据在不同节点之间灵活移动。

集群扩容

1.添加节点:

Redis分片集群提供了为现有集群添加新节点的,命令如下:

redis-cli --cluster add-node new_host:new_port existing_host:existing_port
--cluster-slave
--cluster-master-id <arg>

如果需要直接指定新节点为某一master的从节点,可使用如下命令:

redis-cli --cluster add-node <新节点IP>:<端口> <现有节点IP>:<端口> --cluster-slave --cluster-master-id <主节点ID>

2.迁移插槽:

可通过reshard命令将当前节点的散列插槽分配给其他节点。

redis-cli --cluster reshard <当前节点IP>:<端口>

接着Redis会提示需要移动多少插槽,自行输入即可。

How many slots do you want to move (from 1 to 16384)?

然后需要输入接收插槽的节点ID,确认后即可实现插槽的迁移

What is the receiving node ID?

3.添加从节点:

由于新的master节点相比其他主节点目前还没有从节点,因此该节点不具备故障转移的能力。

可以在从节点下使用cluster replicate 命令为主节点添加对应从节点(在分片集群下slaveof命令添加从节点的操作不再支持)。

cluster replicate <主节点ID>

从节点内部除了对主节点发起全量复制之外,还需要更新本地节点的集群相关状态。

集群缩容

1.迁移插槽:

缩容操作需要非常谨慎,因为如果下线的节点持有插槽,直接删除可能会引起数据一致性问题,因此需要将槽迁移给其他节点后才能安全下线,流程同上。

redis-cli --cluster reshard <当前节点IP>:<端口>

接着Redis会提示需要移动多少插槽,自行输入即可。

How many slots do you want to move (from 1 to 16384)?

然后需要输入接收插槽的节点ID,确认后即可实现插槽的迁移

What is the receiving node ID?

2.忘记节点:

在一个可用的节点上执行删除节点的命令:

redis-cli --cluster del-node <可用的节点IP>:<可用的节点端口> <需要删除的节点ID>

手动故障转移

在 Redis 集群中,手动故障转移允许管理员主动介入,以便在发现主节点故障时,迅速将其替换为副本节点,确保系统的持续可用性和稳定性。

此外,手动数据迁移还支持三种不同模式:

  • 缺省: 默认的流程,如图1~6步
  • force: 省略了对offset的一致性校验
  • takeover: 直接执行第5步,忽略数据一致性、忽略master状态和其它master的意见
  • 15
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值