Redis 集群

发布于:2025-09-04 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

1. 集群是啥?

2. 如何分片? 

2.1 哈希求余

2.2一致性哈希算法

 2.3 哈希槽分区算法

3. 集群的搭建 (基于 docker)

3.1 创建目录

3.2 编写 generate.sh

3.3 编写 docker-compose.yml  

3.4 启动容器

3.5 构建集群

3.6 使用集群

4. 主节点宕机

4.1 列子

4.2 处理流程

4.2.1 故障判定 : 

4.2.2 故障迁移

5. 集群扩容

6. 集群缩容 


1. 集群是啥?

  • 广义的集群, 只要你是多个机器, 构成了分布式系统, 都可以称为是一个 "集群". (前面的主从结构, 哨兵迷失, 也可以称为是 "广义的集群")
  • 狭义的集群, redis 提供的集群模式, 这个额集群模式之下, 主要是解决问题, 存储空间不足的问题 (拓展存储空间) , 其实也就是将 redis 的数据进行分片存储.

2. 如何分片? 

主流的有三种分片方法

2.1 哈希求余

设有 N 个分片, 使用 [0, N - 1] 这样的序号进行编号. 针对某个给定的 key, 先计算 hash 值, 再把得到的结果 % N, 得到的结果即为分片编号.

  • 优点 : 简单高效, 数据分配均匀
  • 缺点 : 一旦需要进行扩容, N 就改变了, 原有的映射规则被破坏, 就需要让节点之间的数据相互传输, 重新排列, 以满足新的映射规则, 此时需要搬运的数据量是比较多的, 开销较大 .

2.2一致性哈希算法

这个方法改良了上面的 哈希 求余

  1.  把 0 -> 2 ^ 32 - 1 这个数据空间, 映射到一个圆环上. 数据按照顺时针方向增长
  2. 假设当前存在三个分片, 就把分片放到圆环的某个位置上.
  3. 假定有一个 key , 计算得到 hash 值 H, 那么从 H 的 位置顺时针向下找, 找到的第一个分片, 就是该 key 所从属的分片.

假设我们加上一块 3 号分片 : 

这时候, 这需要将 0 号分片上的这一段数据搬运给 3 号分片即可, 2 号和 3 号分片都是不变的, 这种搬运成本, 也是有的, 但是bil 哈希求余的方式低了不少, 但是我们的几个分片上的数据量,; 就可能不均匀了(数据倾斜).

 2.3 哈希槽分区算法

redis 真正采用的分片算法

这个算法其实就像是 哈希求余 和 一致性 哈希算法的结合

  1. 我们将 key 所对应的 hash % 16384 得到 哈希槽
  2. 在将这些 哈希槽 分片

这里只是一种可能的分片方式, 实际上分片是非常灵活的, 分片的槽位可以是连续的, 也可以是不连续的.

此处每个 分片都会使用 "位图" 这样的数据结构来表示出当前有多少槽位, 16384 个 bit 位, 每一位用 0/1 来区分自己这个分片当前是否持有该槽位. 所占用的内存也不多, 16384 / 8 = 2048 => 2 KB

要是再加上一个分片呢 : 

此时, 只有被移动的槽位,对应的数据才需要搬运, 分片槽位均匀.

redis 中, 分片包含哪些槽位是可以配置的.

此处还有两个问题 : 

1. redis 集群是最多有 16384 个分片吗? 

  • 每个分片上就只有一个槽位, 此时很难保证数据在各个分片上的均衡性.
  • key 是要先映射到槽位, 再映射到分片上的 (有的槽位可能是有多个 key, 有的槽位可能没有 key). 
  • 如果每个分片包含的槽位比较多, 如果槽位个数相当, 就可以认为是包含的 key 数量相当. 但是如果每个分片包含的槽位非常少, 槽位个数不一定能直观的反应到 key 的数目, 因为槽位数少, 分片多, 有的 分片可能还没分配到 key

2. 为什么是 16384 个槽位? 

  • 节点之间通过心跳包通信. 心跳包中包含了该节点持有哪些 slots. 这个是使用位图这样的数据结构表示的. 表示 16384 (16k) 个 slots, 需要的位图大小是 2KB. 如果给定的 slots 数更多了, 比如 65536 个了, 此时就需要消耗更多的空间, 8KB 位图表示了 8KB, 对于内存来说不算什么,  但是在频繁的网络心跳包中,  还是一个不小的开销的.
  • 另一方面, Redis 集群一般不建议超过 1000 个分片. 所以 16k 对于最大 1000 个分片来说是足够用的, 同时也会使对应的槽位配置位图体积不至于很大.

3. 集群的搭建 (基于 docker)

3.1 创建目录

在 linux 上, 以 .sh 后缀结尾的文件称为 "shell 脚本", 使用 linux 的时候, 都是通过一些命令来进行操作的. 使用命令操作, 就非常适合把命令给写到一个文件中, 批量化执行, 同时, 还能加入, 条件, 循环, 函数等机制. 因此, 就可以基于这些来完成更复杂的工作了.

这里我们要创建 11 个 redis节点, 这些 redis 的配置内容, 大同小异. 此时就可以使用脚本来批量生成.

停止 redis 的有关容器

3.2 编写 generate.sh

执行 shell 脚本

3.3 编写 docker-compose.yml  

version: '3.7'
networks:
  mynet:
    ipam:
      config:
        - subnet: 172.30.0.0/24

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

3.4 启动容器

启动之前先检查是否有 redis 容器没关闭.

3.5 构建集群

3.6 使用集群

这个时候使用的是 redis 的同一个节点, 从 101 - 109 九个节点, 现在是一个整体. 使用客户端连上任意一个节点, 本质上是等价的.

使用 cluster nodes 查看当期那集群消息

设置成集群模式之后, 当前数据就分片了. k1 这个 key 通过 hash 计算之后, 得到 slot 12706, 属于 103 这个分片.

  1. 为了解决这个问题, 我们可以在启动 redis-cli 的时候, 加上 -c 选项. 此时客户端发现当前 key 的操作不在当前分片上, 就能够自动的重定向导对应的分片主机.

使用集群之后, 之前学过的命令, 有些是不能正常使用的. 因为有些指令是操作了多个 key 的, 此时如果 key 是分散在不同的分片上, 就可能会出现问题了. (也是有特殊的方式可以解决上述的问题, hash tag)

4. 主节点宕机

4.1 例子

比如有 redis1, redis2, redis3, 这样三个具有主从关系的节点, 其中, redis1 是主节点, redis2 和 redis3 是从节点. 这个时候要是主节点 redis1 挂了, 那么集群就会充当哨兵的作用, 从redis1 管理的两个 从节点中选一个出来当做 主节点, 这个时候 redis1 就变成了从节点. 重启 redis1, redis1 依然会是从节点.

我们也可以使用 cluster failover 进行集群恢复. 也就是把 101 重新设定成 master.(登录到 101 上执行)

4.2 处理流程

4.2.1 故障判定
  1. 节点 A 给节点 B 发送 ping 包, B 就会给 A 返回一个 pong 包. ping 和 pong 除了 message type 属性之外, 其他部分都是一样的. 这里包含了集群的配置信息 (该节点的 id, 该节点从属于哪个分片, 是主节点还是从节点, 从属于谁, 持有哪些 slots 的位图...).
  2. 每个节点, 每秒钟, 都会给一些随机的节点发起 ping 包, 而不是全发一遍. 这样设定是为了避免在节点很多的时候, 心跳包也非常多 (比如有 9 个节点, 如果全发, 就是 9*8 有 72 组心跳了, 而且这是按照 N ^ 2 这样的级别增长的)
  3. 当节点 A 给节点 B 发起 ping 包, B 不能如期回应的时候, 此时 A 就会尝试重置和 B 的 tcp 连接, 看能否连接成功. 如果仍然连接失败, A 就会把 B 设为 PFAIL 状态 (相当于主观下线).
  4. A 判定 B 为 PFAIL 之后, 会通过 redis 内置的 Gossip 协议, 和其他节点进行沟通, 向其他节点确认 B 的状态. (每个节点都会维护一个自己的 "下线列表" 由于视角不同, 每个节点的下线列表也不一定相同).
  5. 此时 A 发现其他很多节点, 也认为 B 为 PFAIL, 并且数目超过总集群个数的一半, 那么 A 就会把 B 标记成 FAIL (相当于客观下线), 并且把这个消息同步给其他节点 (其他节点收到之后, 也会把 B 标记成 FAIL).
  6. 至此, B 就被彻底判定为故障节点了.

某个或者某些节点宕机, 有的时候会引起整个集群都宕机 (称为 fail 状态).

  • 某个分片, 所有的主节点和从节点都挂了.
  • 某个分片, 主节点挂了, 但是没有从节点.
  • 超过半数的 master 节点都挂了.
4.2.2 故障迁移

要是挂的是从节点, 就不需要进行故障迁移了, 要是挂的主节点, 就会由这个主节点的从节点触发故障迁移了.

  1. 从节点判定自已是否具有参选资格. 如果从节点和主节点已经太久没通信 (此时认为从节点的数据和主节点差异太大了), 时间超过阈值, 就失去竞选资格.
  2. 具有资格的节点, 比如 C 和 D, 就会先休眠一定时间. 休眠时间 = 500ms 基础时间 +[0, 500ms] 随机时间 + 排名*1000ms. offset 的值越大, 则排名越靠前(越小).
  3. 比如 C 的休眠时间到了, C 就会给其他所有集群中的节点, 进行拉票操作. 但是只有主节点才有投票资格.
  4. 主节点就会把自己的票投给 C (每个主节点只有 1 票). 当 C 收到的票数超过主节点数目的一半, C 就会晋升成主节点. (C 自已负责执行 slaveofno one, 并且让 D 执行 slaveof C).
  5. 同时, C还会把自己成为主节点的消息, 同步给其他集群的节点. 大家也都会更新自己保存的集群结构信息.

上述选举的过程, 称为 Raft 算法, 是一种在分布式系统中广泛使用的算法在随机休眠时间的加持下, 基本上就是谁先唤醒, 谁就能竞选成功.

5. 集群扩容

扩容流程 : 

  1. 把新的主节点加入到集群中 : redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
  2. 重新分配 slots : redis-cli --cluster reshard 172.30.0.101:6379 , 这里可以写集群中任意节点的地址, 在执行过程中, redis 会提示用户输入一些内容, : 多少个 slots 要进行 reshard -> 4096, 哪个节点来接收这些 slots -> 172.30.0.110, 这些 slots 从哪些节点搬运过来->all.
  3. 给新的主节点添加从节点 : redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-

6. 集群缩容 

缩容流程 : 

  1. 删除从节点 : redis-cli --cluster del-node [集群中任⼀节点 ip : port] [要删除的从节点  nodeId]
  2. 重新分配 slots : redis-cli --cluster reshard 172.30.0.101:6379, 然后分别填写要分配的 slots
  3. 删除主节点 : redis-cli --cluster del-node [集群中任⼀节点ip : port] [要删除的主节点 nodeId]

网站公告

今日签到

点亮在社区的每一天
去签到