无风无影

   ::  :: 新随笔  ::  ::  :: 管理

  在生产环境中,为了保证 Redis 服务的高可用,我们往往要使用 Redis 的集群模式,Redis 的集群模式有三种:主从同步集群模式、哨兵集群模式、Cluster 集群模式,本篇文章先介绍 Redis 主从同步集群模式的原理及实现。

主从分离

  这种模式的应用场景有:Slave 作为 Master 节点的数据备份;数据读写分离;多个从服务器根据业务拆分;

  Redis 主从同步的原理

   Redis 主从同步分为两个过程:全量同步和增量同步

  全量同步

  全量同步一般发生在 Slave 的初始化阶段,也就是 Slave 节点的启动阶段。具体的步骤如下(最好对照着刚刚启动的日志来看):

  • 从服务器连接主服务器(Log: Connecting to MASTER 192.168.238.1:6379)
  • 从服务器发送 SYNC 命令到主服务器(Log: MASTER <-> SLAVE sync started)
  • 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令(Log: Starting BGSAVE for SYNC with target: disk)
  • 主服务器 BGSAVE 执行完后,递增地将文件发送到从服务器,并在发送期间继续记录被执行的写命令(Log: Synchronization with slave 192.168.238.129:6379 succeeded)
  • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照(Log: MASTER <-> SLAVE sync: Flushing old data)
  • 从服务器将快照载入内存(Log: MASTER <-> SLAVE sync: Loading DB in memory)
  • 主服务器快照发送完毕后开始向从服务器发送缓冲区的写命令
  • 从服务器完成对快照的载入,开始接收主服务器发送的写命令请求,并执行写命令。
  • 至此,一次全量同步完成

  增量同步

  增量同步是指 Redis 在主从模式已经正常工作的情况下,主服务器将写操作同步到从服务器的过程
  增量复制的原理当主服务器每执行一个写命令,就会将该命令发送给所有的从服务器,从服务器接收到命令之后立即执行。

                                                                        

 

  图上能看得到的信息:

  1, 只有1个Master,可以有N个slaver,而且Slaver也可以有自己的Slaver,由于这种主从的关系决定他们是在配置阶段就要指定他们的上下级关系,而不是Zookeeper那种平行关系是自主推优出来的。

  2, 读写分离,Master只负责写和同步数据给Slaver,Slaver承担了被读的任务,所以Slaver的扩容只能提高读效率不能提高写效率。

  3, Slaver先将Master那边获取到的信息压入磁盘,再load进内存,client端是从内存中读取信息的,所以Redis是内存数据库。

  当一个新的Slaver加入到这个集群时,会主动找Master来拜码头,Master发现新的小弟后将全量数据发送给新的Slaver,数据量越大性能消耗也就越大,所以尽量避免在运行时做Slaver的扩容。

  Redis 主从同步的优缺点

  优点

  1. 同一个 Master 可以部署多个 Slave
  2. Slave 还可以接受其他的 Slave 的连接和同步,即所谓的 级联结构。有效的减轻 Master 的压力
  3. 主从同步期间,主从节点均是非阻塞。不影响服务的查询和写入
  4. 可以很好的实现读写分离的架构,系统的伸缩性得到提高

  缺点

  1. 主机的宕机会非常严重,导致整个数据不一致的问题。
  2. 全量的复制的过程中,必须保证主节点必须有足够的内存。若快照的文件过大,还会对集群的服务能力产生影响。

 

Redis 哨兵集群模式

   什么是哨兵模式(Redis Sentinel)?当采用 Master-Slave 的高可用方案时候,如果 Master 宕机之后,想自动切换,可以考虑使用哨兵模式。哨兵模式其实是在主从模式的基础上工作的。

哨兵(Sentinel)模式下会启动多个哨兵进程,哨兵进程的作用如下:

监控:能持续的监控 Redis 集群中主从节点的工作状态
通知:当被监控的节点出现问题之后,能通过 API 来通知系统管理员或其他程序
自动故障处理:如果发现主节点无法正常工作,哨兵进程将启动故障恢复机制把一个从节点提升为主节点,其他的从节点将会重新配置到新的主节点,
并且应用程序会得到一个更换新地址的通知

                                                            

 

 

 

 

  1. 每个哨兵进程会以每秒钟一次的频率向整个集群中的 Master 结点Slave 节点Sentinel 进程发送一个 PING 命令
  2. 如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值,则这个实例就会被哨兵进程标记为主观下线
  3. 如果一个 Master 节点被标记为 主观下线,则所有监视这个 Master 节点的哨兵进程都需要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态
  4. 当有足够数量的哨兵进程在指定时间范围内确认了 Master 主节点进入了主观下线状态,则 Master 节点就会被标记为客观下线
  5. 每个哨兵进程会以每 10 秒一次的频率向集群中的所有 Master/Slave 机器发送 INFO 命令,并从回复信息中提取从节点ID、从节点角色、从节点所属的主节点的ip及端口、主从节点的连接状态、从节点的优先级、从节点的复制偏移量等信息;
  6. 若没有足够数量的哨兵进程同意 Master 主节点下线,Master 主节点的客观下线状态就会被移除。若 Master 主节点重新向哨兵进程发送 PING 返回有效的回复, Master 主节点的主观下线状态就会被移除

  主观下线

  所谓主观下线就是单个的哨兵进程认为某个服务下线,带有主观意识。

  标记为主观下线,是根据发送的 PING 命令是否有效回复来判断,当然主观下线的时间长度可以设置,down-after-milliseconds 毫秒内返回的都是无效回复,则标记为主观下线。

  客观下线

  所谓的客观下线,就是当一个哨兵进程标记某个服务为主观下线时,哨兵需要询问其他的哨兵进程是否也认为该服务为主观下线,接受到足够数量的主观下线的时候,那么该服务就被认为是客观下线。然后开始对服务进行故障转移工作。

  领头哨兵的选举

  如果一个 Redis 节点被标记为客观下线,那么所有监控改服务的哨兵进程会进行协商,选举出一个领头的哨兵,对 Redis 服务进行转移故障操作。领头哨兵的选举大概遵循以下原则:

  • 所有的哨兵都有公平的机会被选举为领头哨兵
  • 在一轮选举中,所有的哨兵都有且仅有一次机会被选举称领头哨兵,一旦选举,不可更改
  • 如果某个哨兵被半数以上的哨兵设置为领头,那么该哨兵称为领头哨兵
  • 如果在限定时间内没选举出来,那么暂停一段时间,再次选举

 

  哨兵模式优缺点

  优点

  1. 哨兵模式本身就是基于主从模式,所以具有主从同步模式的优点
  2. 主从可以切换,故障转移,提高系统的可用性
  3. 系统更加健壮稳定

  缺点

  当 Redis 集群容量达到一定程度时,不能很好的支持在线扩容,所以在使用前必须确保有足够的空间。

  总结

  可以说,哨兵模式是对主从同步模式的一个补充,使得 Redis 集群更加的稳健,可用性更高。但是该种模式下的 Redis 不能水平扩容,不能随时增加或删除结点,这也限制了哨兵模式的广泛使用。在 Redis3.0
之后的版本提供了更加强大的集群模式,Cluster 集群模式,下一篇文章我们再详细讨论。

 

 

 

Redis Cluster集群模式

   该模式也是 Redis3.x 之后才引入的,在该模式下解决了主从同步和 哨兵模式 的不能水平扩容的问题,使得 Redis 集群的性能得到提高,也由此成为了Redis 官方推荐使用的集群方案。

  首先我们需要了解 Redis Cluster 的设计目标:

  1. 高性能:高性能是 Redis 赖以生存的看家本领,增加集群之后不能对性能产生太大影响,否则会得不尝失。
  2. 水平扩展:之前 Redis 集群不能水平扩展的缺点时常被人诟病,所以必须具备水平扩展。
  3. 高可用:在之前,高可用主要是通过 Redis Sentinel 来保障,Cluster 集群也应该具备 Sentinel 的监控、提醒、自动故障转移等功能。

  数据分区方案

  普通哈希分区

  普通的哈希分区比较简单,就是根据规定的哈希函数将数据哈希到指定的节点上,例如:现在有 3 个 Redis 结点 node1、node2、node3。我们的哈希函数采用取余法哈希

function hash(key) {
    return key % 3
}

  当写数据的时候,根据哈希函数写到对应的节点中,读数据的时候先计算出数据在哪个节点,然后再去对应的节点去取。我们发现当节点数固定的时候,该种数据分区的方案没有问题,当增加一个节点或删除一个节点的时候。
   取余哈希函数的分母会改变,导致之前已分配的数据分区大量改变,并且造成大量的数据获取不到。所以该种方案很少使用。

  一致性哈希分区

  详见本文

 哈希Slot这个艺名看起来很文艺,但也不是什么新技术,他的真名就叫分表分库,再上一个图:

  Cluster 采用的正是这种分区的方式。虚拟槽分区巧妙的使用了哈希空间,使用分散度良好的哈希函数把
所有的数据映射到一个固定范围内的整数集合,整数定义为槽(slot),Redis Cluster 的槽范围是 0
~ 16383,槽是集群内的数据管理和迁移的基本单位。每个节点负责一定数量的槽。计算公式:

CRC16(key)&16383

   每一个节点负责维护一部分槽及槽所映射的键值数据,如下图所示(图片来源于网络):

  采用 哈希虚拟槽分区 的特性:

  • 解耦了数据和节点之间的关系,简化了节点的扩容和收缩的难度
  • 节点自身来维护槽的映射关系,不需要客户端或者代理来维护槽分区的元数据
  • 至此节点、槽、键之间的映射查询

 

  图上能看到的信息:

  1、对象保存到Redis之前先经过CRC16哈希到一个指定的Node上,例如Object4最终Hash到了Node1上。

  2、 每个Node被平均分配了一个Slot段,对应着0-16384,Slot不能重复也不能缺失,否则会导致对象重复存储或无法存储。

  3、 Node之间也互相监听,一旦有Node退出或者加入,会按照Slot为单位做数据的迁移。例如Node1如果掉线了,0-5640这些Slot将会平均分摊到Node2和Node3上,由于Node2和Node3本身维护的Slot还会在自己身上不会被重新分配,所以迁移过程中不会影响到5641-16384Slot段的使用。

  简单总结下哈希Slot的优缺点:

  缺点:每个Node承担着互相监听、高并发数据写入、高并发数据读出,工作任务繁重

  优点:将Redis的写操作分摊到了多个节点上,提高写的并发能力,扩容简单。

 节点增加和删除

  • 增加节点
    当增加一个节点 Node-6 时,只需要把其他节点的某些哈希槽挪到新的节点就可以了。

  • 移除节点
    移除一个节点 Node-5 时,只需要把该节点上的哈希槽挪到其他的节点上就可以了。

在增加和删除节点,redis 的其他节点都不需要停机。

  下图展现一个 5 个节点构成的集群,每个节点平均大约负责 3276 个槽,以及通过计算公式映射到对应节点的对应槽的过程。

                          

 

   为什么不用一致性哈希,而用槽哈希分区,原因是什么?Redis 使用的是 crc16 的简单算法,Redis 的作者认为 crc16(key) mod 16384 的效果已经不错了,虽然可能没有一致性哈希灵活,但实现比较简单,节点的增加和删除都比较方便

  节点增加和删除的过程中,数据会不会丢失?节点在数据迁移的时候数据会有备份,不会丢失

 

 

  双剑合并:

  看到这里大家也就发现了,主从和哈希的设计优缺点正好是相互弥补的,将图一每一套主从对应到图二中的每一个Node,就是Redis集群的终极形态,先Hash分逻辑节点,然后每个逻辑节点内部是主从,如图:

 

  想扩展并发读就添加Slaver,想扩展并发写就添加Master,想扩容也就是添加Master,任何一个Slaver或者几个Master挂了都不会是灾难性的故障。

面试问题:为什么RedisCluster会设计成16384个槽呢?

  1.如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。

  如上所述,在消息头中,最占空间的是 myslots[CLUSTER_SLOTS/8]。 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。

  2.redis的集群主节点数量基本不可能超过1000个。

  如上所述,集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。

  3.槽位越小,节点少的情况下,压缩率高

  Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。 如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。而16384÷8÷1024=2kb,怎么样,神奇不!

 

 

 

参考链接:https://phachon.com/redis/redis-1.html

posted on 2018-06-18 18:14  NWNS-无风无影  阅读(231)  评论(0编辑  收藏  举报