baby sword‘s blog baby sword‘s blog
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)

zhengjian

不敢承担失去的风险,是不可能抓住梦想的
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)
  • mysql

  • redis

    • 单机下的redis

    • 集群下的redis

      • 集群下的redis
        • 一.主从模式
          • 1.介绍
          • 2.为什么会出现主从模式?
          • 3.怎么保证主从节点的数据一致性
          • 4.主服务器不进行持久化复制是否会有安全性问题?
          • 5.为什么主从复制使用RDB而不是AOF
          • 6.读写分离及其中的问题
          • 7.主从风暴问题(薪火相传)
          • 8.主从模式的缺点
        • 二.哨兵模式
          • client如何访问主节点
          • 哨兵主要功能
          • 哨兵怎么和主从库建立联系?
          • 那你知道哨兵如何判定主库下线的呢
          • 在master挂掉期间,如果现在外面来了写命令,那么写命令产生的数据会不会丢失呢?
          • sentinel中的leader怎么选举出新的master呢?
          • 假如我们当前有5个哨兵,1个主节点,3个从节点,quorum设置为2。假如此时有3个哨兵挂掉,请问我们可以判定主节点下线以及选举新的主节点嘛?
          • sentinel中的leader是如何选举的
          • 哨兵模式会出现脑裂问题吗
          • 哨兵模式有什么缺点?
        • 三.集群模式
          • 为什么需要集群模式?
          • 集群cluster有什么优势
          • 集群架构是怎么设计的
          • Redis Cluster是如何进行分片的
          • 为什么哈希槽有16384个(2^14)
          • Redis Cluster 扩容缩容期间可以提供服务吗?
          • Redis Cluster 中的节点是怎么进行通信的?
      • 集群下的redis2
      • redisson详解
      • raft协议浅析
      • 一致性hash与hash槽
  • MongoDB

  • 后端存储实战

  • 数据库
  • redis
  • 集群下的redis
xugaoyi
2022-12-24
目录

集群下的redis

单机下的redis确实有很多优良的性能,但是单机情况下面临这机器宕机或节点挂掉的问题,单机情况下节点挂掉,情况非常严重,几秒内可能损失很多数据,何况如果我们人工来重新启动单机节点。为了解决以上问题,redis提出了三种集群模式。

# 一.主从模式

image-20221224164123534

# 1.介绍

1)主从模式里使用一个redis实例作为主机(master),其余多个实例作为备份机(slave);

2)master用来支持数据的写入和读取操作,而slave支持读取及master的数据同步;

3)在整个架构里,master和slave实例里的数据完全一致;

# 2.为什么会出现主从模式?

  • 解决了高可用问题
  • 支持高并发读

单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。

# 3.怎么保证主从节点的数据一致性

通过全量复制和增量复制技术来实现

  • 全量复制(从节点加入集群或者从节点重连后请求的数据已经不在缓冲队列中)

(1)资料1

当从节点启动时,会向主节点发送SYNC命令;

主节点接收到SYNC命令后,开始在后台执行保存快照的命令生成RDB文件,并使用缓冲区记录此后执行的所有写命令;

主节点快照完成后,将快照文件和所有缓存命令发送给集群内的从节点,并在发送期间继续记录被执行的写命令;从节点收到快照后清空自身rdb并加载新的rdb文件

主节点快照发送完毕后开始向从节点发送缓冲区中的写命令;

从节点载入快照文件后,开始接收命令请求,执行接收到的主节点缓冲区的写命令。

image-20221224164758417

  • 增量复制(从节点请求的数据还在缓冲队列中)

主从复制中因网络等原因造成数据丢失场景,当从节点再次连上主节点。如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。

master会在其内存中创建一个复制数据用的缓存队列(复制积压缓冲区),缓存最近一段时间的数据,master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标 offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。

image-20221224165021285

(2)资料2

答: 主从复制有两种模式,我就先来说说全量复制吧,如下图,整体步骤为:

  1. 从节点向主节点发送同步请求,因为不知道主库的runID,并且不知道同步的偏移量是多少,所以参数分别为? -1,同步请求的指令为psync
  2. 主库执行bgsave指令生成rdb指令,将数据发送给从库,从库为了保证数据一致性,会将数据清空,然后加载rdb文件,完成数据同步。在此期间,主库收到的新数据都会被存入replication buffer中。
  3. 主库会将replication buffer发送给从库,完成最新数据的同步。

image-20230624111939338

需要了解的是,当主从同步过程中因为网络等问题发生中断,repl_backlog_buffer会保存两者之间差异的数据,如果从库长时间没有恢复,很可能出现该环形缓冲区数据被覆盖进而出现增量复制失败,只能通过全量复制的方式实现数据同步。

需要一个概念replication buffer,这个缓冲区用于存放用户写入的新指令,完成全量复制之后的数据都是通过这个buffer的数据传输实现数据增量同步

# 4.主服务器不进行持久化复制是否会有安全性问题?

答: 有,假如主节点没有使用RDB持久化,数据没有持久化到磁盘,假如主节点挂掉又立刻恢复了,此时主节点所有数据都丢失了,从节点很可能会因此清空原本数据进而导致数据丢失。

# 5.为什么主从复制使用RDB而不是AOF

答: RDB是二进制且压缩过的文件,传输速度以及加载速度都远远快速AOF。且AOF存的都是指令非常耗费磁盘空间,加载时都是重放每个写命令,非常耗时。需要注意的是RDB是按照时间间隔进行持久化,对于数据不敏感的场景我们还是建议使用RDB

# 6.读写分离及其中的问题

答: 大抵需要考虑以下这些问题:

  1. 延迟与不一致问题:如果对数据一致性容忍度较低,网络延迟导致数据不一致问题只能通过提高网络带宽,或者通知应用不在通过该节点获取数据
  2. 数据过期问题,从节点很可能在某一时刻某些过期数据被读取到了,这就会给用户造成很诡异的场景。
  3. 故障切换问题
  4. 如果在网络断开期间,repl_backlog_size环形缓冲区写满之后,是进行增量复制还是全量复制?

答: 分两种情况说:

  1. 若主库的repl_backlog_buffer的slave_repl_offset已经被覆盖,那么同步就需要全量复制了
  2. 从库会通过psync命令把自己记录的slave_repl_offset发给主库,主库根据复制进度决定是增量复制还是全量复制。

# 7.主从风暴问题(薪火相传)

如果有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),可以做如 下架构,让部分从节点与从节点(与主节点同步)同步数据

image-20221224170228548

# 8.主从模式的缺点

  1. 不支持高可用。主节点一旦挂了,集群就没了
  2. ip和端口都是手动配置的,无法进行动态调整

# 二.哨兵模式

关注的问题:

  • 哨兵怎么认为节点是下线的

  • 哨兵怎么监控所有的节点

  • 哨兵怎么进行leader的重新选主

    介绍:哨兵解决主从下,主节点挂了后无法提供服务的问题

    redis的哨兵模式,就是用于在一主多从的集群环境下,如果主服务器宕机了,它会自动的将从服务器中的一台设为新的master,并且将其余的slave的配置文件自动修改,这样就切换出一套新的主从服务,不需要人工干预,且不会影响服务的使用。

    # client如何访问主节点

    sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。 哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过

    sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis 主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)

    # 哨兵主要功能

    集群监控:负责监控 redis master 和 slave 进程是否正常工作。

    消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。

    故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。

    配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

    image-20221224173051845

    redis哨兵对于集群架构来说有什么作用

    • 哨兵至少需要 3 个实例,来保证自己的健壮性。
    • 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
    • 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

    # 哨兵怎么和主从库建立联系?

    大致分为两个步骤:

    • 哨兵集群的组建

    如下图每个哨兵都在主库的__sentinel__:hello频道和其他哨兵保持联系,从而构成了一张联系网。

    image-20221224175053375

    然后哨兵通过info命令从主库中获取从库的信息,从而与从库建立联系:

    image-20221224175119807

    # 那你知道哨兵如何判定主库下线的呢

    答: 有两种方式,一种是主观下线,任意一个哨兵对主节点进行检测判断,主节点是否下线。

    另一种就是客观判定下线了,哨兵集群通过投票判定当前主节点是否下线,其工作过程如下:

    1. 某个哨兵主观判定主节点下线,向其他哨兵发出is-master-down-by-addr,开始对是否下线判定进行投票
    2. 每个哨兵发出自己的看法
    3. 根据sentinel monitor <master-name> <ip> <redis-port> <quorum>设置quorum,若同意数大于等于quorum则判定主节点下线。

    image-20221224175213515

    • 主观下线:sentinel节点认为某个Redis节点已经下线了
    • 客观下线:法定数量(通常为过半)的sentinel节点认为某个redis节点已经下线,那就真的下线了

    具体流程如下:

    1. 每个哨兵节点以每秒一次的频率向整个集群中的哨兵、master、slave发送ping命令

    image-20221224181318640

    1. 如果对应的节点超过规定的时间(down-after-millisenconds)没有进行有效回复的话,就会被其认定为是主观下线(SDOWN)。注意!这里的有效回复不一定是PONG,可以是-LOADING 或者–MASTERDOWN。

    image-20221224181410465

    1. 如果被认定为主观下线的是 slave 的话, sentinel不会做什么事情,因为slave下线对Redis集群的影响不大,Redis 集群对外正常提供服务。但如果是master被认定为主观下线就不一样了,sentinel整体还要对其进行进一步核实,确保 master是真的下线了。
    2. 所有sentinel节点要以每秒一次的频率确认master的确下线了,当法定数量(通常为过半)的sentinel节点认定 master已经下线, master才被判定为客观下线(ODOWN)。这样做的目的是为了防止误判,毕竟故障转移的开销还是比较大的,这也是为什么Redis官方推荐部署多个sentinel节点(哨兵集群)。

    image-20221224181521923

    1. 随后, sentinel中会有一个Leader的角色来负责故障转移,也就是自动地从slave中选出一个新的 master并执行完相关的一些工作(比如通知slave新的 master连接信息,让它们执行replicaof成为新的master 的 slave)。
    2. 如果没有足够数量的sentinel节点认定master已经下线的话,当master 能对sentinel的 PING命令进行有效回复之后,master也就不再被认定为主观下线,回归正常。

# 在master挂掉期间,如果现在外面来了写命令,那么写命令产生的数据会不会丢失呢?

​ 如果当前发生了master挂掉,在重新选举新master中,外部来的写请求,这时因为无法响应写请求而抛弃,所以在master选举成功之前是会丢失数据的。解决方法可以在采用哨兵模式的时候进行配置min_slaves_to_write,可以保证在master挂掉后,新来的写命令写到多少个从节点中

# sentinel中的leader怎么选举出新的master呢?

slave必须是在线状态才能参加新的master的选举,筛选出所有在线的slave之后,通过下面3个维度进行最后的筛选(优先级依次降低):

  1. slave优先级︰可以通过slave-priority手动设置slave 的优先级,优先级越高得分越高,优先级最高的直接成为新的 master。如果没有优先级最高的,再判断复制进度。

  2. 复制进度: Sentinel总是希望选择出数据最完整(与旧master数据最接近)也就是复制进度最快的slave被提升为新的 master,复制进度越快得分也就越高。

  3. runid(运行 id)︰通常经过前面两轮筛选已经成果选出来了新的 master,万一真②有多个slave 的优先级和复制进度一样的话,那就 runid 小的成为新的master,每个redis节点启动时都有一个40字节随机字符串作为运行id。

image-20221224182304036

总结一下:

  1. 选举出哨兵leader。
  2. 哨兵leader根据上文规则选出新的master。
  3. 从节点复制新leader的数据。
  4. 通知客户端主节点更换。
  5. 若原来的主节点复活,则作为新主节点的从节点

# 假如我们当前有5个哨兵,1个主节点,3个从节点,quorum设置为2。假如此时有3个哨兵挂掉,请问我们可以判定主节点下线以及选举新的主节点嘛?

答: 首先解决第一个问题,由于哨兵挂了3个所以还剩两个,假如主节点挂了,由于quorum等于2,所以我们有一定概率(两个哨兵都认为主节点挂了)判定主节点下线。 再来说说第2个问题,上文已经给出了选举新的哨兵leader的两个条件,本题目明显不符合条件1,因为(5/2)+1=3,而哨兵只剩两个了,所以选不出哨兵的leader,也就没办法指定新的leader了

# sentinel中的leader是如何选举的

答: 判定主库下线后我们就必须选出哨兵中的leader找下一个主节点,所以我们必须建立一个哨兵集群,这个哨兵集群我们必须从中选举出leader作为哨兵头子,而哨兵选举出的leader必须符合以下两个条件:

  1. num(total_sentinels)/2+1 //即半数(所有哨兵数的半数,无论哨兵死活)以上的选票即可成为哨兵中的leader,这就是著名的Raft算法
  2. 选票数还必须大于等于quorum

具体的raft算法可以看我另一篇文章《raft算法浅析》。

# 哨兵模式会出现脑裂问题吗

image-20221224192747296

image-20221224192756158

image-20221224192809435

下面还可以参考下chat gpt的回答,法定人数就是解决这个问题

Redis Sentinel 哨兵模式存在脑裂问题,这是指在主从集群的某个节点出现网络分区(network partition)时,导致节点之间的通信出现问题,从而出现多个主节点同时存在的情况,进而导致数据的不一致。

出现脑裂问题时,多个 Sentinel 节点会认为主节点已经失效,它们都会尝试将从节点升级为新的主节点。但是,由于节点之间的通信出现问题,它们之间无法共享信息,所以每个 Sentinel 节点都可能选举出一个不同的从节点作为新的主节点,导致多个主节点同时存在的情况,从而导致数据的不一致。

为了解决这个问题,Redis Sentinel 提供了一些机制,如 quorum(法定人数)和自动故障转移等。其中,quorum 机制指的是在 Sentinel 集群中设置一个法定人数,只有在超过 quorum 个 Sentinel 节点认为主节点已经失效时,才会将从节点升级为新的主节点,从而避免了多个主节点同时存在的情况。自动故障转移机制则指的是在主节点失效后,由 Sentinel 集群自动选举出一个从节点作为新的主节点,以保证数据的一致性和高可用性。

在实际使用 Redis Sentinel 哨兵模式时,需要根据实际需求设置 quorum 的大小和自动故障转移的参数,以确保系统在出现脑裂问题时能够自动地进行容错处理和数据同步。

# 哨兵模式有什么缺点?

哨兵模式存在的问题:

  1. 无法很好的进行水平扩容(容量扩展)
  2. 无法支持很大的写并发

# 三.集群模式

主要解决了哨兵模式出现的缺点

image-20221224193220715

# 为什么需要集群模式?

master的数量决定了可以存储的数据的数量,slave保证了可用性,以及高并发读

在高并发下,使用redis主要会遇到两个问题:

  1. 缓存的数据太大
  2. 并发量要求极高。虽然Redis 号称单机可以支持10w 并发,但实际项目中,不可靠因素太多,就比如一些复杂的写/读操作就可能会让这个并发量大打折扣。而且,就算真的可以实际支持10w并发,达到瓶颈了,可能也没办法满足系统的实际需求。

在上文介绍的主从和哨兵。

主从复制和Redis Sentinel这两种方案本质都是通过增加主库(master)的副本(slave)数量的方式来提高 Redis服务的整体可用性和读吞吐量,都不支持横向扩展来缓解写压力以及解决缓存数据量过大的问题。

image-20221224195114767

对于这两种方案来说,如果写压力太大或者缓存数据量太大的话,我们可以考虑提高服务器硬件的配置。不过,提高硬件配置成本太高,能力有限,无法动态扩容缩容,局限性太大。从本质上来说,靠堆硬件配置的方式并没有实质性地解决问题,依然无法满足高并发场景下分布式缓存的要求。

为了解决以上问题,redis集群就油然而生了,更能满足高并发场景下分布式缓存的要求

  • 简单来说,Redis 切片集群就是部署多台Redis主节点(master),这些节点之间平等,并没有主从之说,同时对外提供读/写服务。缓存的数据库相对均匀地分布在这些Redis实例上,客户端的请求通过路由规则转发到目标master 上。

  • 为了保障集群整体的高可用,我们需要保证集群中每一个master的高可用,可以通过主从复制给每个master配置一个或者多个从节点(slave) 。

image-20221224200052189

  • Redis Cluster通过分片(Sharding)来进行数据管理,**提供主从复制(Master-Slave Replication)、故障转移(Failover)**等开箱即用的功能,可以非常方便地帮助我们解决 Redis 大数据量缓存以及 Redis服务高可用的问题。
  • Redis Cluster这种方案可以很方便地进行横向拓展(Scale Out),内置了开箱即用的解决方案。当Redis Cluster 的处理能力达到瓶颈无法满足系统要求的时候,直接动态添加Redis 节点到集群中即可。根据官方文档中的介绍,Redis Cluster支持扩展到1000个节点。反之,当Redis Cluster的处理能力远远满足系统要求,同样可以动态删除集群中Redis节点,节省资源。

# 集群cluster有什么优势

可以说,Redis Cluster的动态扩容和缩容是其最大的优势。

虽说Redis Cluster可以扩展到1000个节点,但强烈不推荐这样做,应尽量避免集群中的节点过多。这是因为 Redis Cluster 中的各个节点基于Gossip 协议来进行通信共享信息,当节点过多时,Gossip 协议的效率会显著下降,通信成本剧增。 最后,总结一下Redis Cluster的主要优势:

  • 可以横向扩展缓解写压力和存储压力,支持动态扩容和缩容
  • 具备主从复制、故障转移(内置了Sentinel机制,无需单独部署Sentinel集群)等开箱即用的功能。

# 集群架构是怎么设计的

  • 为了保证高可用,一个redis集群中至少得有3个master、3个slave。即是每个master必须有一个slave,进行主从复制,slave也会实时同步master上的数据

  • 与主从架构不同的是,集群架构的slave不再对外提供读写服务,全部由master来执行相关的服务。slave主要做为副本,当master故障时,由slave替代

  • 如果master只有一个slave 的话,master宕机之后就直接使用这个slave替代master继续提供服务。假设master1出现故障,slave1会直接替代 master1,保证Redis Cluster的高可用。

image-20221224225343254

  • 如果master有多个slave 的话,Redis Cluster 中的其他节点会从这个master的所有slave中选出一个替代 master继续提供服务。Redis Cluster总是希望数据最完整的slave被提升为新的master。
  • Redis Cluster是去中心化的(各个节点基于Gossip进行通信),任何一个master出现故障,其它的 master节点不受影响,因为 key找的是哈希槽而不是Redis节点。不过,Redis Cluster至少要保证宕机的master有一个slave可用。
  • 如果宕机的 master无 slave 的话,为了保障集群的完整性,保证所有的哈希槽都指派给了可用的 master,整个集群将不可用。这种情况下,还是想让集群保持可用的话,可以将cluster-require-full-coverage这个参数设置成no,cluster-require-full-coverage表示需要16384个slot都正常被分配的时候Redis Cluster才可以对外提供服务。

image-20221224225522874

# Redis Cluster是如何进行分片的

  • Redis Cluster并没有使用一致性哈希,采用的是哈希槽分区,每一个键值对都属于-个hash slot(哈希槽)。
  • Redis Cluster通常有16384个哈希槽,要计算给定 key应该分布到哪个哈希槽中,我们只需要先对每个key计算CRC-16 (XMODEM)校验码,然后再对这个校验码对16384(哈希槽的总数)取模,得到的值即是key 对应的哈希槽。
  • 哈希槽的计算公式如下:
HASH_SLOT = CRC16(key) mod NUMER_OF_SLOTS
1
  • 创建并初始化 Redis Cluster的时候,Redis 会自动平均分配这16384个哈希槽到各个节点,不需要我们手动分配。如果你想自己手动调整的话,Redis Cluster 也内置了相关的命令比如ADDSLOTS .ADDSLOTSRANGE(后面会详细介绍到重新分配哈希槽相关的命令)。
  • 假设集群有3个Redis节点组成,每个节点负责整个集群的一部分数据,哈希槽可能是这样分配的(这里只是演示,实际效果可能会有差异)∶

·Node 1 : 0 - 5500的hash slots

·Node 2 : 5501 - 11000 的 hash slots

·Node 3 : 11001 - 16383的hash slots

在任意一个master节点上执行CLUSTER SLOTS命令即可返回哈希槽和节点的映射关系:

image-20221224225926845

客户端连接Redis Cluster中任意一个master节点即可访问Redis Cluster的数据,当客户端发送命令请求的时候,需要先根据key通过上面的计算公示找到的对应的哈希槽,然后再查询哈希槽和节点的映射关系,即可找到目标节点。

image-20221224230039219

image-20221224230058329

这个时候你可能就会疑问:为什么还会存在找错节点的情况呢?根据公式计算难道还会出错?

这是因为 Redis Cluster内部可能会重新分配哈希槽比如扩容缩容的时候(后文中有详细介绍到Redis Cluster的扩容和缩容问题),这就可能会导致客户端缓存的哈希槽分配信息会有误。

从上面的介绍中,我们可以简单总结出 Redis Cluster哈希槽分区机制的优点:解耦了数据和节点之间的关系,提升了集群的横向扩展性和容错性。

# 为什么哈希槽有16384个(2^14)

https://cloud.tencent.com/developer/article/1892966

CRC16算法产生的校验码有16位,理论上可以产生65536 (216,0~65535)个值。为什么Redis Cluster的哈希槽偏偏选择的是16384 (2个14)个呢?

antirez认为哈希槽是16384 (2的14次方)个的原因是:

  • 正常的心跳包会携带一个节点的完整配置,它会以幂等的方式更新l旧的配置,这意味着心跳包会附带当前节点的负责的哈希槽的信息。假设哈希槽采用16384 ,则占空间2k(16384/8)。假设哈希槽采用65536,则占空间8k(65536/8),这是令人难以接受的内存占用。
  • 由于其他设计上的权衡,Redis Cluster 不太可能扩展到超过1000个主节点。
  • 也就是说,65536个固然可以确保每个主节点有足够的哈希槽,但其占用的空间太大。而且,Redis Cluster的主节点通常不会扩展太多,16384个哈希槽完全足够用了。

在cluster.h 文件中定义了消息结构clusterMsg

image-20221224231416349

myslots字段用于存储哈希槽信息,属于无符号类型的 char数组,数组长度为16384/8 = 2048。C语言中的char只占用一个字节,而Java语言中char占用两个字节,小伙伴们不要搞混了。

这里实际就是通过bitmap这种数据结构维护的哈希槽信息,每一个bit代表一个哈希槽,每个bit只能存储0/1。如果该位为1,表示这个哈希槽是属于这个节点。

image-20221224231501501

消息传输过程中,会对myslots进行压缩,bitmap 的填充率越低,压缩率越高。bitmap 的填充率的值是哈希槽总数/节点数,如果哈希槽总数太大的话,bitmap 的填充率的值也会比较大。

最后,总结一下Redis Cluster的哈希槽的数量选择16384而不是65536的主要原因:

  • 哈希槽太大会导致心跳包太大,消耗太多带宽;
  • 哈希槽总数越少,对存储哈希槽信息的 bitmap压缩效果越好;
  • Redis Cluster的主节点通常不会扩展太多,16384个哈希槽已经足够用了。

# Redis Cluster 扩容缩容期间可以提供服务吗?

类似的问题:

●如果客户端访问的 key 所属的槽正在迁移怎么办? ●如何确定给定 key 的应该分布到哪个哈希槽中?

Redis Cluster 扩容和缩容本质是进行重新分片,动态迁移哈希槽。

为了保证 Redis Cluster 在扩容和缩容期间依然能够对外正常提供服务,Redis Cluster 提供了重定向机制,两种不同的类型:

●ASK 重定向 :可以看做是临时重定向,后续查询仍然发送到旧节点。

●MOVED 重定向 :可以看做是永久重定向,后续查询发送到新节点。

客户端向指定节点发送请求命令,从客户端的角度来看,ASK 重定向是下面这样的:

1 如果请求的 key 对应的哈希槽还在当前节点的话,就直接响应客户端的请求。

2 如果请求的 key 对应的哈希槽在迁移过程中,但是请求的 key 还未迁移走的话,说明当前节点任然可以处理当前请求,同样可以直接响应客户端的请求。

3 如果客户端请求的 key 对应的哈希槽当前正在迁移至新的节点且请求的 key 已经被迁移走的话,就会返回 -ASK 重定向错误,告知客户端要将请求发送到哈希槽被迁移到的目标节点。 -ASK 重定向错误信息中包含请求 key 迁移到的新节点的信息。

4 客户端收到 -ASK 重定向错误后,将会临时(一次性)重定向,自动向新节点发送一条 ASKING (opens new window) 命令。也就是说,接收到 ASKING 命令的节点会强制执行一次请求,下次再来需要重新提前发送 ASKING 命令。

5 新节点在收到 ASKING 命令后可能会返回重试错误(TRYAGAIN),因为可能存在当前请求的 key 还在导入中但未导入完成的情况。

6 客户端发送真正需要请求的命令。

7 ASK 重定向并不会同步更新客户端缓存的哈希槽分配信息,也就是说,客户端对正在迁移的相同哈希槽的请求依然会发送到旧节点而不是新节点。

image-20230624175209455

如果客户端请求的 key 对应的哈希槽已经迁移完成的话,就会返回 -MOVED 重定向错误,告知客户端当前哈希槽是由哪个节点负责,客户端向新节点发送请求并更新缓存的哈希槽分配信息,后续查询将被发送到新节点。

# Redis Cluster 中的节点是怎么进行通信的?

Redis Cluster 是一个典型的分布式系统,分布式系统中的各个节点需要互相通信。既然要相互通信就要遵循一致的通信协议,Redis Cluster 中的各个节点基于 Gossip 协议 来进行通信共享信息,每个 Redis 节点都维护了一份集群的状态信息。

Redis Cluster 的节点之间会相互发送多种 Gossip 消息:

●MEET :在 Redis Cluster 中的某个 Redis 节点上执行 CLUSTER MEET ip port 命令,可以向指定的 Redis 节点发送一条 MEET 信息,用于将其添加进 Redis Cluster 成为新的 Redis 节点。

●PING/PONG :Redis Cluster 中的节点都会定时地向其他节点发送 PING 消息,来交换各个节点状态信息,检查各个节点状态,包括在线状态、疑似下线状态 PFAIL 和已下线状态 FAIL。

●FAIL :Redis Cluster 中的节点 A 发现 B 节点 PFAIL ,并且在下线报告的有效期限内集群中半数以上的节点将 B 节点标记为 PFAIL,节点 A 就会向集群广播一条 FAIL 消息,通知其他节点将故障节点 B 标记为 FAIL 。

●......

有了 Redis Cluster 之后,不需要专门部署 Sentinel 集群服务了。Redis Cluster 相当于是内置了 Sentinel 机制,Redis Cluster 内部的各个 Redis 节点通过 Gossip 协议互相探测健康状态,在故障时可以自动切换。

cluster.h 文件中定义了所有的消息类型(源码地址:https://github.com/redis/redis/blob/7.0/src/cluster.h) 。Redis 3.0 版本的时候只有 9 种消息类型,到了 7.0 版本的时候已经有 11 种消息类型了。

// 注意,PING 、 PONG 和 MEET 实际上是同一种消息。

// PONG 是对 PING 的回复,它的实际格式也为 PING 消息,

// 而 MEET 则是一种特殊的 PING 消息,用于强制消息的接收者将消息的发送者添加到集群中(如果节点尚未在节点列表中的话)

#define CLUSTERMSG_TYPE_PING 0          /* Ping 消息 */

#define CLUSTERMSG_TYPE_PONG 1          /* Pong 用于回复Ping */

#define CLUSTERMSG_TYPE_MEET 2          /* Meet 请求将某个节点添加到集群中 */

#define CLUSTERMSG_TYPE_FAIL 3          /* Fail 将某个节点标记为 FAIL */

#define CLUSTERMSG_TYPE_PUBLISH 4       /* 通过发布与订阅功能广播消息 */

#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* 请求进行故障转移操作,要求消息的接收者通过投票来支持消息的发送者 */

#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6     /* 消息的接收者同意向消息的发送者投票 */

#define CLUSTERMSG_TYPE_UPDATE 7        /* slots 已经发生变化,消息发送者要求消息接收者进行相应的更新 */

#define CLUSTERMSG_TYPE_MFSTART 8       /* 为了进行手动故障转移,暂停各个客户端 */

#define CLUSTERMSG_TYPE_MODULE 9        /* 模块集群API消息 */

#define CLUSTERMSG_TYPE_PUBLISHSHARD 10 /* 通过发布与订阅功能广播分片消息 */

#define CLUSTERMSG_TYPE_COUNT 11        /* 消息总数 */
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

cluster.h 文件中定义了消息结构 clusterMsg(源码地址:https://github.com/redis/redis/blob/7.0/src/cluster.h) :

typedef struct {

   char sig[4];        /* 标志位,"RCmb" (Redis Cluster message bus). */

   uint32_t totlen;    /* 消息总长度 */

   uint16_t ver;       /* 消息协议版本 */

   uint16_t port;      /* 端口 */

   uint16_t type;      /* 消息类型 */

   char sender[CLUSTER_NAMELEN];  /* 消息发送节点的名字(ID) */

   // 本节点负责的哈希槽信息,16384/8 个 char 数组,一共为16384bit

   unsigned char myslots[CLUSTER_SLOTS/8];

   // 如果消息发送者是一个从节点,那么这里记录的是消息发送者正在复制的主节点的名字

   // 如果消息发送者是一个主节点,那么这里记录的是 REDIS_NODE_NULL_NAME

   // (一个 40 字节长,值全为 0 的字节数组)

   char slaveof[CLUSTER_NAMELEN];

   // 省略部分属性

   // ......

   // 集群的状态

   unsigned char state;

   // 消息的内容

   union clusterMsgData data;

} clusterMsg;


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

clusterMsgData 是一个联合体(union),可以为 PING,MEET,PONG 、FAIL 等消息类型。当消息为 PING、MEET 和 PONG 类型时,都是 ping 字段是被赋值的,这也就解释了为什么我们上面说 PING 、 PONG 和 MEET 实际上是同一种消息。

union clusterMsgData {

    /* PING, MEET and PONG */

    struct {

        /* Array of N clusterMsgDataGossip structures */

        clusterMsgDataGossip gossip[1];

    } ping;

    /* FAIL */

    struct {

        clusterMsgDataFail about;

    } fail;

    /* PUBLISH */

    struct {

        clusterMsgDataPublish msg;

    } publish;

    /* UPDATE */

    struct {

        clusterMsgDataUpdate nodecfg;

    } update;

    /* MODULE */

    struct {

        clusterMsgModule msg;

    } module;

};


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
编辑 (opens new window)
上次更新: 2024/02/22, 14:03:19
redis内存淘汰策略
集群下的redis2

← redis内存淘汰策略 集群下的redis2→

最近更新
01
spark基础
02-22
02
mysql读写分离和分库分表
02-22
03
数据库迁移
02-22
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式