Redis---集群

(一).概念

前面介绍哨兵的时候,我们发现,哨兵机制并不能帮助我们解决数据存储容量问题。为了解决这个问题,我们可以引入多组主节点/从节点,每一组主节点/从节点存储数据全集的一部分,从而构成了一个更大的整体,称为Redis集群

通过上图可以发现,每一个红框部分都保存1/3的数据,每部分可以称为是一个"分片",如果数据量持续增加,那么只需要增加更多的分片来解决即可。

(二).数据分片算法

目前来说,有三种主流的分片算法分别为1.哈希求余 2.一致性哈希算法 3.哈希槽分区算法

1.哈希求余

哈希求余算法,借助了hash函数,把一个key映射到整数,在针对数组的长度进行求余,就可以得到一个数组的下标。

例如,现在有3个分片,即N=3。此时就可以针对要插入的key计算hash,再把这个hash值余上分片的个数,然后就得到了一个下标,此时,这个数据就放到对应的分片中了 hash(key) % N

优点:简单高效,数据分配均匀

缺点:一旦进行了扩容,增加新分片,那么N就变了,原来的映射规则就被破坏,此时节点的数据需要重新排列来满足新的映射规则,开销比较大

如上图,一共有21个key,只有3个是没有经过搬运的,其他的key都没有进行搬运

2.一致性哈希算法

在哈希求余这种操作中,当前key是属于哪个分片是通过分片的个数来交替的,但是一致性哈希算法把交替出现改成了连续出现

①.把 0 ~ 2 ^32-1这个数据空间,映射到一个圆环上,数据按照顺时针方向增长。

②.假设当前存在3个分片,就把分片放到圆环的某个位置上

③.根据key计算哈希值,然后从会选择所在的位置顺时针向下找,找到的第一个分片,就是key所从属的分片

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

如果进行扩容,此时,原有分片在环上的位置不动,只需要在环上新安排一个分片的位置即可

此时,只需要把0号分片上的部分数据搬运给3号分片即可。1号和2号分片管理的区间都是不变的。

优点:大大降低了扩容时数据搬运的规模,提高了扩容操作的效率

缺点:数据分配不均匀,容易造成**"数据倾斜"**

3.哈希槽分区算法

哈希槽分区算法,是Redis Cluster引入的算法。

hash_slot = crc16(key) % 16384

crc16()也是一种哈希算法;16384=16*1024

这种算法,相当于把整个哈希值映射到了16384个槽位上,也就是0,16383。然后再把这些槽位比较均匀的分配给每个分片,每个分片的节点都需要记录自己持有哪些分片

假设现在有三个分片,一种可能的分配方式就是:
0 号分⽚: 0, 5461, 共 5462 个槽位
1 号分⽚: 5462, 10923, 共 5462 个槽位
2 号分⽚: 10924, 16383, 共 5460 个槽位

如果进行了扩容,例如,新增了3号分片,那么就可以针对原有的槽位进行重新分配,例如
0 号分⽚: 0, 4095, 共 4096 个槽位
1 号分⽚: 5462, 9557, 共 4096 个槽位
2 号分⽚: 10924, 15019, 共 4096 个槽位
3 号分⽚: 4096, 5461 + 9558, 10923 + 15019, 16383, 共 4096 个槽位

注意:分片的规则是很灵活的,每个分片持有的槽位也不一定连续。每个分片的节点使用位图来表示自己持有哪些槽位,对于16384(16384个bit位,用每一位的0/1来区分自己的分片当前是否持有该槽位号)个槽位来说,需要2048个字节,即2KB的内存空间来表示。

我们不需要手动指定哪些槽位分配给某个分片,只需要告诉某个分片应该持有多少个槽位即可,Redis会自动完成后续的槽位分配,以及对应的key的搬运工作

注意:分片并不一定是越多越好。Redis的作者建议集群的分片不应该超过1000。key需要先映射到槽位,再映射到分片,如果分片太多,就无法保证数据在各个分片上的均衡性。

注意:这里设置成16384个槽位的原因是,节点和节点之间是通过心跳包来进行通信的。心跳包中就包含了该节点持有那些slots。使用的是位图这样的数据结构来表示的,如果给定的slots(槽位)更多,例如 65535个,那么就需要耗费更多的空间,即8KB,站在网络心跳包的角度,这是一个非常吃网络带宽的操作。而且,本身16384这个值基本上够用了,同时占用的硬件资源的个数也不多

(三).基于docker进行集群搭建

下面,我们将基于docker来搭建一个集群

1.创建目录和配置

在redis-cluster文件夹中,我创建了一个.yml文件和一个.sh文件。我们使用docker-compose来进行容器的编排,所以创建了一个docker-compose.yml的配置文件,把具体的要创建的这些容器,每个容器运行的各种参数给描述清楚,然后通过一个命令,就能批量的启动和停止这些容器。

这个.sh文件是一个shell脚本。在linux环境下,我们通常都是采用命令行的方式进行操作。我们可以把这些命令写入到一个文件中,然后批量化的执行,同时也可以写入循环,条件,函数等机制。

这个集群环境,我们需要创建11个节点

有9个节点构成了3个分片,然后剩下的两个节点是用来扩容的

上图是shell脚本中的内容。我们可以通过bash generate.sh命令,来运行这个shell脚本

可以看到,所有的文件都创建出来了

可以看到,配置文件中的内容正好是我们配置的

2.编写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

上面是我yml文件中的内容

3.启动容器

通过 docker-compose up -d 启动容器

可以发现,所有的容器都已经启动了

4.构建集群

这里,我们是把前9个主机构成集群,3主6从,剩下的那两个作为扩容用,目前先不用

这样配置设置成了之后,redis就知道了,3个节点是一伙的,即一个分片,一共9个节点,即3个分片。命令直接在在命令行中执行即可

我们可以通过 cluster nodes来查看当前集群的信息

此时,我们可以连接上任何一个redis服务了

我们可以同通过两种方式来连接。第一种是通过容器内网IP进行访问;第二种是通过宿主机端口映射访问。这也是我们之前配置的端口映射

同时,我们可以通过cluster nodes命令,来查看当前集群的信息

注意:目前来说,我们的集群已经设置好了,当前再存放数据就可以成功完成分片了。

但是,当我们运行程序的时候,发现,好像出错了。这是因为,"key111" 这个 key 通过hash计算之后,得到的槽位为13680,属于172.30.0.103这个分片,我们无法在172.30.0.101这个分片上存储

我们可以在启动redis-cli的时候,加上 -c选项。加上-c选项之后,即使当前的key不在当前分片上,也能够自己重定向到对应的分片主机上

可以看到,已经自己重定向到对应的分片主机上。-c 添加完成之后,客户端会根据当前key实际算出来的槽位号,自动找到匹配的分片主机。

注意:目前来看,我们是可以正常使用的。但是一旦使用了一些操作多个key的命令,并且这多个key是分散在不同的分片上,就可能会出现问题。

解决上面的问题,可以采取hash tag的方式来解决。

(四).主节点挂了

1.演示

我现在将redis1这个主节点停掉,然后再来看当前的集群信息

即使我将redis1重启了,依然是一个从节点。我们可以通过cluster failover 命令 对集群进行恢复,把101 重新设置为master

2.具体的执行流程

(1).故障判定

集群中的所有节点都是通过周期性的心跳包来进行通信的

①.节点A给节点B发送ping包,B就会给A返回一个pong包。里面包含了汲取你的配置信息,例如节点的id,该节点属于哪个分片,是主节点还是从节点等等信息

②.每个节点,每秒钟,都会给一些随机的节点发送ping包,而不是全发一遍,这样的设计是避免节点数多的时候,心跳包也很多

③.当A给B发送ping包,B不能如期回应的时候,此时A就会尝试重置和B的tcp连接,看能否连接成功,如果依然失败,此时节点A就会把节点B设置为PFAI状态(相当于主观下线)

④.A判定B为PFAIL之后,会通过redis内置的Gossip协议,和其他的节点进行沟通,向其他节点确认B的状态

⑤.此时A发现,很多节点也认为B为PFAIL,并且数目超过了总集群个数的一半,此时A就会把B标记为FAIL(相当于客观下线),并且把这个消息同步给其他节点,其他节点也会把B标记为FAIL,此时B就成为故障节点了

注意:这几种情况下,这个集群都会挂掉:

①.某个分片,所有的主节点和从节点都挂了

②.某个分片,主节点挂了,但是没有从节点

③.超过半数的主节点都挂了

(2).故障迁移

故障迁移,指的是把从节点晋升为主节点,继续给整个集群提供支持

①.从节点判定自己是否具有参选资格,如果从节点和主节点太久没有通信,时间超过了阈值,那么就会失去竞选资格

②.具有资格的节点,例如C和D,那么就会先休眠一定的时间,休眠时间=500ms 基础时间 + 0,500ms随机时间 + 排名 *1000ms 。这里的"排名"是和offset的值有关,offset值越大,说明从节点和主节点的数据越靠近,所以排名就越靠前

③.假设,C的休眠时间到了,此时C就会给其他所有集群中的节点,进行拉票操作,但是只有主节点才有投票的资格

④.主节点会把自己的票投给C,每个主节点只有1票,当C收到的票数超过主节点数目的一般,C就晋升为主节点,C负责执行slaveof no one,并且让D执行 slaveof C

⑤.同时,C还会把自己成为主节点的信息同步给其他集群的节点,大家也都会更新自己保存的集群结构信息

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

(五).集群扩容

目前来说,我们的集群包含了172.30.0.101 ~ 172.30.0.109这9个节点。下面,我们将110和111这两个节点也加入到集群中,并且110作为主节点,111作为从节点

1.把新的节点添加到集群中

add-node 后面的第一组地址是新节点的地址,第二组地址是集群中的任意节点的地址

然后我们通过cluster nodes命令,查看当前集群的信息

可以看到虽然110这个节点为主节点,但是没有任何的槽位

2.重新分配slots(槽位)

这里我们采取的方式是,把之前三组的主节点上的slots拎出来一些,分配给新的主节点110

输入"yes"

此时,再执行cluster nodes命令,来查看当前集群的信息

可以看到,110这个节点已经成功获取到了对应的槽位信息

注意:当获取到槽位信息之后,就要进行分配,任何搬运对应的key。在搬运key的过程中,对于那些不需要搬运的key,访问的时候是没有任何的问题的。但是对于需要搬运的key,进行访问可能会出现短暂的访问错误,这是因为key的位置发生了变化,随着搬运的完成,这样的错误也就恢复了

3.给新的主节点添加从节点

此时,我们再通过 cluster nodes命令来查看当前集群的信息

可以看到,我们添加的丛节点也成功了

相关推荐
Allstar_431 小时前
DV/PV 验证管理:为量产放行提供完整证据链——全星研发项目管理 APQP 软件系统汽车电子行业专业级研发项目管理平台
数据库·汽车
小范的技术工坊1 小时前
向量数据库解决了什么问题?
数据库
小蒜学长1 小时前
在线保险服务与管理平台的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·服务平台·在线保险
做运维的阿瑞2 小时前
SQL 面试高频:用户、商品、订单三表查询全解析
数据库·sql·mysql
hweiyu003 小时前
Redis命令:HSCAN
redis·缓存
CJi0NG3 小时前
【自用】MySQL - 语法:DDL、DML、DQL、DCL
数据库·mysql
姜鱼问生3 小时前
数据库备份 + 灾难恢复演练:从 dump 恢复全库
运维·数据库
清水白石0084 小时前
Python 对象模型深度解析:从“一切皆对象”到 id、type、isinstance 底层机制与小整数缓存原理
开发语言·python·缓存
ao-weilai4 小时前
MySQL数据库:表约束
数据库·mysql·adb