Redis系列(七)Redis主从、哨兵、cluster集群方案解析

Redis主从

为什么要有主从?

  1. 故障恢复:主挂了或数据丢失了,从库有数据备。
  2. 负载均衡,流量分发。比如主写从读,减小单实例的读写压力。
  3. 高可用,sentinel和cluster都是基于主从实现的。

主从数据同步

一般而言,主数据库负责写,从数据库负责读。

主从数据同步分为两部分:同步和命令传播

同步

slave向master发送psync命令,请求数据同步。psync有两种模式:完整重同步(全量同步)、部分重同步(增量同步)

建立连接
  1. 假设有个节点A,当它第一次成为主节点的一个从节点时,会执行replicaof ip port命令。此时:从节点会保存主节点的IP与端口等信息,主节点也会保存从节点的信息,保证两节点正常连接。
完整重同步(全量同步)
  1. 建立连接后,从节点A向master发送数据同步指令(psync),指令中A向master发送自己保存的master_replid、offset等字段。

  2. master收到数据同步指令后,比较其中的master_repid和自己的repid是不是一样。如果不一样或者传的空值,那么就需要全量同步。

    • 备注:4.0之前,用的是runId,但是runId每次实例重启时都会改变,会导致主从切换;或者重启的时候,都需要全量同步,所以4.0之后采用
      replid
  3. slave首次连接master,slave肯定是没有master的replid,触发全量同步。

  4. 全量同步过程:

    1. master开始执行bgsave,生成一个rdb文件,并且把rdb文件传输给我们的slave,同时把master的replid以及offerset(master的数据偏移量,处理完命令后,都会写入自身的offerset)。

    2. slave接收到rdb文件后,清空slave自己内存中的数据,然后用rdb来加载数据,这样保证了slave拿到的数据是master生成rdb时候的最新数据。

    3. 由于master生成rdb文件是用的bgsave生成,所以,在生成文件的时候,master是可以接收新的指令的。这些新的指令需要找一个地方保存,等到slave加载完rdb文件以后要同步给slave。

      • 在master生成rdb文件期间,接收新的指令会保存在一个内存区间,这个内存区间就是replication_buffer(缓冲区)。我们可以通过以下方式设置replication_buffer的大小。
      client-output-buffer-limit replica 256mb 64mb 60
      //256mb:硬性限制,如果接收的新指令超过256M,master断开和slave的连接,再次连接得重新全量同步。
      //64mb 60:软限制,如果接收的新指令超过64M并且持续时间超过了60s还没进行同步(slave没加载完rdb文件),则master断开和slave的连接。
      
      • 所以replication_buffer空间不能太小,如果太小,为了数据安全,会关闭跟从库的网络连接。再次连接得重新全量同步,但是问题还在,会导致无限的同步的问题。
    4. 等到slave加载完rdb文件后,master把replication_buffer中的数据、对应的offset同步给slave。slave执行完这些写命令后,此时的数据库状态便和master一致了。

触发条件

1. slave第一次接入master

2. slave在增量同步时,master的积压缓存里没有需要同步的数据

部分重同步(增量同步)

完整重同步(全量同步)会消耗master大量的CPU、内存和磁盘I/O资源。如果slave跟master网络只断开一小会,期间master增加的数据并不多。slave再跟master重连后,如果还要执行完整重同步就很不划算了。所以redis提供了部分重同步(增量同步)。

部分重同步涉及如下几个概念

  • 复制偏移量(offset)
  • 复制积压缓冲区(replication_backlog_buffer)
复制偏移量(offset)

主从服务器都会分别维护各自的复制偏移量。

  • 主服务器每次向从服务器传播 n 个字节数据时,都会将自己的复制偏移量加 n。
  • 从服务器接受主服务器传来的数据时,也会将自己的复制偏移量加 n。

举个例子:

若当前主服务器的复制偏移量为 10000,此时向从服务器传播 30 个字节数据,结束后复制偏移量为 10030。

此时从服务器的复制偏移量为 10000,如果从服务器接收到这 30 个字节数据,结束后复制偏移量为 10030;如果从服务器还没接收这 30 个字节数据就断线了,然后重新连接上之后,该从服务器的复制偏移量是10000,此时主从数据不一致,从服务器会向主服务器发送 psync 命令。

复制积压缓冲区(replication_backlog_buffer)
  • master有一个复制积压缓冲区(replication_backlog_buffer),该缓冲区有且只有一个,所有slave共享此缓冲区,其作用在于备份最近主库发送给从库的数据。
  • 复制积压缓冲区默认大小 1MB。由下列参数配置
# repl-backlog-size 1mb		//积压缓存默认大小为1M,如果超过,数据覆盖
  • 复制积压缓冲区是一个固定长度,先进先出的队列。当主服务器进行命令传播时,不仅会将命令发送给从服务器,还会发送一份到复制积压缓冲区,作为写命令的备份。复制积压缓冲区的构造如下:

在这里插入图片描述

  • 由上面的构造图可知,除了存储最近的写命令,复制积压缓冲区中还存储了每个字节相应的复制偏移量,由于复制积压缓冲区是固定大小先进先出的队列,所以它总是保存的是最近redis执行的命令,早期的命令会逐步删除。

  • 当从服务器向主服务器发送 psync 命令时,会将自己的复制偏移量带上,主服务器就可以对从服务器传来的复制偏移量和自己复制积压缓冲区的偏移量做对比:

    • 若复制积压缓冲区存在从服务器的复制偏移量 + 1 后的数据,则进行部分重同步,否则进行完整重同步
部分重同步执行过程

执行过程如下:

  1. slave向master发送数据同步指令(psync),向master传输slave的master_repid、offset等信息。
  2. master收到数据同步指令后,如果发现master_repid != 自己的repid,则执行完整重同步(全量同步);如果发现master_repid == 自己的repid,则master会去找到你这个slave还有哪些数据没有同步。
  3. 通过slave传过来的offset和自己在复制积压缓冲区的数据的offset作比较,master就能够知道它有哪些数据没有同步。
    • 复制积压缓冲区存在从服务器的offset + 1 后的数据,就执行增量同步操作。
    • 否则,执行全量同步操作。

命令传播(指令同步)

经过上述的同步操作,此时主从的数据库状态其实已经一致了,但这种一致的状态的并不是一成不变的。

主服务器还会接受新的写命令,执行完新的写命令后,主从的数据库状态又不一致。

为了再次让主从数据库状态一致,主服务器就需要向从服务器执行命令传播操作 ,即把刚才造成不一致的写命令,发送给从服务器去执行。从服务器执行完成之后,主从数据库状态就又恢复一致了

心跳检测

主从同步有同步和命令传播 2 个步骤。

当完成了同步之后,主从服务器就会进入命令传播阶段,此时从服务器会以每秒 1 次的频率,向主服务器发送命令:REPLCONF ACK <replication_offset> 其中 replication_offset 是从服务器当前的复制偏移量

发送这个命令主要有三个作用:

  • 检测主从服务器的网络状态
  • 辅助实现 min-slaves 选项
  • 检测命令丢失(若丢失,主服务器会将丢失的写命令重新发给从服务器)

Redis sentinel哨兵

概述

虽然Redis主从解决了比如负载、数据备份等问题。但是,我们发现如果master挂了,slave不会直接升级为master,必须手动把slave升级为master才能恢复主从网络。我们不希望master挂了后,还需要人半夜来解决这件事情,来启动slave容灾。所以就有了我们的哨兵集群,以及我们后面的cluster集群。

哨兵(Redis sentinel)为Redis提供了高可用性。并且提供了监测、通知、自动故障转移、配置提供等功能。

  • 监控:能够监控我的Redis各实例是否正常工作。
  • 通知:如果Redis的实例出现问题, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障转移:当master宕机,可以把其中一个slave自动升级为master。
  • 配置提供:sentinel可以提供Redis的master实例地址,那么客户端只需要跟sentinel进行连接,master挂了后会提供新的master。如下图

在这里插入图片描述

哨兵故障转移流程

发现master故障

  1. 每个Sentinel节点会每隔1秒对主节点、从节点、其他Sentinel节点发送ping命令做心跳检测。当这些节点超过down-after-milliseconds没有进行有效回复,Sentinel节点就会对该节点做失败判定,这个时候不会触发故障转移,只会标记一个状态,这个状态就是SDOWN(Subjectively Downcondition),也就是主观下线。
  2. 当Sentinel主观下线的节点是master节点时,该Sentinel节点会通过sentinel is-master-down-by-addr命令向其他Sentinel节点询问对主节点的判断,当超过Quorum(法定人数)的sentinel都认为master不可用,都标记SDOWN状态,这个时候,master可能就
    真的是down了。那么就会将master标为ODOWN(Objectively Downcondition 客观下线)。

Sentinel节点领导者选举

  1. 当master状态是ODOWN的时候,我们就需要去触发故障转移,但是有这么多的sentinel,我们需要选一个sentinel节点作为领导者去做故障转移这件事情,并且这个sentinel在做故障转移的时候,其他sentinel不能进行故障转移。
  2. 选举过程有2个因素:
    • Quorum(法定人数)如果小于等于Sentinel节点数的一半,那么必须超过半数的sentinel授权,你才能去做故障迁移,比如5台 sentinel,你配置的Quorum=2,那么选举的时候必须有3(5台的一半以上)节点同意。
    • Quorum如果大于一半,那么必须Quorum个数的sentinel授权,故障迁移才能启动。

故障转移

  1. 当领导者Sentinel节点被选出,就执行故障转移,选择1个slave升级成master。选择条件如下:
    • 过滤:“不健康”(主观下线、断线)、5秒内没有回复过Sentinel节点ping响应、与主节点失联超过超时时间(down-after-milliseconds)* 10秒的节点。
    • 优先级:选择replica-priority最小的从节点列表,如果存在则返回,不存在则继续。replica-priority越小表示优先级越高,但是配置为0的时候,永远没有资格升为master。
    • 已复制数据的偏移量:比较slave的赋值的数据偏移量,数据最新的优先升级为master,如果存在则返回,不存在则继续。
    • Run ID (每个实例启动都会有个Run ID ):选择runid最小的从节点。
  2. Sentinel领导者节点会对上一步选出来的从节点执行slaveof no one命令,让其成为master节点。
  3. Sentinel领导者节点会向剩余的从节点发送命令,让它们成为新主节点的从节点。
  4. Sentinel节点集合会将原来的主节点更新为从节点,并保持着对其关注,当其恢复后命令它去复制新的主节点。

哨兵导致的数据一致性问题解析

官方建议配置至少3个sentinel节点。原因:

  1. 如果只有1个sentinel实例,则这个实例挂了就不能保证sentinel的高可用性
  2. 如果有2个sentinel实例,容易产生脑裂问题。

脑裂问题

在这里插入图片描述

如图,有个1主1从2哨兵的网络,如果配置quorum=1。在sentinel2失去master的感知的时候,

  1. 因为127跟128网络断开,这个时候会触发主观下线,同时,sentinel2只能连接到1个sentinel,也满足半数以上原则(只有1个sentinel2)。 所以sentinel2可以通过领导者选举,做故障转移。
  2. sentinel2会将128的slave升级为master,此时就出现了2个master,这个情况叫作脑裂。并且客户端会连接到2个master。
  3. 此时2个master都会写入数据,当网络恢复后,127会变成slave,127的数据会从128去拿取,这个时候127的数据就会丢失.

脑裂问题其实就是集群中产生2个master,导致client会从不同的master写数据,从而在master恢复的时候会导致数据丢失。

不管多少个节点,都可能会出现脑裂问题

解决方案

redis.conf 修改属性,通过活跃slave节点数和数据同步延迟时间来限制master节点的写入操作。

# master 至少有 x 个副本连接。
min-slaves-to-write x
# 主从之间的数据复制和同步的延迟不能超过 x 秒。
min-slaves-max-lag x

为什么redis推荐奇数个节点

其主要原因还是从成本上考虑的,因为奇数个节点和偶数个节点允许宕机的节点数是一样的,比如3个节点和4个节点都只允许宕机一台。

Redis Cluster

sentinel提供了比如监控、自动故障转移、客户端配置等高可用的方案,但是没有分片功能。
所谓的分片:就是我希望把数据分布到不同的节点。这样如果某些节点异常,其他数据能正常提供服务,跟我们微服务的思想很相似。

Redis Cluster就提供了这样的功能。

  1. 多个节点之间的数据拆分,也就是数据分片功能。
  2. 当某些节点遇到故障的时候,其他的节点还能继续服务。

hash slot(虚拟槽)

分片要做的事情就是要把不同的数据放到不同的实例里面去。就相当于我们分表,不同的数据放到不同的表里。 那么如何进行分片?

普通取模问题

参考hashMap,我们有个方法:

计算key的hash值,然后节点个数取模,得到存入的节点下标。比如节点个数是3,那么取模后得到0-2的值,每个值代表一个节点。但是这种取模方法有一个问题,假如我做了实例的扩容与缩容,那么全部数据要进行迁移。比如我之前是3台,扩容到4台,那么所有的数据都必须重新rehash。

所以,Redis里面提出了一个Hash槽,也叫作虚拟槽的概念。什么是虚拟槽,其实就是虚拟节点。

虚拟槽

Redis cluster中有16384个虚拟槽(2^14)。

  • 根据key通过CRC16 取模 16383 得到一个0到16383的值,计算公式:slot = CRC16(key) & 16383。得到的值,就代表这个key在哪个虚拟槽。
  • 虚拟槽会跟我们的真实节点进行对应,这个关系是可以变更的。可以设置某个实例节点包含哪些虚拟槽,但是至少得有3个主实例节点,主节点可以有自己的从节点。

假如,我们现在有6台实例,三主三从。 主跟槽的对应关系可以如下

master1 0-5460虚拟槽
master2 5461-10922虚拟槽
master3 10923-16383虚拟槽
127.0.0.1:6380> cluster nodes //查看当前节点的虚拟槽信息的指令
  • 由此可见,每次节点的扩容与缩容,只需要改变节点跟虚拟槽的关系即可,数据迁移也只迁移一部分数据。

比如上述例子要增加一个节点,主跟槽的对应关系可以做如下变更。那只需要迁移5461-6460虚拟槽中数据即可。

master1 0-5460虚拟槽
master4 5461-6460虚拟槽
master2 6461-10922虚拟槽
master3 10922-16383虚拟槽

添加节点(集群扩容)

  1. 启动集群

  2. 启动需要添加的节点,这里要添加的节点是:127.0.0.1:6436

./redis-server ../redis.conf
  1. 将节点添加进集群
./redis-cli --cluster add-node 127.0.0.1:6436 127.0.0.1:6435

查看node节点信息,从最后一行可以看到,新节点已经加进集群

127.0.0.1:6430> cluster nodes
082f87e4bdebbc424a9a996048e085c4372ab515 127.0.0.1:6431@16431 slave 63dc100e63a583c8dcb77161c7c4c9ae4f866b7e 0 1667488539000 8 connected
a0bf7101c4d885532fc76946a4e13c0e5c8043d7 127.0.0.1:6430@16430 myself,slave 999e5ed482bbfffde7e9508e832da7f51200a583 0 1667488540000 7 connected
63dc100e63a583c8dcb77161c7c4c9ae4f866b7e 127.0.0.1:6434@16434 master - 0 1667488540000 8 connected 5461-10922
999e5ed482bbfffde7e9508e832da7f51200a583 127.0.0.1:6433@16433 master - 0 1667488541138 7 connected 0-5460
9250d1d78c76e405262e4791936402e3e9d868bc 127.0.0.1:6435@16435 slave bc9c6cb3bd9367724977e72727eb67d587905af8 0 1667488540135 3 connected
bc9c6cb3bd9367724977e72727eb67d587905af8 127.0.0.1:6432@16432 master - 0 1667488539135 3 connected 10923-16383
b49a91a9bf80fcfdfe96b8cb2d1a56b4f752e633 127.0.0.1:6436@16436 master - 0 1667488538132 0 connected

数据迁移

  1. 分配slot,做数据迁移
redis-cli --cluster reshard 127.0.0.1:6436

该命令需要交互实现,也可以使用脚本命令。

交互内容:

  • How many slots do you want to move (from 1 to 16384)?(需要迁移多少个槽)

  • What is the receiving node ID?(接受 slot 槽的节点ID)

  • Please enter all the source node IDs.
    Type ‘all’ to use all the nodes as source nodes for the hash slots.
    Type ‘done’ once you entered all the source nodes IDs.(哪些节点需要导出,all是自动分配,done是手动分配,扩容用all即可)

  • Do you want to proceed with the proposed reshard plan (yes/no)?(确认信息,选yes)

脚本命令:

redis-cli --cluster reshard 127.0.0.1:6436 --cluster-from bc9c6cb3bd9367724977e72727eb67d587905af8,63dc100e63a583c8dcb77161c7c4c9ae4f866b7e,999e5ed482bbfffde7e9508e832da7f51200a583 --cluster-to b49a91a9bf80fcfdfe96b8cb2d1a56b4f752e633 --cluster-slots 4000 --cluster-yes

至此,添加master节点完成。

  1. 添加slave节点
redis-cli --cluster add-node 127.0.0.1:6437 127.0.0.1:6436 --cluster-slave --cluster-master-id b49a91a9bf80fcfdfe96b8cb2d1a56b4f752e633

说明:

  • add-node: 后面的分别跟着新加入的 slave 和 slave 对应的 master
  • –cluster-slave:表示加入的是 slave 节点
  • –cluster-master-id:表示 slave 对应的 master 的 node ID

删除节点(集群缩容)

假设要删除127.0.0.1:6436节点:

  1. 如果删除是主节点,必须先对其做数据迁移,让主节点为空。所以这里先对127.0.0.1:6436节点做数据迁移
redis-cli --cluster reshard 127.0.0.1:6432

#redis-cli --cluster reshard <数据迁移的目标节点的ip:port>

交互内容:

  • How many slots do you want to move (from 1 to 16384)?

    • 写入127.0.0.1:6436包含的slot数量
  • What is the receiving node ID?

    • 选择127.0.0.1:6432的节点ID
  • Please enter all the source node IDs.
    Type ‘all’ to use all the nodes as source nodes for the hash slots.
    Type ‘done’ once you entered all the source nodes IDs.

    • 先写入导出的节点的Id,这里是127.0.0.1:6436
    • 然后写入done
  • Do you want to proceed with the proposed reshard plan (yes/no)?

    • yes
  1. 执行节点删除
redis-cli --cluster del-node 127.0.0.1:6436 b49a91a9bf80fcfdfe96b8cb2d1a56b4f752e633

#redis-cli --cluster del-node <待删除节点的ip:port> <待删除节点的id>

参考资料

  • Redis命令

https://zhuanlan.zhihu.com/p/359563484

https://www.processon.com/view/link/62cd1c400e3e74070446a5b2

  • redis集群

https://blog.csdn.net/hebtu666/article/details/114827837?ops_request_misc=%7B%22request%5Fid%22%3A%22166731098216782425189807%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fblog.%22%7D&request_id=166731098216782425189807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-4-114827837-null-null.nonecase&utm_term=redis&spm=1018.2226.3001.4450)

  • redis哨兵

https://zhuanlan.zhihu.com/p/426497450

  • 脑裂问题

https://www.cnblogs.com/dcdcyyy/p/14275124.html

  • 为什么redis推荐奇数个节点

https://blog.csdn.net/qq32933432/article/details/105785571

  • 主从同步

https://zhuanlan.zhihu.com/p/55532249

  • 流程图

https://www.processon.com/view/link/62cd1c400e3e74070446a5b2

  • redis cluster扩容和缩容

https://blog.csdn.net/lzb348110175/article/details/122168638

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值