• 【Redis】深入探索 Redis 集群(Cluster)模式的概念、原理、数据分片算法,基于 Docker 模拟搭建 Redis 集群分布式架构



    一、对 Redis Cluster 集群模式的认识

    1.1 Redis 集群模式的概念

    Redis Cluster 是一种分布式集群模式,它允许将数据分散存储在多个节点上,从而提供了横向扩展、高可用性和更大存储容量。Redis Cluster 有以下关键特点:

    • 分布式数据存储: Redis Cluster 将数据划分为多个槽(slot),这些槽分布在不同的节点上。每个节点负责管理一部分槽中的数据,这样数据可以分布在多个节点上,避免了单节点存储容量的限制。

    • 自动数据分片: Redis Cluster 使用哈希槽来分片数据,客户端根据键的哈希值将数据路由到正确的节点。这使得数据的分片和路由是自动的,不需要手动管理数据分片。

    • 高可用性: Redis Cluster 提供了内置的高可用性机制。数据会在多个节点之间复制,每个槽位都会有一个主节点和多个从节点。如果主节点失效,系统会自动选举一个从节点作为新的主节点,确保数据的可用性。

    1.2 Redis 集群模式解决的问题

    Redis 集群模式是为了解决在大规模数据存储和高可用性需求下出现的问题而引入的。在前面的文章中介绍了 Redis 哨兵模式,哨兵模式提供了系统的高可用性,但实际上所有的数据都需要储存到单个 master 和其对应的 slave 节点中。这种架构在面对大规模数据存储的挑战时会遇到一些问题,主要包括以下方面的挑战和限制:

    1. 内存限制: Redis 是内存数据库,数据存储在内存中以提供快速访问。如果数据量非常大,接近或超出了单个 master 和其对应的 slave 节点的物理内存容量,就会出现内存不足的问题。这会导致性能下降,甚至系统崩溃。

    2. 水平扩展问题: 在哨兵模式下,要扩展存储容量或处理更多的请求,通常需要升级硬件,将单个节点的内存容量增加到更大的规模。但这种垂直扩展的方式存在成本高昂和物理限制的问题,因此在大规模数据存储需求下不够灵活。

    为了解决这些问题,Redis 引入了集群模式。Redis 集群模式的核心思想是利用多组 Master/Slave 节点来分散存储数据,并提供更大的内存容量和高可用性以下是 Redis 集群模式解决的问题和优势:

    问题1:内存限制

    Redis 集群模式通过将数据分散存储在多个节点上来解决内存限制问题。每个 Master 节点都可以存储数据的一部分,因此整个集群的总内存容量可以随着添加更多节点而线性增加。这使得 Redis 集群能够处理更大规模的数据,而无需单节点内存升级。

    问题2:水平扩展问题

    Redis 集群模式通过分片(Sharding)机制实现水平扩展。每个分片由一组 Master/Slave 节点管理,而不是依赖于单一节点的垂直扩展。这允许系统在需要时简单地添加更多分片,以满足不断增长的存储和请求需求。这种横向扩展的方式更加灵活,可以适应不同的工作负载和数据规模。

    问题3:高可用性

    Redis 集群模式提供了内置的高可用性支持。每个 Master 节点都有对应的 Slave 节点作为备份。如果一个 Master 节点发生故障,其对应的 Slave 节点可以自动升级为新的 Master 节点,从而确保数据的可用性和系统的持续运行。这种自动故障切换机制大大提高了系统的稳定性。

    示例:

    例如下面的例子,假定整个数据全集是 1 TB。通过引入三组 Master/Slave 节点来存储数据,每组机器只需要存储整个数据全集的 1/3。

    具体示例如下:


    在上图上:

    • Master1 和 Slave11 以及 Slave12 保存的是同样的数据,占总数据的 1/3。
    • Master2 和 Slave21 以及 Slave22 保存的是同样的数据,占总数据的 1/3。
    • Master3 和 Slave31 以及 Slave32 保存的是同样的数据,占总数据的 1/3。

    每个 Slave 都是对应 Master 的备份。当 Master 节点发生故障时,对应的 Slave 节点会自动升级为新的 Master,保持数据的可用性。

    通过这种方式,Redis 集群模式解决了在大规模数据存储和高可用性场景下所面临的内存限制、水平扩展和高可用性的问题,使 Redis 成为更加强大和可靠的数据存储解决方案。这种分布式架构允许 Redis 处理大容量数据,并在硬件性能或可用性方面进行灵活扩展,适应不断变化的业务需求。

    二、数据分片算法

    2.1 哈希求余算法

    哈希求余算法是一种简单而常见的数据分片算法,它用于将数据分散到不同的分片(Shard)中。这种算法通常用于分布式系统中,特别是在需要将数据存储在多台服务器上并确保均衡负载的情况下。以下是哈希求余算法的基本原理和问题:

    基本原理:

    假设有 N 个分片,每个分片都被分配一个编号,通常从 0 到 N -1。对于给定的数据键(key),通过以下步骤确定它应该存储在哪个分片上:

    1. 对数据键进行哈希运算,通常使用哈希函数(例如,MD5或SHA-1),得到一个哈希值。
    2. 将得到的哈希值取余 N,即哈希值 % N。这个结果就是数据键所属的分片编号。

    举例来说:

    假设有 3 个分片(N=3)并且需要存储一个数据键 “hello”。首先,对 “hello” 进行哈希运算(比如使用 MD5 算法),得到哈希值 “bc4b2a76b9719d91”。然后,将该哈希值取余 3,结果为 0。因此,“hello” 数据键应该被存储在编号为 0 的分片上。

    存在的问题:

    尽管哈希求余算法是一种简单有效的数据分片方法,但它也存在一些问题,特别是在需要扩容或缩容分片时:

    问题1: 扩容的问题

    当需要引入新的分片,将 N 从 3 增加到 4 时,原有的映射规则会被破坏。因此,需要重新计算每个数据键的哈希值并将其映射到新的分片编号。这可能需要大量的数据迁移,对系统性能产生负面影响。

    如下面的例子,当 N 为 3 时,我们计算了 21 个数据键的哈希值分布。但当引入新的分片后,只有 3 个数据键的哈希值保持不变,其他的数据键需要迁移到新的分片。这导致了数据搬迁的开销。

    扩容示例

    问题2: 数据不均匀分布

    另一个问题是哈希求余算法可能导致数据不均匀地分布在分片中。如果哈希值的分布不均匀,某些分片可能会负载过重,而其他分片负载较轻,这可能导致性能不均衡。

    综上所述,虽然哈希求余算法是一种简单的数据分片方法,但在需要扩容或面临数据不均匀分布的情况下,它可能会引发一些问题。因此,在选择数据分片算法时,需要权衡各种因素,并根据实际需求选择最合适的方法。其他分片算法,如一致性哈希算法和哈希槽分区算法,可以解决一些哈希求余算法存在的问题。

    2.2 一致性哈希算法

    一致性哈希算法是一种解决数据动态添加和删除时数据迁移问题的算法,广泛应用于分布式系统中。其基本思想是将数据键和分片使用哈希函数映射到一个环形空间中,每个分片负责环上某个范围内的数据。当需要添加或删除分片时,只有部分数据需要迁移,而不是全部数据。

    基本原理:

    1. 创建哈希环: 首先,将所有可能的数据键和分片使用哈希函数映射到一个环形空间上,通常是一个范围从 0 到 2^32-1 的圆环。这个环上的每个点对应一个哈希值。

      创建哈希环

    2. 分配分片: 接下来,将每个分片映射到环形空间上的某个点。分片的位置可以通过对分片标识(如分片名称或ID)进行哈希运算来确定。

      分配分片

    3. 数据映射: 当需要存储或查找数据时,使用相同的哈希函数将数据的键映射到环形空间上的一个点。然后,沿着环的顺时针方向找到离数据点最近的分片,将数据存储在该分片上。

      数据映射
      这就相当于,N 个分片的位置,把整个圆环分成了 N 个管辖区间,Key 的 hash 值落在某个区间内,就归对应区间管理。

    分区管理

    优点:

    • 数据迁移效率高: 一致性哈希算法在扩容或缩容分片时,只需要移动部分数据,而不需要重新分配所有数据,因此数据迁移的开销相对较小。
    • 负载均衡: 数据在环形空间上均匀分布,因此各个分片负载相对均衡,提高了系统性能和可扩展性。

    缺点:

    • 数据分布不均匀: 尽管一致性哈希算法通过哈希函数均匀分布数据,但在实际应用中,数据分布可能不均匀,导致某些分片存储的数据较多,而其他分片较少,可能引发数据倾斜问题。

    扩容示例:

    在一致性哈希算法中,扩容分片的操作相对高效。当引入新的分片时,只需要在环形空间上添加一个新的点,而不影响原有分片的位置。数据迁移仅涉及到那些需要映射到新分片位置的数据。

    扩容示例

    在上述示例中,只需将原先映射到分片 0 的一部分数据迁移到新的分片 3 即可,其他分片的数据不受影响。这减少了数据搬迁的规模,提高了扩容操作的效率。

    综上所述,一致性哈希算法是一种有效解决数据动态分片管理的算法,它减小了数据迁移开销,并支持负载均衡,但需要注意数据分布不均匀可能导致数据倾斜的问题。

    2.3 哈希槽分区算法

    为了解决数据动态添加和删除时数据迁移的高成本以及数据分配不均匀的问题,Redis Cluster 引入了哈希槽分区算法。这个算法的核心思想是将数据的键和分片使用哈希函数映射到一个环形空间中的不同槽位(hash slots)。每个分片负责管理一部分槽位,而不是特定的数据键。

    基本原理:

    1. 哈希函数计算哈希槽位: 对于每个数据键,使用哈希函数(例如,crc16)计算其哈希值,然后将哈希值对 16384 取模,得到的结果即为该数据键的哈希槽位编号。
    hash_slot = crc16(key) % 16384
    
    • 1
    • 这里的 16384 是槽位的总数,即 16 * 1024,也就是 214
    1. 分配哈希槽位给分片: Redis Cluster 将这些哈希槽位均匀地分配给每个分片,确保每个分片负责管理一部分槽位。每个分片的节点会记录它所持有的槽位范围。

      例如,有三个分片的分配方式可能如下:

      • 分片 0 负责槽位范围 [0, 5461]
      • 分片 1 负责槽位范围 [5462, 10923]
      • 分片 2 负责槽位范围 [10924, 16383]

      这里的分配是灵活的,槽位范围不一定要连续。

    2. 数据映射到哈希槽位: 当需要存储或查找数据时,使用相同的哈希函数将数据的键映射到一个哈希槽位编号。然后,根据哈希槽位编号找到负责该槽位的分片,将数据存储在该分片上。

      数据映射示例

    3. 扩容

    当需要进行扩容时,例如新增一个3号分片,可以根据原有的槽位重新分配。以下是一个可能的扩容示例:

    初始分配:

    • 0号分片: [0, 4095],共 4096 个槽位
    • 1号分片: [5462, 9557],共 4096 个槽位
    • 2号分片: [10924, 15019],共 4096 个槽位

    扩容后的分配:

    • 0号分片: [0, 3412],共 3413 个槽位
    • 1号分片: [5462, 6874],共 1413 个槽位
    • 2号分片: [10924, 12336],共 1413 个槽位
    • 3号分片: [3413, 4095] + [6875, 9557] + [12337, 15019],共 3414 个槽位

    这个示例展示了如何将原有的槽位重新分配以容纳新的分片。新的3号分片获得了一部分槽位,同时原有的槽位也被重新分配给其他分片,以保持槽位的均匀分布。这种方式降低了扩容时数据搬迁的规模,提高了扩容操作的效率。

    另外值得注意的是:在实际使用 Redis 集群分片时,无需手动指定哪些槽位分配给特定分片。只需告诉某个分片应该持有多少个槽位,Redis 会自动处理后续的槽位分配以及与之相关的数据迁移工作。这种自动化的槽位管理方式极大地简化了集群配置和维护的复杂性,使集群更容易扩展和管理。

    优点:

    • 数据迁移高效: 哈希槽分区算法在扩容或缩容分片时,只需要重新分配槽位,而不需要大规模的数据迁移,因此降低了数据迁移的成本和对系统性能的影响。

    • 负载均衡: 哈希槽分区算法通过将槽位均匀分布在不同分片上,实现了数据的负载均衡,提高了系统的性能和可扩展性。

    缺点:

    • 分片数不宜过多: 一般不建议 Redis Cluster 中分片数超过 1000,因为分片数过多可能导致网络心跳包通信的开销增加,同时也会使槽位配置的位图体积变得较大。

    • 不一定保证数据均匀分布: 尽管算法会尽量均匀分布槽位,但实际上数据分布不一定均匀,可能导致某些分片存储的数据较多,而其他分片存储的数据较少,引发数据倾斜问题。

    此处还有两个问题:

    问题一:Redis 集群是最多有 16384 个分片吗?

    Redis Cluster 并不限制槽位数为 16384,这个数量只是一个默认值。实际上,Redis Cluster 支持自定义槽位数量,但需要注意选择合适的槽位数量以兼顾性能和内存开销。

    问题二:为什么默认是 16384 个槽位?

    槽位数选择 16384 的原因是为了兼顾内存开销和通信效率。槽位数量较多会导致节点之间的心跳包通信开销增加,因为每个节点需要在心跳包中包含其所持有的槽位信息。同时,较大的槽位数量会导致位图(bitmap)体积变大,占用更多内存。因此,16384 被认为是一个适中的值,适用于大多数场景。

    三、基于 Docker 模拟搭建 Redis 集群

    在分布式系统中,Redis 集群是一种常见的架构,用于提高数据的可用性和性能。Redis 集群通过将数据分布在多个节点上来实现高可用性和负载均衡。下文将介绍如何使用 Docker 在本地模拟搭建一个 Redis 集群,并探讨集群的拓扑结构、配置文件生成、容器编排、启动、创建集群等步骤。

    3.1 目标集群的拓扑结构

    以下是通过 Docker 模拟搭建 Redis 集群时的目标拓扑结构:

    拓扑结构图:


    说明:

    • 在这个集群中一共有三个分片,即分片 0、分片 1 和分片 3。
    • 每一个分片都是一个 Redis 主从结构,每一个主从结构包括一个主节点和两个从节点。
    • 创建容器的时候为每个容器都分配了静态 IP,其范围是 172.30.0.101 ~ 172.30.0.111。
    • 前 9 个节点用来建立集群,后面两个容器用来演示集群的扩容。

    3.2 创建目录和配置文件

    为了搭建 Redis 集群,现在需要创建一个名为 redis-cluster 的目录,并在其中创建两个重要文件:docker-compose.ymlgenerate.sh

    redis-cluster/
    ├── docker-compose.yml
    └── generate.sh
    
    • 1
    • 2
    • 3

    其中,docker-compose.yml 文件用于编排和管理 Redis 容器,而 generate.sh 文件则是一个 Shell 脚本,用于批量生成 Redis 配置文件 redis.conf

    下面是 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    说明:

    • 这个脚本用于批量创建 Redis 配置文件 redis.conf,其中包含了一些基本配置,以及用于集群的配置参数。
    • 第一个循环生成了前 9 个节点的配置,注意 cluster-announce-ip 的值每个节点都不同,确保 IP 地址唯一性。
    • 第二个循环生成了后面两个用于演示集群扩容的节点的配置,同样也需要确保 cluster-announce-ip 的值唯一。
    • 执行脚本后,会在 redis-cluster 目录下创建多个子目录,每个子目录对应一个节点,包含了相应的配置文件。

    执行命令:

    bash generate.sh 
    
    • 1

    执行结果:

    redis-cluster/
    ├── docker-compose.yml
    ├── generate.sh
    ├── redis1
    │   └── redis.conf
    ├── redis10
    │   └── redis.conf
    ├── redis11
    │   └── redis.conf
    ├── redis2
    │   └── redis.conf
    ├── redis3
    │   └── redis.conf
    ├── redis4
    │   └── redis.conf
    ├── redis5
    │   └── redis.conf
    ├── redis6
    │   └── redis.conf
    ├── redis7
    │   └── redis.conf
    ├── redis8
    │   └── redis.conf
    └── redis9
        └── redis.conf
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以发现,生成 Redis 配置文件的脚本运行成功。

    查看生成的配置文件内容:

    generate.sh 脚本中,通过循环为每个 Redis 容器生成了独立的 redis.conf 配置文件。以下是 redis1 容器的配置文件示例,其他容器的配置文件类似,唯一不同之处是 cluster-announce-ip 参数:

    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.101
    cluster-announce-port 6379
    cluster-announce-bus-port 16379
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    说明:

    • port 6379: 指定 Redis 服务器监听的端口号。
    • bind 0.0.0.0: 允许远程连接到 Redis 服务器。
    • protected-mode no: 禁用保护模式,以便允许集群节点之间的通信。
    • appendonly yes: 启用持久化日志,以便数据持久化。
    • cluster-enabled yes: 启用 Redis 集群支持。
    • cluster-config-file nodes.conf: 指定集群配置文件的名称。
    • cluster-node-timeout 5000: 设置节点超时时间,单位为毫秒。
    • cluster-announce-ip: 此参数配置了节点在集群中进行通信的 IP 地址,确保每个节点都有唯一的 IP 地址用于节点识别和连接。
    • cluster-announce-port: 配置节点用于宣布自己的端口号。
    • cluster-announce-bus-port: 配置节点用于集群总线通信的端口号。

    这些配置文件的生成确保了 Redis 集群中的每个节点都有适当的配置,以便它们可以协同工作并构建一个完整的集群。每个节点的 cluster-announce-ip 参数的不同之处确保了每个节点都有唯一的标识,这是集群正常运行所必需的。

    3.3 编写 docker-compose.yml 文件

    docker-compose.yml 文件是用于定义和配置 Docker 容器的编排文件。它允许我们在单个文件中定义多个容器,以及这些容器之间的关系和配置参数。
    其内容大致分为两个部分:

    • 先创建 networks 局域网,并分配网段为: 172.30.0.0/24。
    • 然后配置每个节点,注意配置文件映射、端口映射、以及容器的 IP 地址

    说明:

    • 设定成固定 IP 方便后续的观察和操作。
    • 此处的端口映射不配置也可以,配置的目的是为了可以通过 宿主机 IP: 映射的端口 进行访问。不配置的话通过容器自身 IP: 6379 的方式也可以访问。

    下面是 docker-compose.yml 文件的内容:

    version: '3.7'
    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
    
    networks:
      mynet:
        ipam:
          config:
            - subnet: 172.30.0.0/24
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162

    对文件内容的说明:
    docker-compose.yml 文件是 Docker Compose 工具使用的配置文件,用于定义和配置 Docker 容器的编排。在您提供的文件中,它主要用于配置 Redis 集群中的 11 个节点的容器。以下是对文件内容的详细说明:

    1. version: '3.7': 指定 Docker Compose 文件的版本。

    2. services: 定义了要启动的各个服务(容器)以及它们的配置。

    3. 每个 Redis 节点的配置块类似如下示例:

      • redis1: 定义了容器的名称。
      • image: 'redis:5.0.9': 指定要使用的 Redis 镜像及其版本。
      • container_name: redis1: 指定容器的名称。
      • restart: always: 设置容器在退出后总是自动重启。
      • volumes: 指定将宿主机上的配置文件挂载到容器内部的目录。
      • ports: 映射容器内部的端口到宿主机,允许通过宿主机的 IP 地址和映射的端口进行访问。
      • command: redis-server /etc/redis/redis.conf: 指定容器启动时执行的命令,这里是启动 Redis 服务。
      • networks: 指定容器所属的网络。
      • mynet: 定义的自定义网络名称。
    4. networks: 定义了自定义网络 mynet,并配置了 IP 地址管理(IPAM)配置。它指定了容器所属的 IP 子网范围为 172.30.0.0/24,并为每个容器分配了一个唯一的 IPv4 地址。

    总而言之,该 docker-compose.yml 文件配置了一个自定义网络 mynet,并启动了 11 个 Redis 节点的容器,每个容器都有不同的 IPv4 地址、挂载了不同的配置文件,并映射了端口,以便可以通过宿主机的 IP 地址和端口访问这些容器。这些容器可以协同工作以构建 Redis 集群。

    3.4 启动容器

    使用下面的命令启动刚才编排的所有容器:

    docker-compose up -d
    
    • 1

    启动结果:

    可以使用docker ps -a 命令来查看当前所有的容器。


    此时,11 个 Redis 节点已经成功创建并运行了。

    3.5 创建集群

    在创建 Redis 集群时,将前面的前 9 个 Redis 节点用于构建集群。

    以下是创建集群的命令:

    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
    
    • 1

    命令的详细说明:

    • redis-cli: 这是 Redis 命令行工具,用于与 Redis 服务器进行交互和执行各种 Redis 命令。

    • --cluster create: 这个部分指示 redis-cli 命令要创建一个 Redis 集群。

    • 172.30.0.101:6379172.30.0.109:6379: 这是集群中的各个 Redis 节点的 IP 地址和端口号。这里列出了 9 个主节点,它们将组成 Redis 集群的一部分。

    • --cluster-replicas 2: 这个选项指定了每个主节点需要有多少个从节点来备份数据。在这个命令中,设置为 2,表示每个主节点都会有 2 个从节点用于备份数据。

    当执行这个命令时,Redis 将会根据提供的 IP 地址和端口号创建一个 Redis 集群,并配置每个主节点都有 2 个从节点。这样的集群配置旨在提供高可用性和容错性,以确保在某个节点出现故障时,数据仍然可用。这是通过将数据复制到主节点的多个从节点来实现的。

    执行结果:

    当提交命令之后,首先会出现以下的交互信息:

    上面的交互信息是在创建 Redis 集群时出现的,主要用于确认集群的配置是否符合我们的期望,并防止不小心误操作。下面是对这些信息的详细说明:

    1. 首先,Redis 集群创建命令会执行哈希槽的分配。哈希槽是 Redis 集群用来分片数据的方式,每个主节点都负责一定范围的哈希槽。

    2. 接着,命令列出了每个主节点的信息,包括主节点的 ID、IP 地址和端口,以及分配给主节点的哈希槽范围。

    3. 然后,命令列出了每个从节点的信息,包括从节点的 IP 地址和端口,以及它所复制的主节点的 ID。

    4. 最后,命令询问是否接受这个配置。它会提示 “Can I set the above configuration? (type ‘yes’ to accept):”,并等待确认。

      • 如果同意并希望继续创建集群,键入 “yes” 并按回车键。
      • 如果不满意配置或需要进行更改,键入 “no” 并按回车键,然后可以重新运行命令以更改配置。

    这个交互式确认过程是为了确保我们在创建 Redis 集群时能够仔细检查和确认配置,以避免错误或不完全符合预期的集群配置。一旦确认配置正确,Redis 将按照实际的要求继续创建集群。

    当输入 “yes” 之后:


    这是 Redis 集群创建成功后的输出信息。以下是对输出信息的说明:

    1. “Nodes configuration updated”:表示节点配置已经更新,新的集群配置已生效。

    2. “Assign a different config epoch to each node”:为每个节点分配了不同的配置时代(config epoch)。配置时代是用于管理节点配置更改的内部计数器。

    3. “Sending CLUSTER MEET messages to join the cluster”:表示正在向各个节点发送 CLUSTER MEET 消息,以便它们可以加入集群。

    4. “Waiting for the cluster to join…”:正在等待集群中的各个节点加入,这是一个等待过程。

    5. “Performing Cluster Check (using node 172.30.0.101:6379)”:正在执行集群检查,使用了一个节点(172.30.0.101:6379)作为参考节点。

    6. 接下来是每个节点的信息,包括节点的类型(主节点或从节点)、节点的 ID、IP 地址和端口,以及节点所负责的哈希槽范围和复制关系。

    7. “[OK] All nodes agree about slots configuration.”:表示所有节点都就哈希槽配置达成一致,说明集群中的各个节点都已正确分配了哈希槽。

    8. “Check for open slots…” 和 “Check slots coverage…”:表示正在检查是否有未分配的哈希槽和检查哈希槽是否已完全分配。“[OK] All 16384 slots covered.” 表示所有的 16384 个哈希槽都已经被分配和覆盖,这是一个成功的标志。

    这些信息表明 Redis 集群已成功创建,并且所有节点都已加入集群并正确配置了哈希槽。现在,就可以开始使用 Redis 集群了。

    3.5 演示集群的使用

    此时,使用 Redis 客户端连上集群中的任何一个节点,都相当于连上了整个集群。

    注意事项:

    • 客户端后面需要加上 -c 选项,否则如果 key 没有落到当前节点上的分片区域中,是不能操作的。 -c 选项会自动把请求重定向到对应节点上。

    连接集群:

    这里以连接 172.30.0.101: 6379 的 Redis 节点为例,下面是连接的命令。

    redis-cli -h 172.30.0.101 -p 6379 -c
    
    • 1

    查看整个集群信息:

    使用 cluster nodes 命令可以查看到整个集群的情况:

    在这个示例中,我们连接到了 Redis 集群中的一个节点(172.30.0.101:6379),并使用 -c 选项以确保请求被自动重定向到正确的节点。然后,我们运行了 CLUSTER nodes 命令来查看整个集群的节点信息。

    以下是对输出信息的简要说明:

    • 每行代表集群中的一个节点。
    • 每个节点有一个唯一的标识符。
    • 节点的IP地址和端口号被列出。
    • 节点的角色(master或slave)也被指定。
    • 如果是主节点,那么它管理的槽位范围也被列出。
    • 如果是从节点,它会指定它正在复制的主节点。

    在这个示例中,我们可以看到集群中的各个节点以及它们之间的关系,包括主从关系和槽位的分配情况。这使我们能够了解整个 Redis 集群的拓扑结构和状态。

    如果在当前连接集群的客户端中设置或者获取 key,如果要设置或获取的 key 不在当前节点的分片中,则会重定向到对应的节点。

    四、集群故障的处理

    4.1 主节点宕机

    此时可以通过手动停止一个主节点来模拟主节点的宕机:比如上述拓扑结构中,可以看到 redis1redis2redis3 是主节点,随便挑⼀个停止。

    例如,停止 redis1 节点:

    docker stop redis1
    
    • 1

    结果:

    发现 redis1 成功停止了。

    4.2 Redis 集群故障的自动转移及其原理

    观察故障的自动转移:

    此时连接 redis2 节点,再次查看集群中的所有节点信息:

    根据上面的信息可以发现:

    • 此时集群中的 172.30.0.101:6379 节点,即 redis1 的状态为 fail
    • 并且 172.30.0.105:6379 节点,也就是 redis5 成为了 0 - 5460 分片中新的主节点;
    • 另一个从节点 172.30.0.106:6379,也就是 redis6 也成为了 redis5 的从节点了。

    故障转移原理:

    Redis集群的故障转移是指在 Redis 集群中的某个主节点发生故障时,系统能够自动地将一个备用的从节点升级为新的主节点,以保持数据的可用性和高可用性。故障转移是 Redis 集群的关键特性之一,确保了在节点故障时系统的持续稳定运行。

    以下是Redis集群的故障转移工作原理:

    1. 主节点故障检测:Redis 集群中的其他节点会定期检测主节点的健康状态。如果一个主节点不可用(比如它崩溃或无法访问),其他节点会注意到这一点。

    2. 选举新的主节点:一旦集群中的某个节点检测到主节点不可用,它将发起一次投票来选举新的主节点。投票通常基于节点的复制偏移量(replication offset),即哪个节点的数据最新。

    3. 选举过程:集群中的每个节点都可以发起一次选举,它们会尝试达成一致,以选择新的主节点。通常,具有最新数据的从节点将被选为新的主节点。其他节点将成为新主节点的从节点。

    4. 数据同步:一旦新的主节点被选出,其他节点将开始从新主节点同步数据。这确保了新主节点具有与以前主节点相同的数据。

    5. 客户端重定向:一旦故障转移完成,Redis集群将通知客户端以及其他节点有关新的主节点的位置,以便客户端可以更新其连接。

    总之,Redis 集群的故障转移是自动的、分布式的过程,它确保了系统在主节点故障时的数据可用性和可恢复性。这使得 Redis 集群成为一个强大的工具,用于构建高可用性和可扩展性的应用程序。

    4.3 恢复宕机的主节点

    我们可以尝试恢复刚才停止掉的 redis1 节点,启动的命令如下:

    docker start redis1
    
    • 1

    启动结果:


    发现成功启动了 redis1 节点。

    连接 redis 集群,查看集群中节点信息:


    发现,redis1 节点在恢复之后成为了 redis5 的从节点了。

    五、集群的扩容

    扩容是一个在开发中比较常遇到的场景,随着业务的发展,现有集群很可能无法容纳日益增长的数据。此时给集群中加入更多新的机器,就可以使存储的空间变得更大了。

    因此可以看出,所谓分布式的本质,就是使用更多的机器,引⼊更多的硬件资源。

    5.1 将新节点加入到集群中

    上面已经把redis1 - redis9 的节点构成了集群,接下来把就 redis10redis11 也加入到这个集群中。此处我们把 redis10 作为应该主节点,把 redis11 作为该节点的从节点。

    redis10 节点加入到集群中:

    redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379 
    
    • 1

    说明:

    • add-node 后的第一组地址是新节点的地址,第二组地址是集群中的任意节点地址,表明是哪个节点。
    • 使用上述命令加入的节点会成为集群中的一个主节点。

    连接集群中任意节点,查看集群的状态:

    可以发现,redis10 成功加入了集群,并成为了一个主节点,但是并没有分配任何槽位。

    5.2 重新分配 slots 槽位

    使用下面的命令来重新分配槽位:

    redis-cli --cluster reshard 172.30.0.101:6379 
    
    • 1

    说明:

    • reshard 后的地址是集群中的任意节点地址,意思是指明要重新分配槽位的集群。
    • 注意单词拼写,是 reshard(重新切分),而不是 reshared(重新分享),注意不要多写个 e

    执行上述命令之后,会进入交互式操作,Redis 会提示用户输入以下内容:

    • 多少个 slots 要进行 reshard 重新切分。为了均匀分布,此处填写 4096。
    • 哪个节点来接收这些重新分配的 slots。此处填写 172.30.0.110:6379,也就是 redis10 这个节点在集群中的 ID。
    • 这些重新分配的 slots 从哪些节点搬运过来。此处填写 all,表示其他所有的节点都进行搬运。

    执行过程如下:

    当所有的搬运工作准备完成后会再次询问是否介绍这个结果,只有输入了 “yes” 才会进行真正的槽位搬运。

    最后可以再次连接集群,查看集群的节点信息:


    可以发现 redis10 分配的槽位区间为:0-1364、5461-6826、10923-12287,并且其他主节点的槽位也进行了相应的缩减。

    5.3 给新的主节点添加从节点

    目前新增的节点只是一个单一的主节点,此时扩容的目标已经初步达成。但是为了保证集群可用性,还需要给这个新的主节点添加
    从节点,以保证该主节点宕机之后,有从节点能够代替。

    增加从节点的命令如下:

    redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave 
    
    • 1

    说明:

    • add-node 后面的第一组地址是要新增从节点的地址,第二组地址则是要增加从节点的主节点地址。
    • --cluster-slave 表示要新增的节点是一个从节点。

    执行过程:

    最后可以再次连接集群,查看集群的节点信息:

    根据节点的 ID,可以发现成功的为 redis10 新增了应该从节点 redis11

    六、集群的缩容

    在 Redis 集群中,扩容是比较常见的操作,但是缩容其实非常少见的,此处简单了解缩容的操作步骤即可。

    接下来演示把刚才新增的 redis10redis11 这两个节点删除掉。

    6.1 删除从节点

    首先,需要删除 redis10 的从节点 redis11,删除的命令如下:

    # redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
    redis-cli --cluster del-node 172.30.0.101:6379 d35c427842bd1ab776ddbbda031f410a5d8ddb99
    
    • 1
    • 2

    说明:

    • del-node 选项表示要删除节点
    • del-node 后面的地址是集群中的任意一个节点的地址,表示要从哪个集群中删除节点。
    • 再接着的是要删除的节点的 ID。

    删除过程:

    可以连接集群,查看集群的节点信息:


    可以发现 redis11 成功被删除了。

    6.2 重新分配 slots 槽位

    重新分配槽位命令和上文一样:

    redis-cli --cluster reshard 172.30.0.101:6379 
    
    • 1

    执行后仍然进入交互式操作:

    需要注意的是:此时要删除的主节点,包含 4096 个 slots,把 redis10 这个节点上的这 4096 个 slots 分成三份,即 1365 + 1365 + 1366,分别分给其他三个主节点。这样可以使 reshard 之后的集群各个分片的 slots 数目仍然均匀发布。

    第一次重分配:分配给 redis5 1365 个 slots

    • 接收 slots 的 node ID 是 redis5 节点的 ID,Source node 填写的则是 redis10 节点的 ID。

    第二次重分配:分配给 redis2 1365 个 slots

    • 接收 slots 的 node ID 是 redis2 节点的 ID,Source node 填写的则是 redis10 节点的 ID。

    第三次重分配:分配给 redis3 1366 个 slots

    • 接收 slots 的 node ID 是 redis3 节点的 ID,Source node 填写的则是 redis10 节点的 ID。


    连接集群,查看集群的节点信息:


    发现redis10 节点上已经没有任何槽位了。

    6.3 删除主节点

    最后,将 redis10 这个节点从集群中删除。

    # redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
    redis-cli --cluster del-node 172.30.0.101:6379 9b12a2493edecd6cfa70d811d9383c0c5130dcf6
    
    • 1
    • 2

    执行过程:


    再次查看集群信息,可以发现 redis10 节点已经不再集群中了。

    七、总结

    在本文中,我们深入探讨了Redis Cluster集群模式,包括了以下主要内容:

    1. Redis Cluster集群模式的概念:首先介绍了Redis Cluster集群模式的基本概念,了解了它是如何将数据分布到多个节点上,并提供高可用性和横向扩展性的。

    2. 数据分片算法:讨论了三种常用的数据分片算法,包括哈希求余算法、一致性哈希算法和哈希槽分区算法,以及它们在Redis Cluster中的应用。

    3. 基于Docker模拟搭建Redis集群:演示了如何使用Docker容器技术来模拟搭建一个Redis Cluster集群,从目标拓扑结构的创建到容器的启动和集群的创建。

    4. 集群故障的处理:详细讨论了Redis Cluster中主节点宕机的情况以及自动转移的原理,以及如何恢复宕机的主节点,确保集群的可用性。

    5. 集群的扩容和缩容:介绍了如何将新节点加入到集群中以进行扩容,并且讨论了如何删除从节点和主节点以进行缩容,以满足不同场景下的需求。

    通过阅读本文,可以帮助我们对 Redis Cluster 集群模式有了更深入的理解,并且能够在实际应用中使用 Redis Cluster 来构建高可用性和可扩展性的 Redis 集群。

  • 相关阅读:
    微服务架构最佳实践:消息队列
    ctfshow web29-web40
    链式队列----数据结构
    【Python教学】pyqt6入门到入土系列,超详细教学讲解
    Typescript给定一个由key值组成的数组keys,返回由数组项作为key值组成的对象
    JavaScript阻止控制台输出信息
    【机器学习】聚类【Ⅱ】原型聚类经典算法
    RK3568-74HC595
    美格智能出席无锡智能网联汽车生态大会,共话数字座舱新势力
    cookie相关
  • 原文地址:https://blog.csdn.net/qq_61635026/article/details/133158882