文章目录

  • 集群
  • 主从模式
  • 如何进行主从同步?
  • 会带来什么问题?
  • 在同步阶段网络断了怎么处理?(增量复制只会把主从库网络断连期间主库收到的命令,同步给从库)
  • 哨兵模式(主库挂了,如何不间断服务?)
  • 哨兵机制的基本流程
  • 如何判定一个主库或者从库 gg了?
  • 如何选定新主库?
  • 筛选打分
  • 哨兵集群的组成和运行机制
  • 由哪个哨兵执行主从切换?
  • 集群模式(数据切片模式)
  • 概述
  • Redis 集群的数据分布之分片
  • clusterNode 结构解读
  • clusterState 结构解读
  • 在集群中执行重定向
  • 重新分片
  • 复制与故障转移
  • 故障检测
  • 故障转移
  • 慢查询


集群

主从模式

如何进行主从同步?

当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof (Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。

redis查看集群状态命令 redis 查看集群信息_Redis

  1. 第一步:从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。,主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数
  2. 第二步:主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照生成的 RDB 文件
  3. 第三步:主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了

会带来什么问题?

  1. 生成 RDB 文件和传输 RDB 文件 会浪费大量的 CPU 资源和贷带宽资源。

如何解决呐?

在主从库模式中,所有的从库都是和主库连接,所有的全量复制也都是和主库进行的。现在,我们可以通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。(其实就是选一些亲信来让亲信完成同步操作,想想皇宫的口谕是怎么传下来的即可

redis查看集群状态命令 redis 查看集群信息_redis查看集群状态命令_02

在同步阶段网络断了怎么处理?(增量复制只会把主从库网络断连期间主库收到的命令,同步给从库)

当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。

repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位
置,从库则会记录自己已经读到的位置。

只要 主从库保存了 offset ,那么就能知道 从库 到底读取到哪里了!

redis查看集群状态命令 redis 查看集群信息_Redis_03


具体流程如下:

redis查看集群状态命令 redis 查看集群信息_redis_04


当然,如果在缓冲区写满后,主库会继续写入,而从库还处于连接断开的过程的话,就会导致缓冲区数据被覆盖。可以再适当调大 缓冲区大小,或者使用 切片集群来分担单个主库的请求压力

哨兵模式(主库挂了,如何不间断服务?)

哨兵机制的基本流程

  • 哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。
如何判定一个主库或者从库 gg了?
  • 发送 PING 命令,看是否在规定的时间内返回响应。
  • 针对主库,还会让多个哨兵一起判定是否 gg

如何选定新主库?

一般来说,哨兵选择新主库的过程称为“筛选 + 打分”。简单来说,我们在多个从库中,先按照一定的筛选条件,把不符合条件的从库去掉。然后,我们再按照一定的规则,给剩下的从库逐个打分,将得分最高的从库选为新主库,如下图所示:

redis查看集群状态命令 redis 查看集群信息_redis查看集群状态命令_05

筛选打分
  1. 是否在线
  2. 网络是否经常断连
  3. slave-priority 配置项比较
  4. 和旧主库同步程度最接近的从库得分高
  5. ID 号小的从库得分高

哨兵集群的组成和运行机制

首先哨兵与哨兵之间可以相互发现,具体是根据 Redis 的 pub/sub 机制
哨兵只要和主库建立起了连接,就可以在主库上发布消息了,比如说发布它自己的连接信息(IP 和端口)。同时,它也可以从主库上订阅消息,获得其他哨兵发布的连接信息。当多个哨兵实例都在主库上做了发布和订阅操作后,它们之间就能知道彼此的 IP 地址和端口。

为了区分不同应用的消息,Redis 会以频道的形式,对这些消息进行分门别类的管理。所谓的频道,实际上就是消息的类别。当消息类别相同时,它们就属于同一个频道。反之,就属于不同的频道。只有订阅了同一个频道的应用,才能通过发布的消息进行信息交换。

在主从集群中,主库上有一个名为“sentinel:hello”的频道,不同哨兵就是通过它来相互发现,实现互相通信的。

redis查看集群状态命令 redis 查看集群信息_数据库_06


发布订阅过程,如图所示。如上就是哨兵之间如何发现对方并形成集群的发布订阅模式,可以看到主要是从 主库通信入手,一点一点得到哨兵信息的,当然,还有一个问题就是哨兵是如何知道从库的 IP 地址和端口的呢?

redis查看集群状态命令 redis 查看集群信息_Redis_07


还差一步,那就是:万一进行了主从库切换,那客户端也是需要知道这个信息的,哨兵有责任完成通知客户端这件事。那么是怎么实现的呐?

答:是通过哨兵的 发布/订阅 机制完成的。客户端可以从哨兵订阅消息。哨兵提供的消息订阅频道有很多,不同频道包含了主从库切换过程中的不同关键事件。

redis查看集群状态命令 redis 查看集群信息_Redis_08


至此,已经完成了哨兵集群的监控、选主和通知三个任务就基本可以正常工作了。但是 主库故障以后,哨兵集群有多个实例,那怎么确定由哪个哨兵来进行实际的主从切换呢?

由哪个哨兵执行主从切换?

  1. 某一个哨兵判断主观下线,会向其他实例发送 is-master-down-by-addr 命令,其他实例会根据自己和主库的连接情况,做出 Y 或 N 的响应,Y 相当于赞成票,N 相当于反对票。阈值是哨兵配置文件中的 quorum 配置项设定的。 (主观下线 -> 客观下线)
  2. 此时,这个哨兵就可以再给其他哨兵发送命令,表明希望由自己来执行主从切换,并让所有其他哨兵进行投票。这个投票过程称为“Leader 选举”。因为最终执行主从切换的哨兵称为 Leader,投票过程就是确定 Leader。

每个哨兵节点只能投票一次,包括投给自己的也算
在投票过程中,任何一个想成为 Leader 的哨兵,要满足两个条件:第一,拿到半数以上的赞成票;第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。以 3 个哨兵为例,假设此时的 quorum 设置为 2,那么,任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以了。

如果某轮投票不会产生 Leader。哨兵集群会等待一段时间(也就是哨兵故障转移超时时间的 2 倍),再重新选举。这是因为,哨兵集群能够进行成功投票,很大程度上依赖于选举命令的正常网络传播。如果网络压力较大或有短时堵塞,就可能导致没有一个哨兵能拿到半数以上的赞成票。所以,等到网络拥塞好转之后,再进行投票选举,成功的概率就会增加。

需要注意的是,如果哨兵集群只有 2 个实例,此时,一个哨兵要想成为 Leader,必须获得 2 票,而不是 1 票。所以,如果有个哨兵挂掉了,那么,此时的集群是无法进行主从库切换的。因此,通常我们至少会配置 3 个哨兵实例。这一点很重要,你在实际应用时可不能忽略了。

集群模式(数据切片模式)

概述

每个Redis服务器启动起来时,都会去检查配置cluster-enabled。如果是yes,自然就成为集群的节点。如果no,那么就作为一个服务器单机。

对于每一个节点服务器用到的数据,Redis 将他们保存到了clusterNode,cliusterLikn,clusterState三个结构中。

Redis 节点与节点之间,通过命令cluster meet ip port 来互相连接成为一个集群。节点与节点之间通信流程如下:

redis查看集群状态命令 redis 查看集群信息_Redis_09

  • 1 节点A会为节点B创建一个clusterNode结构,并将该结构添加到自己的clusterState. nodes字典里面。

  • 2 之后,节点A将根据CLUSTER MEET命令给定的IP地址和端口号,向节点B发送一条MEET消息( message )。

  • 3 如果- -切顺利,节点B将接收到节点A发送的MEET消息,节点B会为节点A创建一个clusterNode结构,并将该结构添加到自己的clusterState. nodes字典里面。

  • 4 之后,节点B将向节点A返回一条PONG消息。

  • 5 如果-切顺利,节点A将接收到节点B返回的PONG消息,通过这条PONG消息节点A可以知道节点B已经成功地接收到了自己发送的MEET消息。

  • 6 之后,节点A将向节点B返回- -条PING消息。

  • 7 如果一切顺利,节点B将接收到节点A返回的PING消息,通过这条PING消息节点B可以知道节点A已经成功地接收到了自己返回的PONG消息,握手完成。

Redis 集群的数据分布之分片

Redis 通过分片的方式来保存数据库中的键值对,集群的整个数据库被分成16384 个槽,每一个键都会属于到某一个槽中,每个节点处理部分槽。

当16384中的任意一个槽没有被使用时,Redis集群都是下线的。

  • 每个节点都会通过一个数组slots来记录自己负责哪些槽(这里就使用了位图的数据结构)

  • 每个节点的clusterState结构还会有一个数组(clusterNode *slots[16384] )来记录所有槽被哪个节点负责。

redis查看集群状态命令 redis 查看集群信息_Redis_10

clusterNode 结构解读

  • 结构clusterLink *link保存了与其他节点TCP连接的所有信息。(比如:socketfd,缓冲区等)

clusterState 结构解读

每个节点保存一个,这个结构主要记录的是从当前节点的角度出发,集群目前所处的状态,集群包含多少个节点等信息。

  • 结构 nodes : 与我这个节点通信的所有节点的clusterNode

redis查看集群状态命令 redis 查看集群信息_Redis_11

在集群中执行重定向

redis查看集群状态命令 redis 查看集群信息_redis_12

  • 计算:CRC16(key) & 16384
  • moved:重定向

redis查看集群状态命令 redis 查看集群信息_数据库_13


如果是完全切换完成,那就返回 MOVED 重定向(更新客户端缓存),否则返回 ASK(不更新客户端缓存)。

ASK 命令表示两层含义:

  • 第一,表明 Slot 数据还在迁移中;
  • 第二,ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后再发送操作命令。。

重新分片

  • 1 redis-trib对目标节点发送CLUSTER SETSLOT IMPORTING <source_id>命令,让目标节点准备好从源节点导人( import)属于槽slot的键值对。

  • 2 redis-trib对源节点发送CLUSTER SETSLOT MIGRATING <target_ id>命令,让源节点准备好将属于槽slot的键值对迁移( migrate)至目标节点。

  • 3 redis-trib向源节点发送CLUSTER GETKEYSINSLOT 命令,获得最多count个属于槽slot的键值对的键名( key name )。

  • 4 对于步骤3获得的每个键名,redis-trib都向源节点发送一个MIGRATE<target_ ip> <target_ port> <key_ name> 0 命令,将被选中的键原子地从源节点迁移至目标节点。

  • 5 重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点为止。每次迁移键的过程如图17-24所示。

  • 6 redis-trib向集群中的任意-一个节点发送CLUSTER SETSLOT NODE<target_ id> 命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽slot已经指派给了目标节点。

redis查看集群状态命令 redis 查看集群信息_redis查看集群状态命令_14


这其中就有ASK错误(迁移过程中,没有在原来的DB中找到时)

redis查看集群状态命令 redis 查看集群信息_redis_15


其实我想不通的是:为啥要有一个 ASKING 标识呐,而且还只能够使用一次??有啥子作用嘛?这玩意儿之后还是会发送到原来的DB上去。

复制与故障转移

// 节点状态
struct clusterNode {
    // 创建节点的时间
    mstime_t ctime; /* Node object creation time. */

    // 节点的名字,由 40 个十六进制字符组成
    // 例如 68eef66df23420a5862208ef5b1a7005b806f2ff
    char name[REDIS_CLUSTER_NAMELEN]; 

    // 节点标识
    // 使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
    // 以及节点目前所处的状态(比如在线或者下线)。
    int flags;     

    // 由这个节点负责处理的槽
    unsigned char slots[REDIS_CLUSTER_SLOTS/8]; 

    // 该节点负责处理的槽数量
    int numslots;  
    // 如果本节点是主节点,
    //那么用这个属性记录从节点的数量
    int numslaves;  

    // 指针数组,指向各个从节点
    struct clusterNode **slaves; 

    // 如果这是一个从节点,那么指向主节点
    struct clusterNode *slaveof; 
 }

故障检测

定期发送消息不用说,检测不到标记 flag 呗。

当-个主节点A通过消息得知主节点B认为主节点C进人了疑似下线状态时,主节点A会在自己的clusterState. nodes字典中找到主节点C所对应的clusterNode结构,并将主节点B的下线报告( failure report) 添加到clusterNode结构的fai1_ reports链表里面:

哎,这个其实就是一个链表,保存了所有其他节点认为我这个节点下线的报告。

故障转移

redis查看集群状态命令 redis 查看集群信息_redis查看集群状态命令_16

慢查询

Redis的慢查询日志功能用于记录执行时间超过给定时长的命令请求,用户可以通过这个功能产生的日志来监视和优化查询速度。

服务器配置有两个和慢查询日志相关的选项:

  • slowlog-log-slower-than :规定时间
  • slowlog-max-len:规定条数
struct redisServer {

    // 保存了所有慢查询日志的链表
    list *slowlog;  保存的结构是slowlogEntry           

    // 下一条慢查询日志的 ID
    long long slowlog_entry_id;     

    // 服务器配置 slowlog-log-slower-than 选项的值
    long long slowlog_log_slower_than; 

    // 服务器配置 slowlog-max-len 选项的值
    unsigned long slowlog_max_len;     
}

redis查看集群状态命令 redis 查看集群信息_redis_17


redis查看集群状态命令 redis 查看集群信息_缓存_18


新的日志会被放在链表头部,打印日志通过遍历来实现。