redis集群算法,搭建,故障处理及扩容

集群

一台机器上的硬件资源是有限的,如果要存储的数据越来越多,那么这台机器是无法负载的,所以可以引入其他机器,将需要存储的数据分为几份,分给几台机器共同存储,每台机器都存1/n。

如果某一台机器挂了,就会导致数据丢失,所以每个机器master都要引入从节点,当主节点挂了的时候,数据不会丢失。

数据分片算法

哈希求余

需要存储新数据的时候,不知道要放到哪一个分片中,就可以借助hash函数,将一个key映射到整数(例如md5),再针对数组的长度求余,就可以得到一个数组下标。后续查询key的时候,key是一样的,hash函数也是一样的,最后得到的分片值就是一样的。

但这个方法有一个问题,当这个服务器集群需要扩容的时候,就需要比较高的成本了,因为数组的长度变化了,得到的分片值就会发生变化,需要重新对已有的数据进行哈希求余,大约要搬运70%的数据。

一致性哈希

  • 将0~2^31-1这个数据空间映射到一个圆环上,按顺时针方向增长。

  • 假设当前存在3个分片,将3个分片放在圆环的某个位置上。

  • 假定有一个key,计算得到hash值h,在h的值位置顺时针往下找,找到的第一个分片就是这个key的分片值了。

    通过以上的哈希算法,在扩容的时候,可以在0号管辖区域划分一块给3号区域,这样,1号和2号管辖区域不需要改变,就可以大大减少扩容带来的搬运成本,但随之而来的是不均匀的问题。

哈希槽分区

这也是redis采用的hash算法

bash 复制代码
hash_slot=crc16(key)%16384

进一步将这16384个哈希槽(为什么是16384呢,因为节点之间是网络通信的,太大的位图会导致网络通信的负担)分配到分片上,比如说0号分片上就有[0,5461],1号分片上就有[5462,10923],2号分片就是[10924,16383],这只是一种可能的分片方式,分片的槽位号是可以连续,也可以不连续的,只需要保证每个分片的槽位号数量差不多即可。每个分片都是使用位图这样的数据结构进行存储槽位号的,仅需要持有2Kb大小的空间即可。

当需要扩容的时候,从0,1,2分片上都拿出来一部分放到3号分片中,这四个分片的槽位数量应该是差不多的,在上述过程中,只有需要移动的槽位才需要搬运,不需要移动的槽位是不用搬运的,这样既可以解决不均匀的问题,又可以解决搬运的效率问题。

搭建集群环境

受限于硬件条件,接下来使用docker在一台主机上搭建基于以下图片结构的redis集群:

  • 创建redis-cluster目录和配置

    shell脚本的配置
bash 复制代码
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

使用bash generate.sh执行此脚本,如果没有报错则执行成功。

docker-compose文件配置:在启动之前,需要保证之前docker-compose启动的都关掉,再启动此配置文件。

bash 复制代码
version: '3.8'
networks:
  mynet:
    ipam:
      config:
        - subnet: 172.30.0.0/24
services:
  redis1:
    image: 'redis:6.0.16'
    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:6.0.16'
    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:6.0.16'
    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:6.0.16'
    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:6.0.16'
    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:6.0.16'
  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:6.0.16'
    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:6.0.16'
    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:6.0.16'
    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:6.0.16'
  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
  • 配置集群关系:cluster-replicas 2是配置每个分片的从节点为2个,输入yes才会真正构建集群。
bash 复制代码
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


  • 使用集群:任意连接上9个节点中的任意一个节点,相当于连接上了整个集群。

    在启动redis-cli的时候,需要带上一个-c选项,否则在set一个key进行hash映射的时候,如果此key映射的分片不是当前连接的分片,就会报错。
    如果加上了-c选项,在客户端发现当前key映射的分片不是现在连接的分片时,就会自动重定向到对应的主机,如果是从节点,也会自动调整到对应的主节点上,客户端会根据key自动调整连接的分片主机。

故障处理

过程

如果集群中某一个主节点挂了,处理方式类似于哨兵。

下图的fail表示此节点挂了。

之前的时候,101的从节点是105和106,当101节点挂了,这里的105节点就被推举为主节点,当101节点重新启动以后,会变成从节点。

原理

故障判定

  • 首先基于心跳包进行故障判定,这里的心跳包除了message type属性以外,其他部分都是一样的,包含集群的配置信息,每个节点每秒钟都会给一些随机的节点发起ping包,而不是全部发一遍。
  • 当A向B发送ping包,而B没有如期回应pong包的时候,A会尝试重置和B的TCP连接, 如果仍旧连接失败的话,A就会将B置为PFAIL状态,相当于主观下线,接下来A会通过Gossip协议,向集群内其他节点沟通确认B的状态。
  • 如果A发现集群内很多节点都将B置为PFAIL状态,且数目超过集群总数的一半的时候,那么A会将B标记为FAIL,相当于客观下线,并将这个信息同步给其他节点,其他节点收到以后,也将B节点标记为FAIL。

故障迁移

  • 如果B是从节点,那么不需要进行故障迁移。
  • 如果B是主节点,那么就需要进行故障迁移,从节点需要判定自己是否具有竞选资格,当从节点和主节点已经太久没通信,这个从节点就会失去竞选资格。
  • 当C和D都有资格参与竞选,就会先休眠一段时间(休眠时间=500ms基础时间+随机时间+排名*1000ms,offset越大,排名越小),若是C休眠时间先结束,就会给集群中其他节点进行拉票操作,只有集群中的主节点有资格投票。
  • 当C收到的票数大于集群中主节点数目的一半,C就会晋升成主节点,同时将这个信息同步给集群里的其他节点。
    集群宕机:当某个或者某些节点挂了的时候,会导致整个集群都宕机,比如说所有主从节点都挂了,或者某个分片的主节点挂了,但没有从节点,或者说集群中超过半数的主节点挂了,都会导致整个集群宕机。

集群扩容

  • 之前已经在集群中创建了9个节点,构成3主6从的结构了,接下来要将新的节点110和111加入集群中,以110为master节点,111为slave节点。
bash 复制代码
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
  • 但这样添加的主节点没有任何槽,所以接下来还要分配槽。

    可以看到每一个机器slot分配的情况,输入我们想移动的slot数量,节点ID和slot来源即可。
bash 复制代码
redis-cli --cluster reshard 172.30.0.101:6379


这样,就可以为新增加的节点分配slot了。

  • 将111从节点添加进来。
bash 复制代码
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id [172.30.1.110 节点的 nodeId]

这样就扩容成功了。

相关推荐
plus4s1 小时前
2月19日(85-87题)
c++·算法
白太岁2 小时前
Redis:(2) hiredis 使用、C++ 封装与连接池
c语言·c++·redis·缓存
Desirediscipline2 小时前
cerr << 是C++中用于输出错误信息的标准用法
java·前端·c++·算法
Renhao-Wan2 小时前
Java 算法实践(八):贪心算法思路
java·算法·贪心算法
今儿敲了吗2 小时前
23| 画展
c++·笔记·学习·算法
Jasmine_llq2 小时前
《AT_arc081_d [ARC081F] Flip and Rectangles》
算法·动态规划(dp)·贪心思想扩展 / 收缩边界·预处理转换网格状态·二维数组遍历实现逐点计算
Desirediscipline4 小时前
#define _CRT_SECURE_NO_WARNINGS 1
开发语言·数据结构·c++·算法·c#·github·visual studio
范纹杉想快点毕业4 小时前
C语言550例编程实例说明
算法
小O的算法实验室4 小时前
2026年SEVC SCI2区,面向无人机路径规划的领域专用算子进化算法,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进