Redis的主从复制、哨兵模式和集群模式的原理剖析


1. 单机架构

单机架构模式中只有一个Redis服务器,它既负责数据的存储,也负责数据的读取。架构示意如下所示:


image-20200813110750078

但是在实际的应用场景中并不会使用单机架构模式,它无法有效的解决如下的问题:

  • 数据量伸缩问题:如果将Redis作为缓存数据库使用,我们可以通过设置缓存失效时间,以及不同的淘汰策略来清除失效的数据,避免数据常占Redis的内存空间。但是,当Redis作为一个NoSQL数据库使用时,随着数据量的增大,单机模式显然无法满足要求
  • 访问量伸缩问题:Redis是基于单线程运行的,当读写访问请求很多时,Redis节点无法满足正常的响应时间要求
  • 节点故障问题:当Redis节点发生宕机时,无论是读请求还是写请求,节点都无法正常提供服务

2. 主从架构

主从架构示意图如下所示:


image-20200813112447705

2.1 配置

下面我们同样通过docker-compsoe来搭建Redis主从架构模式的服务器。首先,编写对应的docker-compose.yml文件,内容如下:

version: '3.1'
services:
  # master
  redis1:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis1
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7001:6379
    volumes:
      - ./conf/redis1.conf:/usr/local/redis/redis.conf
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  # slave1
  redis2:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis2
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7002:6379
    volumes:
      - ./conf/redis2.conf:/usr/local/redis/redis.conf
    # 通过links指定它的master是谁
    links:
      - redis1:master
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  # slave2
  redis3:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis3
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7003:6379
    volumes:
      - ./conf/redis3.conf:/usr/local/redis/redis.conf
    links:
      - redis1:master
    command: ["redis-server", "/usr/local/redis/redis.conf"]

相应的需要为每一个节点准备一个配置文件,并且在redis2.conf和redis3.conf中添加salve节点相应的配置。

# 从节点配置
replicaof master 6379

最后启动容器,通过命令进入到mater节点后,使用info命令查看节点信息可以看到,当前节点为master,而且它有两个slave,对应的ip和port也可以看到。

# Replication
role:master
connected_slaves:2
slave0:ip=172.23.0.3,port=6379,state=online,offset=56,lag=0
slave1:ip=172.23.0.4,port=6379,state=online,offset=56,lag=0

然后进入一个slave节点,同样使用info命令查看节点信息:

# Replication
role:slave
master_host:master
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
2.2 原理

主从复制是指将一个Redis节点上的数据,复制到其他的 Redis节点上。前者称为 主节点(master),后者称为 从节点(slave)。而且数据的复制是 单向 的,只能由主节点到从节点。Redis 主从复制支持 主从同步从从同步 两种。主从复制的优势在于:

  • 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
  • 故障恢复 :当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复
  • 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务。在写少读多的场景下,通过多个从节点分担读请求,可以大大提高 Redis节点的并发量
  • 高可用基石 :主从复制还是哨兵模式和集群模式能够实施的基础

整个主从复制的原理如下所示:


image-20200913091552559
source

整个过程可以分为准备阶段数据同步阶段命令传播三个阶段。其中同步操作有SYNCPSYNC两个命令可以选择使用,其中SYNC命令消耗资源非常多,每次执行命令,master节点都需要执行如下的操作:

  • master生成RDB快照,消耗大量的CPU、内存和磁盘IO资源
  • master节点需要将生成的RDB文件发送给slave节点,这又会消耗大量的网络资源,并且会影响master节点的响应时间
  • slave节点在收到RDB文件后需要载入,载入期间会因为阻塞而没办法处理读命令请求

PSYNC命令有两种模式:

  • 全量复制 :用于初次复制或者无法部分复制的场景,它会将master节点的数据全部发送给slave节点
  • 部分复制:用于网络中断后的复制,只将中断期间master节点执行的写命令发送给slave节点。如果中断时间太长,导致master无法完整的保存中断期间执行的写命令,那么仍会使用部分复制

sync负责把缓存区上的东西排到写队列中(缓冲区->写队列),在由守护进程负责把队列里的东西写到磁盘上,而sync函数在把缓存区上的东西排到写队列后,不管写队列中的内容是否写到磁盘上都立即返回。fsync函数则是对指定文件的操作,而且必须等到写队列中的内容都写到磁盘后才返回,并且更新文件inode结点里的内容。

下面着重看一下PSYNC的主要流程,如下所示:


在这里插入图片描述

master节点端的操作如下:

  • 首先,slave节点向自己的master节点发起sync命令,master将新进的slave节点加入到自己的slave列表中,,然后执行bgsave来进行持久化操作
  • 持久化操作结束后,将得到的快照发送给slave节点。发送期间,如果master节点收到来自客户端的写命令,除了正常的响应外,还会存一份到back-log中
  • 当快照发送结束后,还将back-log队列中的信息发送给salve节点。全部发送结束后,master节点后续的写操作同时发送给slave节点,保持实时的异步复制

slave节点端的操作如下:

  • 当slave节点发送sync命令后,继续对外提供读服务
  • 当slave节点收到快照后,将slave现有的数据清空,再将快照写入到自己的内存空间。同时接收back-log,执行重放操作,期间继续执行读服务
  • 重放完成后,slave节点将继续接收master节点的命令副本并继续重放,保证主从之间的数据一致性

如果master节点对应的多个slave节点发送了多个sync命令,只要在master节点bgsave操作执行结束之前到达,那么这些salve节点收到的快照是一样的。如果master节点在执行了一次主从复制后,收到了新的sync命令,那么将再执行一次复制操作。


3. 哨兵架构

哨兵(sentinel)的主要作用为:当master节点发生故障时,众多slave节点中的一个可晋升为新的master节点来提供写服务,其余的slave节点仍然提供读服务,这种机制也别称为故障转移(failover)。主从复制+哨兵的架构模式如下所示:


image-20200813114003771

3.1 配置

下面通过docker-compose演示如何在主从架构的基础上启用哨兵机制。修改docker-compose.yml文件内容,在volume中多加一个数据卷的映射,对应的配置文件为哨兵所需的.conf配置文件。

version: '3.1'
services:
  redis1:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis1
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7001:6379
    volumes:
      - ./conf/redis1.conf:/usr/local/redis/redis.conf
      - ./conf/sentinel1.conf:/data/sentinel.conf
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  redis2:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis2
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7002:6379
    volumes:
      - ./conf/redis2.conf:/usr/local/redis/redis.conf
      - ./conf/sentinel2.conf:/data/sentinel.conf
    links:
      - redis1:master
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  redis3:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis3
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7003:6379
    volumes:
      - ./conf/redis3.conf:/usr/local/redis/redis.conf
      - ./conf/sentinel3.conf:/data/sentinel.conf
    links:
      - redis1:master
    command: ["redis-server", "/usr/local/redis/redis.conf"]

并且在宿主机的conf目录下新建三个.conf配置文件。其中master对应的sentinel1.conf文件内容为:

# 哨兵需要后台启动
daemonize yes
# 指定master节点的ip和端口
sentinel monitor master 127.0.0.1 6379 2
# 哨兵每隔多久监听一次Redis架构
sentinel down-after-milliseconds master 10000

在slave节点的哨兵配置文件中填写如下内容:

# 哨兵需要后台启动
daemonize yes
# 指定master节点的ip和端口
sentinel monitor master master 6379 2
# 哨兵每隔多久监听一次Redis架构
sentinel down-after-milliseconds master 10000

然后使用docker-compose up -d启动容器,可以看到此时redis都已经正常启动,但是哨兵并没有启动。


image-20200813144816477

接着需要分别进入到三个Redis容器内部,使用redis-sentinel sentinel1.confredis-sentinel sentinel2.confredis-sentinel sentinel2.conf分别启动哨兵。退出容器后,再次查看宿主机的sentinel1.conf文件可以看到如下内容:


image-20200813145121632

至此,带有哨兵机制的主从架构就启动完毕。

3.2 原理

哨兵具有如下的功能:

  • 监控:哨兵会不断的检查master节点和slave节点是否正常运作
  • 自动故障转移:当哨兵发现master节点无法正常工作,它会将对应的slave节点中的一个晋升为master节点。即使出错的master节点后续恢复正常,它也只能作为新master节点的slave
  • 配置提供者:客户端初始化时,通过哨兵来获取当前Redis服务的master节点配置
  • 通知:哨兵可以将故障转移的结果发送给客户端

其中,当master节点发生故障时,新的master节点的选择原则如下:

  1. 在失效主服务器属下的从服务器当中, 那些被标记为主观下线、已断线、或者最后一次回复 PING 命令的时间大于五秒钟的从服务器都会被 淘汰
  2. 在失效主服务器属下的从服务器当中, 那些与失效主服务器连接断开的时长超过 down-after 选项指定的时长十倍的从服务器都会被 淘汰
  3. 经历了以上两轮淘汰之后 剩下来的从服务器中, 我们选出 复制偏移量(replication offset)最大 的那个 从服务器 作为新的主服务器;如果复制偏移量不可用,或者从服务器的复制偏移量相同,那么 带有最小运行 ID 的那个从服务器成为新的主服务器

那么,哨兵是如何感知到master节点出现故障了呢? 哨兵节点会定期的向master节点发送心跳包判断其是否存活,称为PING。当某个哨兵发现master发生了故障,它将其标记为主观不可用状态,然后将其发送给其他的哨兵节点。如果确认master故障的哨兵节点个数超过了设置的阈值,则认为该master客观不可用,进行故障转移的流程。


3. 集群架构

集群机构模式如下所示,它有多个Redis节点组组成,每个节点组所服务的数据之间不存在交集。其中,每个节点组都有且仅有一个master节点,如果使用主从复制,那么可以有0到多个slave节点。master节点提供写服务和读服务,而slave节点只提供读服务。


image-20200813150424238

3.1 配置

下面通过docker-compose来搭建集群。首先编写docker-compose.yml文件,内容如下:

version: '3.1'
services:
  redis1:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis1
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7001:7001
      - 17001:17001
    volumes:
      - ./conf/redis1.conf:/usr/local/redis/redis.conf
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  redis2:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis2
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7002:7002
      - 17002:17002
    volumes:
      - ./conf/redis2.conf:/usr/local/redis/redis.conf
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  redis3:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis3
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7003:7003
      - 17003:17003
    volumes:
      - ./conf/redis3.conf:/usr/local/redis/redis.conf
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  redis4:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis4
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7004:7004
      - 17004:17004
    volumes:
      - ./conf/redis4.conf:/usr/local/redis/redis.conf
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  redis5:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis5
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7005:7005
      - 17005:17005
    volumes:
      - ./conf/redis5.conf:/usr/local/redis/redis.conf
    command: ["redis-server", "/usr/local/redis/redis.conf"]
  redis6:
    image: daocloud.io/library/redis:5.0.7
    restart: always
    container_name: redis6
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7006:7006
      - 17006:17006
    volumes:
      - ./conf/redis6.conf:/usr/local/redis/redis.conf
    command: ["redis-server", "/usr/local/redis/redis.conf"]

每个节点的配置文件内容格式如下:

# 指定Redis的端口号
port 7001
# 开启集群架构模式
cluster-enabled yes
# 集群信息的文件
cluster-config-file nodes-7001.conf
# 集群对外ip
cluster-announce-ip 121.199.75.6
# 集群对外端口号
cluster-announce-port 7001
# 集群的总线端口
cluster-announce-bus-port 17001

编写好相应的配置文件后,使用doker-compose up -d启动全部的Redis容器。容器启动后,随便进入到一个容器中,使用如下命令进行集群节点之间的互连:

redis-cli --cluster create 121.199.75.6:7001 121.199.75.6:7002 121.199.75.6:7003 121.199.75.6:7004 121.199.75.6:7005 121.199.75.6:7006 --cluster-replicas 1

等待一段时间,Redis的集群模式就搭建完毕了。

3.2 原理

Redis集群架构可以保证主从+哨兵的基本功能之外,继续提升Redis存储数据和并发响应的能力。Redis集群模式无中心,集群中每个Redis节点之间都是互相两两连接,客户端只需要随机的连接到其中任意一个节点上,就可以对集群中其他的Redis节点执行读写操作。


image-20200913093620706
source

不同的节点之间通过Redis Cluster Bus进行交互,交换的信息有:

  • slot槽和节点之间的对应关系
  • 集群中每个节点的可用状态
  • 当集群结构发生改变时,需要一定的协议对集群内所有的节点的配置信息达成一致
  • 发布/订阅功能需要传递的信息

Redis集群中内置了16384个哈希槽,客户端连接到 Redis 集群之后,会同时得到一份关于这个 集群的配置信息。当客户端具体对某一个 key 值进行操作时,会使用CRC16校验后计算出它的Hash值,然后把哈希值对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽。

Redis 会根据节点数量 大致均等 的将哈希槽映射到不同的节点,再结合集群的配置信息就能够知道这个 key 值应该存储在哪一个具体的 Redis 节点中。如果不属于自己管,那么就会使用一个特殊的 MOVED 命令来进行一个跳转,告诉客户端去连接指定的节点执行读写请求。

cluster不支持跨节点的命令,如果一个请求中涉及到两个节点的key,那么请求将操作失败。Redis为了解决这个问题,引入了HashTag的概念,允许只使用key的某一部分计算哈希值,进而划分到相应的哈希槽。如果两个key某些部分是一致的,那么它们将会落到同一个槽中。

集群相对于前两种模式具有如下的优点:

  • 数据分区:集群将数据分散到多个节点,一方面突破了 Redis 单机内存大小的限制,存储容量大大增加;另一方面,每个master节点都可以对外提供读写服务,大大提高了集群的响应能力
  • 高可用:集群支持主从复制和主节点的自动故障转移(与哨兵类似),当任一节点发生故障时,集群仍然可以对外提供服务
3.3 分区方案

常用的分区方案有如下三种:

  • 哈希值 % 节点数:这是最简单也是最直接的一种方式,简单的取模进行节点的选择。但是当新增或是删减节点导致节点的数量发生变化时,系统中所有的数据都需要重新计算映射关系,使得数据迁移工作繁重

  • 一致性哈希分区:将整个哈希值空间组织为一个虚拟的圆环,范围是 [ 0 − 2 32 − 1 ] [0 - 2^{32-1}] [02321]。对于每一个数据来说,首先根据key计算得到哈希值后,明确了它在环上的位置,然后从该位置顺时针往下走,找到的第一个节点就是它需要映射的节点。

    image-20200913094306032
    source

    当节点的数量发生变化时,对于数据的影响仅限于附近的数据。以上图为例,如果在 node1node2 之间增加 node5,则只有 node2 中的一部分数据会迁移到 node5;如果去掉 node2,则原 node2 中的数据只会迁移到 node4 中,只有 node4 会受影响。

    但是,当节点的数量比较少时,增加或删除节点可能会导致数据倾斜,造成严重的数据不均衡。例如,如果去掉 node2node4 中的数据由总数据的 1/4 左右变为 1/2 左右,与其他节点相比负载过高。

  • 带有虚拟节点的一致性哈希分区:即上面所介绍的Redis中哈希槽的思想,每个实际的节点都包含一定数量的哈希槽,每个槽包含的数据都在一定的范围内,它是数据管理和迁移的基本单位,此时节点数量的变化对于系统的影响就很小。槽解耦了数据和实际节点之间的关系,增加或删除节点对系统的影响很小。

问:当集群中增删节点,哈希槽的分配是如何变化的呢?

答:仍以上图为例,系统中有 4 个实际节点,假设为其分配 16 个槽(0-15),槽 0-3位于node14-7位于node2;以此类推….如果此时删除 node2,只需要将槽4-7重新分配即可,例如槽 4-5 分配给 node1,槽 6 分配给 node3,槽 7 分配给 node4;可以看出删除 node2后,数据在其他节点的分布仍然较为均衡。

3.4 slot迁移

当新增节点、节点下线或者因负载不均需要充分调整slot分布时,就会触发slot迁移操作。slot的迁移操作由外部的系统完成,Redis只是提供了过程中所需的原语供调用。这些原语主要包含两种:

  • 节点迁移状态设置:源节点或是目标节点
  • key迁移的原子命令:执行具体的迁移工作

slot迁移的过程示意图如下所示:


image-20200914222941702

假设需要将A节点中的id为1的slot迁移到B节点,那么整个迁移的过程为:

  • 向B节点发送状态变更命令,将B节点对应的slot设置为IMPORTING
  • 向A节点发送状态变更命令,将A节点对应的slot设置为MIGERTAING
  • 针对于A的slot中所有的key,分别向A发送MIGRATE命令,告诉A将对应的key迁移到B上

由于两个节点在迁移的过程中仍然会相应客户端的请求,因此,面对请求两个节点的操作和正常状态下是有区别的:

  • 对于A节点来说,如果客户端访问的key仍在A上,则正常处理该请求;如果key已经或者正在迁移到B,那么A会回复客户端ASK消息,让它跳转到B上执行
  • 对于B节点来说,所有非ASK请求跳转而来的操作都不进行处理,而是回复MOVED命令,让客户端跳转到A执行

这样保证了同一个key在迁移之前总是在源节点执行操作,迁移之后总是在目标节点上执行操作。当slot中所有的key都成功的迁移到了B上,客户端使用CLUSTER SETSLOT命令,这是B的slot信息,让B感知到迁移的slot。

3.5 failover

集群的故障迁移需要解决如下的几个问题:

  • 如果某个节点发生了故障,它如何让集群中其他的节点感知到
  • 如果多个节点感知到了故障信息,那么如何确定感知到的节点一定就是发生了故障
  • 如果确定发生了故障,如果将故障节点的一个slave晋升为新的master节点
  • 当升级完成后,如何让集群所有的节点感知到变更的发生

集群中节点两两之间通过Redis Cluster Bus来进行PING/PONG通信,当节点A向节点B通信时,如果发送的PING消息没有正常得到PONG消息,那么节点A将节点B置为PFAIL(possible fail)状态。之后,节点A与集群中其他的节点使用gossip协议进行通信时,PFAIL状态信息就会传递到其他的节点。

节点A之后也会收到其他节点的PONG信息,如果其他节点认为节点B确实发生了故障的数量超过了一定值,那么节点A就将节点B设置为FAIL状态。

当节点B被设置为FAIL状态后,需要从它的多个slave节点中选举一个升级为新的master节点。slave节点在晋升前会进行协商优先级,优先级的重要决定因素就是slave节点最后一次同步master信息的时间,越近表示这个slave节点的数据越新,相应的优先级就越高。优先级越高的slave节点更有可能早发起选举,成为新的master节点的可能性越高。

参加选举的slave节点会向其他的master节点发送消息,如果master节点本轮还没有投票,则回复同意,否则拒绝。当slave节点收到超过半数的master节点同意的消息,则它升级为新的master节点。然后它通过PONG消息将最新的epoch进行广播,使得集群中其他的节点可以及时更新拓扑结构,直到集群收敛。

3.6 优化手段

如果集群中的节点设置了主从复制,那么客户端对于master节点的读请求会被直接响应,而对于slave节点的读请求,会被MOVED消息转移到master节点,由master节点响应。为了更好的满足读写分离的需求,Redis提供了READONLY命令,如果slave节点收到的是该命令,则直接处理请求,无需再转换到master节点处理。

另外,Redis集群还提供了单点保护功能。假设master节点A只有一个slave节点,当它的slave发生故障时,集群为了继续保证高可用性,会将节点B的slave进行副本迁移后,让它重新成为节点A的slave使用。为了满足这种需求,集群中节点的数量应该保持为2 * master + 1个节点。迁移的示意过程如下所示:


image-20200915095818275


4. 节点间的通信
4.1 端口

集群中所有的节点都存储数据,并且参与集群状态的维护。为此,每个节点都提供了两个TCP端口:

  • 普通端口:主要用于为客户端提供服务,以及节点之间数据的迁移,通常定义为7000、70001等
  • 集群端口:通常定义为普通端口+10000,它主要用于节点之间的通信,如集群搭建、节点增减和数据迁移等操作时,节点之间的通信
4.2 协议

节点之间的通信协议通常可以分为点对点、广播、Gossip协议等类型,其中:

  • 点对点:两个节点之间的通信,其他的节点不参与它们之间的通信
  • 广播:向集群内所有的节点发送信息,优点在于集群收敛速度快,但每条消息需要发送给集群内所有的节点,消耗CPU、带宽等资源较多
  • Goosip协议:指在节点数量有限的网络中,每个节点都按照某种规则随机的与部分节点进行通信,经过一段时间的通信,每个节点的状态很快会达到一致。它的优点在于负载比广播方式低、去中心化、容错性高等,当时相比于广播方式,它的收敛速度要慢一些

集群收敛指集群内所有的节点获取到的信息是一致的。

4.3 消息

集群的节点采用每秒10次的定时任务进行通信工作,例如判断是否需要发送消息及消息的类型、确定接收节点、发送消息等。如果集群状态发生了变化,如果节点数量改变、槽状态改变等,通过节点之间的通信可以很快的让所有节点都收到消息,使集群快速收敛。

节点之间发送的消息主要分为如下5种,不同的消息类型、通信协议、发送的频率和时间、接收节点的选择等是不同的:

消息类型描述
MEET在节点握手阶段,当节点收到客户端的 CLUSTER MEET 命令时,会向新加入的节点发送 MEET 消息,请求新节点加入到当前集群;新节点收到 MEET 消息后会回复一个 PONG 消息
PING集群里每个节点每秒钟会选择部分节点发送 PING 消息,接收者收到消息后会回复一个 PONG 消息。PING 消息的内容是自身节点和部分其他节点的状态信息,作用是彼此交换信息,以及检测节点是否在线。PING 消息使用 Gossip 协议发送,接收节点的选择兼顾了收敛速度和带宽成本,具体规则如下:随机找 5 个节点,在其中选择最久没有通信的 1 个节点;扫描节点列表,选择最近一次收到 PONG 消息时间大于 cluster_node_timeout / 2 的所有节点,防止这些节点长时间未更新
PONGPONG 消息封装了自身状态数据。可以分为两种:第一种 是在接到 MEET/PING 消息后回复的 PONG 消息;第二种 是指节点向集群广播 PONG 消息,这样其他节点可以获知该节点的最新信息,例如故障恢复后新的主节点会广播 PONG 消息
FAIL当一个主节点判断另一个主节点进入 FAIL 状态时,会向集群广播这一 FAIL 消息;接收节点会将这一 FAIL 消息保存起来,便于后续的判断
PUBLISH节点收到 PUBLISH 命令后,会先执行该命令,然后向集群广播这一消息,接收节点也会执行该 PUBLISH 命令
4.4 数据结构

节点为了存储集群状态而提供的数据结构中,最关键的是 clusterNodeclusterState 结构:前者记录了一个节点的状态,后者记录了集群作为一个整体的状态。

  • clusterNode :保存了 一个节点的当前状态,包括创建时间、节点 id、ip 和端口号等。每个节点都会用一个 clusterNode 结构记录自己的状态,并为集群内所有其他节点都创建一个 clusterNode 结构来记录节点状态

     typedef struct clusterNode {
         //节点创建时间
         mstime_t ctime;
         //节点id
         char name[REDIS_CLUSTER_NAMELEN];
         //节点的ip和端口号
         char ip[REDIS_IP_STR_LEN];
         int port;
         //节点标识:整型,每个bit都代表了不同状态,如节点的主从状态、是否在线、是否在握手等
         int flags;
         //配置纪元:故障转移时起作用,类似于哨兵的配置纪元
         uint64_t configEpoch;
         //槽在该节点中的分布:占用16384/8个字节,16384个比特;每个比特对应一个槽:比特值为1,则该比特对应的槽在节点中;比特值为0,则该比特对应的槽不在节点中
         unsigned char slots[16384/8];
         //节点中槽的数量
         int numslots;
         …………
     } clusterNode;
    

    除了上述字段,clusterNode 还包含节点连接、主从复制、故障发现和转移需要的信息等。

  • clusterState :保存了在当前节点视角下,集群所处的状态。主要字段包括:

    typedef struct clusterState {
        //自身节点
        clusterNode *myself;
        //配置纪元
        uint64_t currentEpoch;
        //集群状态:在线还是下线
        int state;
        //集群中至少包含一个槽的节点数量
        int size;
        //哈希表,节点名称->clusterNode节点指针
        dict *nodes;
        //槽分布信息:数组的每个元素都是一个指向clusterNode结构的指针;如果槽还没有分配给任何节点,则为NULL
        clusterNode *slots[16384];
        …………
    } clusterState;
    

    除此之外,clusterState 还包括故障转移、槽迁移等需要的信息。

Redis(9)——史上最强【集群】入门实践教程


5. 淘汰机制

当key的生存时间到时,并不会立即删除,而是采用下面的两种方式进行删除:

  • 定期删除:Redis每隔一段时间就会查看设置了过期时间的key,一般会在100ms的间隔中默认查看3个key
  • 惰性删除:当用户去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,如果生存时间已到,直接删除key,并且返回给用户一个空值

在Redis已经没有存储空间的情况下,添加一个新数据,Redis就会执行淘汰机制。Redis支持的淘汰机制有如下几种:

  • volatile-lru:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个最近最少使用的key
  • allkeys-lru:当内存不足时,Redis会在全部的key中淘汰掉一个最近最少使用的key
  • volatile-lfu:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个最近最少频次使用的key
  • allkeys-lfu:当内存不足时,Redis会在全部的key中淘汰掉一个最近最少频次使用的key
  • volatile-random:当内存不足时,Redis会在过了生存时间的key中随机淘汰掉一个key
  • allkeys-random:当内存不足时,Redis会在全部的key中随机淘汰掉一个key
  • `volatile-ttl:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个剩余生存时间最少的key
  • noeviction(默认):当内存不足时,直接报错

通过maxmemory-policy策略的命令来执行具体使用的淘汰机制。同时,还可以通过maxmemory 字节大小来设置redis的最大内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值