关于Redis Cluster集群运维与核心原理

关于Redis Cluster集群运维与核心原理

  • 1、Redis集群方案比较
    • 哨兵模式
    • 高可用集群模式
  • 2、Redis高可用集群搭建
  • 3、Java操作redis集群
    • 1、Redis客户端Jedis操作
    • 2、集群的SpringBoot整合Redis
  • 4、Redis集群原理分析
    • 槽位定位算法
    • 跳转重定位
    • Redis集群节点间的通信机制
      • 集中式:
      • gossip:
        • gossip协议包含多种消息:
        • gossip协议优点:
        • gossip协议缺点:
      • **gossip通信端口**:
  • 5、网络抖动
  • 6、Redis集群选举原理分析
  • 7、集群脑裂数据丢失问题
  • 8、集群是否完整才能对外提供服务
  • 9、Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?
  • 10、Redis集群对批量操作命令的支持
  • 11、哨兵leader选举流程

1、Redis集群方案比较

哨兵模式


关于Redis Cluster集群运维与核心原理_第1张图片

在 Redis 3.0 之前,实现集群通常依赖于哨兵(Sentinel)工具来监控主节点的状态。当主节点异常时,哨兵会触发主从切换,将某个从节点晋升为新的主节点。然而,哨兵模式存在一些挑战,包括配置较为繁琐、性能和高可用性表现一般等问题。

特别是在主从切换的瞬间,可能存在访问中断的情况,这对于需要持续服务的应用来说是不可接受的。此外,哨兵模式限制了对外提供服务的主节点数量,无法支持很高的并发。同时,单个主节点内存设置过大可能导致持久化文件庞大,从而影响数据的恢复和主从同步的效率。

为了解决这些问题,Redis 在 3.0 版本引入了新一代的集群模式。这个集群模式克服了哨兵模式的一些限制,支持更好的水平扩展、高可用性和性能。它允许数据分布在多个节点上,每个节点都可以对外提供服务,避免了单一主节点的瓶颈。集群模式还支持自动分片,提高了系统的性能。升级到 Redis 集群模式可以带来更好的水平扩展性和高可用性,尤其适用于需要处理大量并发请求的应用场景。

高可用集群模式


关于Redis Cluster集群运维与核心原理_第2张图片

Redis 集群是一个由多个主从节点组成的分布式服务器群,它具备复制、高可用和分片等特性。与之前版本的哨兵模式相比,Redis 集群在性能和高可用性方面均有显著优势。不同于哨兵模式,Redis 集群不需要使用哨兵来完成节点移除和故障转移的功能。每个节点都需要被设置成集群模式,这种模式没有中心节点的概念,可以水平扩展,根据官方文档的称述,甚至可以线性扩展到上万个节点(尽管官方推荐不超过 1000 个节点)。

Redis 集群的配置相较于哨兵模式更为简单,其设计理念使得它更适合大规模的分布式应用。这个集群模式的灵活性和水平扩展性为系统的性能提供了更多的可能性。整体而言,采用 Redis 集群能够为应用提供更稳定、高效的服务,尤其在处理大规模并发请求和维护高可用性方面具备显著的优势。

2、Redis高可用集群搭建

Redis 集群需要至少三个 Master 节点,以三个为例。

搭建 Master 节点,并且给每个 Master 各搭建一个 Slave 节点,总共 6 个,使用三台服务器部署,每台机器一主一从:

  1. 创建文件夹
# 在/usr/local下创建redis-cluster文件夹
mkdir -p /usr/local/redis-cluster

# 在redis-cluster下分别创建8001和8004文件夹
mkdir /usr/local/redis-cluster/8001
mkdir /usr/local/redis-cluster/8004
  1. 配置文件修改

将之前的 redis.conf 配置文件复制到 8001 下,然后修改如下内容:

# redis-cluster/8001/redis.conf
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
# bind 127.0.0.1(注释掉bind,内网一般可以不配置)
protected-mode no
appendonly yes
requirepass autumn
masterauth autumn
  1. 批量替换端口号
# 在8001的目录下执行以下命令
sed -i 's/8001/8004/g' /usr/local/redis-cluster/8001/redis.conf
  1. 启动Redis实例
# 启动6个redis实例
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/800*/redis.conf
# 检查是否启动成功
ps -ef | grep redis
  1. 创建Redis集群
# 创建整个redis集群
/usr/local/redis-5.0.3/src/redis-cli -a autumn --cluster create --cluster-replicas 1 192.168.0.61:8001 192.168.0.62:8002 192.168.0.63:8003 192.168.0.61:8004 192.168.0.62:8005 192.168.0.63:8006 

  1. 验证集群
# 连接任意一个客户端
/usr/local/redis-5.0.3/src/redis-cli -a autumn -c -h 192.168.0.61 -p 800*
# 进行验证: cluster info(查看集群信息)、cluster nodes(查看节点列表)
# 进行数据操作验证
# 关闭集群
/usr/local/redis-5.0.3/src/redis-cli -a autumn -c -h 192.168.0.60 -p 800* shutdown

3、Java操作redis集群

1、Redis客户端Jedis操作

  1. 引入依赖:
<dependency>
  <groupId>redis.clientsgroupId>
  <artifactId>jedisartifactId>
  <version>2.9.0version>
dependency>
  1. 访问代码:
public class JedisClusterTest {
    public static void main(String[] args) throws IOException {

        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(20);
        config.setMaxIdle(10);
        config.setMinIdle(5);

        Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
        jedisClusterNode.add(new HostAndPort("192.168.0.61", 8001));
        jedisClusterNode.add(new HostAndPort("192.168.0.62", 8002));
        jedisClusterNode.add(new HostAndPort("192.168.0.63", 8003));
        jedisClusterNode.add(new HostAndPort("192.168.0.61", 8004));
        jedisClusterNode.add(new HostAndPort("192.168.0.62", 8005));
        jedisClusterNode.add(new HostAndPort("192.168.0.63", 8006));

        JedisCluster jedisCluster = null;
        try {
            //connectionTimeout:连接一个 url 的连接等待时间
            //soTimeout:连接上一个 url,获取 response 的返回等待时间
            jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "autumn", config);
            System.out.println(jedisCluster.set("cluster", "autumn"));
            System.out.println(jedisCluster.get("cluster"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedisCluster != null)
                jedisCluster.close();
        }
    }
}

运行结果:
OK
autumn

2、集群的SpringBoot整合Redis

  1. 引入相关依赖:
<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

<dependency>
   <groupId>org.apache.commonsgroupId>
   <artifactId>commons-pool2artifactId>
dependency>
  1. springboot项目核心配置:
server:
  port: 8080

spring:
  redis:
    database: 0
    timeout: 3000
    password: autumn
    cluster:
      nodes: 192.168.0.61:8001,192.168.0.62:8002,192.168.0.63:8003,192.168.0.61:8004,192.168.0.62:8005,192.168.0.63:8006
   lettuce:
      pool:
        max-idle: 50
        min-idle: 10
        max-active: 100
        max-wait: 1000
  1. 访问代码:
@RestController
public class IndexController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/test_cluster")
    public void testCluster() throws InterruptedException {
       stringRedisTemplate.opsForValue().set("autumn", "666");
       System.out.println(stringRedisTemplate.opsForValue().get("autumn"));
    }
}

4、Redis集群原理分析

Redis Cluster 的原理是基于槽位划分的分布式架构。在这个集群中,所有数据被分成 16384 个槽位,每个节点负责管理其中的一部分。每个节点存储了整个集群槽位的信息。

当客户端连接到 Redis Cluster 时,即会获取一份集群的槽位配置信息,并将其缓存在客户端本地。当客户端需要访问某个特定的 key 时,便可直接定位到负责该槽位的目标节点,实现了高效的数据定位。

由于槽位信息可能会在客户端与服务器之间不一致,Redis Cluster 还实现了校验和调整机制,以确保槽位信息的一致性。

这种基于槽位划分的方式使得 Redis Cluster 具有良好的水平扩展性,每个节点只需要管理一部分槽位,使得集群能够应对大规模数据和高并发的场景。

槽位定位算法

Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。

HASH_SLOT = CRC16(key) mod 16384

跳转重定位

当客户端向一个错误的节点发出了指令,该节当客户端向一个错误的节点发送指令时,该节点会察觉到指令涉及的 key 并不在自己负责的槽位上。这时节点会向客户端发送一条特殊的跳转指令,带有正确节点的地址,告知客户端去连接这个节点以执行操作。客户端接收到这个指令后,不仅会切换到正确的节点执行操作,还会更新本地的槽位映射表缓存,确保后续的所有 key 都使用新的槽位映射表。这个过程实现了客户端与集群之间的自动纠正机制,确保了数据的准确获取。

关于Redis Cluster集群运维与核心原理_第3张图片

Redis集群节点间的通信机制

Redis 集群节点之间采用 gossip 协议进行通信,以维护集群的元数据(包括节点信息、主从角色、节点数量、共享数据等)。

有两种元数据维护方式:

集中式:

  • 优点:时效性好,一旦元数据变更,即刻更新到集中式存储,其他节点能即时感知。
  • 不足:更新压力集中在一个地方,可能导致存储压力,常用中间件如 ZooKeeper。

gossip:

gossip协议包含多种消息:
  • meet:新节点发送 meet 给现有节点,请求加入集群,然后开始与其他节点通信。
  • ping:节点频繁发送 ping 给其他节点,携带自身状态和维护的集群元数据,通过交换 ping 来更新元数据。
  • pong:对 ping 和 meet 的返回,包含自身状态和其他信息,用于广播和更新。
  • fail:某节点判断另一节点宕机后,发送 fail 通知其他节点。
gossip协议优点:
  • 分散元数据更新,避免集中压力。
  • 更新请求分散,降低集中更新压力。
gossip协议缺点:
  • 元数据更新有一定延时,可能导致集群操作滞后。

gossip通信端口

  • 每个节点有一个专门用于 gossip 通信的端口,即服务端口号 +10000。例如,服务端口为 7001,则 gossip 通信端口为 17001。
  • 每个节点定期向其他节点发送 ping 消息,其他节点接收后返回 pong 消息。

采用 gossip 协议的通信机制能够实现分布式集群的信息同步,通过节点之间的消息交换,维护集群的一致性和稳定性。

5、网络抖动

网络抖动是指网络连接在短时间内发生不稳定的波动,有时连接可能中断,然后又迅速恢复正常。在机房网络中,这样的情况很常见。对于 Redis Cluster 这样的分布式系统,这种抖动可能导致节点之间的通信问题。

为了处理这种情况,Redis Cluster 提供了 cluster-node-timeout 这个选项。这个选项定义了一个节点持续失联的时间阈值。只有当某个节点在一定时间内持续失联,系统才会认为该节点出现了故障,需要进行主从切换。

如果没有这个选项,网络抖动可能导致节点频繁地被认为是故障节点,触发主从切换。这会导致数据频繁地重新复制,增加系统的负担和延迟。

6、Redis集群选举原理分析

  1. 主从节点发现故障: 当一个从节点(slave)发现自己的主节点(master)变为 FAIL 状态时,表示主节点出现了故障。
  2. 记录当前 Epoch: 从节点记录当前集群的 Epoch 值,然后加 1,以确保新的主节点具有更高的 Epoch 值。
  3. 广播选举请求: 从节点向整个集群广播 FAILOVER_AUTH_REQUEST 信息,表示它准备发起选举成为新的主节点。
  4. 主节点响应: 集群中的其他节点接收到选举请求,只有当前主节点(FAIL 状态的那个)会响应。主节点判断请求的合法性,并发送 FAILOVER_AUTH_ACK。
  5. 收集 ACK: 试图进行 Failover 的从节点收集其他主节点返回的 FAILOVER_AUTH_ACK,确保收到的 ACK 超过半数。
  6. 成为新 Master: 当从节点收到超过半数主节点的 ACK 后,它会成为新的主节点。这也解释了为什么 Redis 集群至少需要三个主节点,以确保在一个主节点挂掉时仍然可以选出新的主节点。
  7. 广播 Pong消息: 新的主节点广播 Pong 消息通知其他集群节点,确保整个集群知道它已经成为新的主节点。
  8. 延迟机制: 从节点不会立即尝试发起选举,而是有一定的延迟。这确保了在主节点故障被传播到整个集群之前,不会有多个从节点同时尝试进行选举。延迟计算公式包括了一个基础延迟、一个随机延迟和一个与复制数据总量有关的延迟,这确保了持有最新数据的从节点将首先发起选举。

通过这个机制,Redis集群能够在主节点故障时快速、安全地进行主从切换,确保系统的高可用性。

7、集群脑裂数据丢失问题

在 Redis 集群中,如果没有足够的机制来确保半数以上的节点参与写服务,可能发生脑裂问题。脑裂是指网络分区发生后,多个主节点继续对外提供写服务,导致数据不一致。当网络分区解决后,其中一个主节点可能被变为从节点,这时会发生大量数据丢失。

为了规避这个问题,可以在 Redis 配置中添加一些参数。例如:

min-replicas-to-write 1

这个参数表示写数据成功所需的最少同步从节点数量。这个数量可以设置为模仿大于半数机制的配置。比如,如果集群总共有三个节点,可以设置为 1,再加上 leader 节点就是 2,这超过了半数。

这样的配置在一定程度上可能会影响集群的可用性。如果从节点少于 1 个,即使 leader 节点正常,集群也无法提供服务。在具体场景中,需要权衡选择,根据可靠性和可用性的要求进行配置。

8、集群是否完整才能对外提供服务

在 Redis 集群中,有一个配置项叫做 cluster-require-full-coverage,值分为两种:

  • no:即使集群中某个主库下线了而且没有相应的从库进行故障恢复,整个集群仍然可以对外提供服务。简单说,即使有一部分节点出了问题,集群还是能继续工作。
  • yes:只有当所有负责一个插槽的主库都在线,并且有相应的从库进行故障恢复时,整个集群才会对外提供服务。简单说,所有的节点都必须正常,缺一不可。

这个配置的选择通常取决于对可用性和一致性的需求。若对可用性要求比较高选"no";若对一致性要求比较高选"yes"。

9、Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?

在 Redis 集群中,有一个关键的机制是进行新的主节点选举。这个选举过程需要大于半数的集群主节点的同意才能成功。现在我们来看一下为什么需要至少三个master节点:

  1. 故障容忍: 如果只有两个master节点,当其中一个挂掉时,就不足以满足大于半数的同意条件,新master节点选举将无法进行。因此,三个节点是最小的合理配置,以确保即使一个节点失效,仍然可以选举出新的master节点。
  2. 奇数的好处: 推荐节点数为奇数是因为奇数个节点更容易满足大于半数的条件。假设我们有三个节点,只需要两个节点同意就可以进行选举。而在四个节点的情况下,需要至少三个节点同意才能进行选举。奇数节点更容易在出现问题时维持大多数节点的正常运行。

总的来说,三个节点是为了确保基本的故障容忍,而奇数个节点则更有利于在选举等场景中更容易达到大多数同意的条件

10、Redis集群对批量操作命令的支持

在 Redis 集群中,有一些批量操作命令,如 mset(设置多个key的值)、mget(获取多个key的值)等。但是需要注意的是,对于这些操作,Redis 集群只支持所有的 key 都位于同一个 slot 槽位的情况。而如果有多个 key,确保它们都落在同一个 slot 槽位可能有些困难。

为了解决这个问题,可以使用一种特殊的写法。在key的前面加上大括号 {},比如 {user1}:1:name。这样 Redis 在计算 hash slot 时只会考虑大括号里的值,而不考虑其他部分。这就确保了不同的 key 在计算 slot 时会得到相同的结果,最终它们会被映射到同一个 slot 槽位上。

举个例子,假设 nameage计算的 hash slot 值不一样,但是通过在命令中使用 {user1},Redis 只会用这个大括号里的值进行 hash slot 计算,因此这两个 key 最终都能落在同一个 slot 槽位上。这种写法确保了在集群中执行这样的批量操作时,数据能够被正确映射到同一个 slot 槽位,保证了操作的一致性。

11、哨兵leader选举流程

  1. 主从节点发现故障: 当一个从节点(slave)发现自己的主节点(master)变为FAIL状态时,表示主节点出现了故障。
  2. 记录当前Epoch: 从节点记录当前集群的Epoch值,然后加1,以确保新的主节点具有更高的Epoch值。
  3. 广播选举请求: 从节点向整个集群广播FAILOVER_AUTH_REQUEST信息,表示它准备发起选举成为新的主节点。
  4. 主节点响应: 集群中的其他节点接收到选举请求,只有当前主节点(FAIL状态的那个)会响应。主节点判断请求的合法性,并发送FAILOVER_AUTH_ACK。
  5. 收集ACK: 试图进行Failover的从节点收集其他主节点返回的FAILOVER_AUTH_ACK,确保收到的ACK超过半数。
  6. 成为新Master: 当从节点收到超过半数主节点的ACK后,它会成为新的主节点。这也解释了为什么Redis集群至少需要三个主节点,以确保在一个主节点挂掉时仍然可以选出新的主节点。
  7. 广播Pong消息: 新的主节点广播Pong消息通知其他集群节点,确保整个集群知道它已经成为新的主节点。
  8. 延迟机制: 从节点不会立即尝试发起选举,而是有一定的延迟。这确保了在主节点故障被传播到整个集群之前,不会有多个从节点同时尝试进行选举。延迟计算公式包括了一个基础延迟、一个随机延迟和一个与复制数据总量有关的延迟,这确保了持有最新数据的从节点将首先发起选举。

通过这个机制,Redis 集群能够在主节点故障时快速、安全地进行主从切换,确保系统的高可用性。不过为了高可用一般都推荐至少部署三个哨兵节点。推荐奇数个哨兵节点的原理跟集群奇数个 Master 节点类似。

你可能感兴趣的:(redis,运维,分布式)