写点什么

主从哨兵集群终于给你说明白了

用户头像
moon聊技术
关注
发布于: 2021 年 01 月 04 日
主从哨兵集群终于给你说明白了

前言碎语


说起 Redis 应该没有人会陌生了吧,作为开发中最最最最最最最常用的 nosql,它的重要性不言而喻。


Redis 有三种集群模式,第一个就是主从模式,第二种“哨兵”模式,第三种是 Cluster 集群模式。


今天就和大家细细聊聊这三种模式。


主从复制


截屏 2021-01-03 下午 1.26.51.png

当其中一台服务器更新之后,服务器会自动的将这台更新的数据同步到另外一台服务器上。


通过持久化的功能,redis 可以保证就算是服务宕机重启了,也只有少量的数据会丢失。但是在真实的使用场景当中,如果真的只有一台服务器,并且恰好宕机了,那么就会导致整个服务都不可用,因此 redis 提供了集群的方式来部署,可以避免这种问题。


在主从复制这种集群部署模式中,我们会将数据库分为两类,第一种称为主数据库(master),另一种称为从数据库(slave)。


数据库会负责我们整个系统中的读写操作,数据库会负责我们整个数据库中的操作。


其中在职场开发中的真实情况是,我们会让主数据库只负责写操作,让从数据库只负责读操作,就是为了读写分离减轻服务器的压力


但是我在实际开发中会遇到一种情况,该数据是个热点数据,我们知道,数据同步一定是会耗时的,那么当一个热点数据进入 master 中而 slave 没有来得及更新,再去读这个数据就会造成数据不一致现象,所以当时我的方案就是直接去读 master 节点,这个逻辑同样适用于 mysql 主从中出现的问题。


主从同步原理


  • 当一个从数据库启动时,它会向主数据库发送一个 SYNC 命令

  • master 收到后,在后台保存快照,也就是我们说的 RDB 持久化,当然保存快照是需要消耗时间的,并且 redis 是单线程的(redis 后面也支持了多线程,这里我们先不讲),在保存快照期间 redis 收到的命令会缓存起来,快照完成后会将缓存的命令以及快照一起打包发给 slave 节点,从而保证主从数据库的一致性。

  • 从数据库接受到快照以及缓存的命令后会将这部分数据写入到硬盘上的临时文件当中,写入完成后会用这份文件去替换掉 RDB 快照文件,当然,这个操作是不会阻塞的,可以继续接收命令执行,具体原因其实就是 fork 了一个子进程,用子进程去完成了这些功能。


因为不会阻塞,所以,这部分初始化完成后,当主数据库执行了改变数据的命令后,会异步的给 slave,这也就是我们说的复制同步阶段,这个阶段会贯穿在整个主从同步的过程中,直到主从同步结束后,复制同步才会终止。


那么我上文提到的数据不一致的现象又是怎么回事呢?


是因为 redis 采用了乐观复制的策略:


容忍一定时间内主从数据库的数据是不一致的,但是会保证最终的结果一致


所以当主从复制发生时,正常情况下的命令都会在主数据库完成,然后直接反回给客户端,这样我们的性能就不会受到影响了,因为这里是主数据库先完成命令,那么就会产生其他问题。


举个例子,假如现在有 1 个 master,6 个 slave,现在只有两个 slave 完成了同步,master 写了新命令,在 master 准备将此命令传输给其他 slave 时,此刻其他的 slave 断电了,那么就会造成数据不一致的现象发生。


所以 redis 针对这种情况作了两个配置


min-slaves-to-write 2 (只有2个及以上的从数据库连接到了主数据库时,master库才是可写的)


min-slaves-max-lag 10 (10秒slave没有和master进行交互就认为丢失链接)


无硬盘复制


我们刚刚说了主从之间是通过 RDB 快照来交互的,虽然看来逻辑很简单,但是还是会存在一些问题:


  • 1.master 禁用了 RDB 快照时,发生了主从同步(复制初始化)操作,也会生成 RDB 快照,但是之后如果 master 发成了重启,就会用 RDB 快照去恢复数据,这份数据可能已经很久了,中间就会丢失数据

  • 2.在这种一主多从的结构中,master 每次和 slave 同步数据都要进行一次快照,从而在硬盘中生成 RDB 文件,会影响性能


为了解决这种问题,redis 在后续的更新中也加入了无硬盘复制功能,也就是说直接通过网络发送给 slave,避免了和硬盘交互,但是也是有 io 消耗的。


增量复制


为什么会有增量复制?


刚刚我们说了复制的原理,但是他的缺点是很明显的,就是在断开主从链接后,即使你只发生了一条数据变化,也需要将所有的数据通过 SYNC 命令用 RDB 将所有的数据同步给 slave,但是其实并不需要同步所有的数据,只需要将改变的这小部分数据同步给 slave 就好了


所以为了解决这个问题,redis 就有了增量复制。


这个原理其实是很简单的,学过 kafka 的小伙伴应该知道,kafka 消费是通过偏移量来计算的,redis 的增量复制也是如此。


master 会记下每个 slave 的 id,在复制期间,如果有新消息,会将新消息(其实是新的命令,当然只包括让数据放生变动的命令,如 set 这种 )存放在一个固定大小的循环队列中,这个大小是可以配置的,当然这时候发送的就是 PSYNC 命令了,然后 master 会在复制完成后将这部分数据发送给 slave,这样就在很大程度上保证了数据一致性。


哨兵模式


上文咱们说主从复制,在这种一主多从的结构中,我们让主从数据库做到了读写分离,也让从数据库能够完成数据备份的功能,可是也留下了一个比较严重的问题,当 master 挂了之后,只能由运维人员重新选择一个 slave 升级成 master,然后继续提供服务


想想一下,你国庆正放假,躺在三亚的海边沐浴着阳光,享受着香槟,突然你们 boss 给你来了个电话,说线上的 master 挂了,是不是会心里一句 mmp(嗯,我这是文明用语)???,所以,redis 为了你考虑,在 redis2.6 版本中,他来了他来了--------哨兵模式


什么是哨兵?


顾名思义,哨兵其实就是放哨的,它主要会有完成两个功能


  • 1.监控整个主数据库和从数据库,观察它们是否正常运行

  • 2.当主数据库发生异常时,自动的将从数据库升级为主数据库,继续保证整个服务的稳定


哨兵其实是一个独立的进程,如下图


截屏 2021-01-03 下午 1.26.55.png

当然,上图只是一个哨兵存在时的情况,但在现实中还会有两个,甚至更多哨兵存在的情况


截屏 2021-01-03 下午 1.29.33.png

实现原理


当一个哨兵进程启动时,它会先通过配置文件,找我们的主数据库,当然,我们这里也只需要配置其监控的主数据库就好,之后哨兵会自动发现所有复制该主数据库的从数据库,当然一个哨兵是可以监控多个 redis 系统的,同时,多个哨兵也可以同时监控一个 redis 系统的,这里 moon 先给大家灌输下这个概念,大家理解下,详细的我会在后文提到。


哨兵进程启动后后会和 master 建立两条链接


  • 1.用来获取其他同样在监控着此 redis 系统的哨兵信息

  • 2.发送一个 info 命令来获取此 redis 系统 master 本身的信息


当和 master 完成链接建立后,该哨兵就会定时的做以下三件事情


  • 1.每 10 秒会向 master 和 slave 发送 info 命令

  • 2.每 2 秒会向 master 和 slave 发送自己的信息

  • 3.每 1 秒会向 master,slave 以及其他同样在监控着此 redis 系统的哨兵发送 ping 命令


以上三个操作可是说是哨兵的核心了,下面就着重介绍一下这三个命令


首先,info 命令可以让哨兵获取到当前数据库的信息,比如运行 id,复制信息等等,从而实现新节点的自动发现,从数据库的信息正是从 info 命令中获取的,获取从数据库信息后,就会和从数据库建立两条链接,和主数据库建立的链接是完全一样的,之后就会每 10s 向主从数据库发送 info 命令,当有新的从数据库加入时,就会从 info 命令中发现了,从而将这个新的 slave 加入自己的监控列表中。


当然如果有新的哨兵加入到了监控中,其他哨兵也是从这个 info 命令中获取的。


于此,就完成了对数据库以及其他哨兵的自动发现和监控,是不是很 easy 呢??


以上讲了自动发现数据库和其他的哨兵节点,之后哨兵就开始了它的工作,就是去监控这些数据库和节点有没有停止,哨兵就会每隔一段时间向这些节点发送 PING 命令,如果一段时间没有收到回复后,那么这个哨兵就会认为该节点已经挂了,我们将其称为主观下线


如果该节点是 master,哨兵就会向其他节点询问,看其他节点时候也认为该 master 挂了,我们可以认为他们在投票,当票数达到了一定的次数,那么哨兵就认为该节点真的挂了,我们成为客观下线,然后哨兵之间就会选举,选出一个领头的哨兵对主从数据库发起故障的修复。


哨兵选举过程


  • 1.第一个发现该 master 挂了的哨兵,向每个哨兵发送命令,让对方选举自己成为领头哨兵

  • 2.其他哨兵如果没有选举过他人,就会将这一票投给第一个发现该 master 挂了的哨兵

  • 3.第一个发现该 master 挂了的哨兵如果发现由超过一半哨兵投给自己,并且其数量也超过了设定的 quoram 参数,那么该哨兵就成了领头哨兵

  • 4.如果多个哨兵同时参与这个选举,那么就会重复该过程,知道选出一个领头哨兵


选出领头哨兵后,就开始了故障修复,会从选出一个从数据库作为新的 master


master 选举过程


  • 1.从所有在线的从数据库中,选择优先级最高的从数据库

  • 2.如果有多个优先级高的从数据库,那么就会判断其偏移量,选择偏移量最小的从数据库,这里的偏移量就是增量复制的

  • 3.如果还是有相同条件的从数据库,就会选择运行 id 较小的从数据库升级为 master


cluster 集群模式


在 redis3.0 版本中支持了 cluster 集群部署的方式,这种集群部署的方式能自动将数据进行分片,每个 master 上放一部分数据,提供了内置的高可用服务,即使某个 master 挂了,服务还可以正常地提供,我们先来看张图:


截屏 2021-01-03 下午 1.45.02.png

使用 cluster 集群模式,只需要将每个数据库节点的 cluster-enabled 配置选项打开即可,但是每个 cluster 集群至少要保证有 3 个主数据库才能正常运行。


cluster 集群模式是怎么存放数据的?


一个 cluster 集群中总共有 16384 个节点,集群会将这 16384 个节点平均分配给每个节点,当然,我这里的节点指的是每个主节点,就如同下图:


image

键是如何和 16384 个插槽做关联的?


redis 将每个 redis 的键的键名有效部分使用 CRC16 算法计算出散列值,然后与 16384 取余数,这样的就可以使每个键能够尽量的均匀分布在 16384 个插槽中。


插槽是如何和节点做关联的?


  • 1.插槽之前没有被分配过,现在想分配给指定节点

  • 2.插槽之前被分配过,现在想移动指定节点


第一种情况可以通过 cluster add slot s 命令来实现


第二种情况的原理相对麻烦一点,但是 redis 也提供的便捷的方式去操作,我们可以使用 redis-trib.rb 去实现


如何获取与插槽对应的节点?


当客户端向 redis 集群中的任意一个节点发送命令后,该节点都会判断当前键的信息是否存在于当前节点:


  • 如果存在,那么就会像单机的 reids 一样执行命令。

  • 如果不存在,就会返回一个 move 重定向请求,告诉客户端负责该数据的节点是哪一个,然后客户端会向该节点发送命令再次请求获取数据


新节点的加入


需要通过 cluster meet 命令来实现:


cluster meet ip port


ip port 是我们已运行的 redis 集群中任意一个节点的地址和端口号,新节点在客户端输入命令后,会与命令中的节点进行握手,握手后,命令中的集群节点会将这个新节点的信息分享给集群中的每一个节点。


故障恢复


判断故障的逻辑其实与哨兵模式有点类似,在集群中,每个节点都会定期的向其他节点发送 ping 命令,通过有没有收到回复来判断其他节点是否已经下线。


如果长时间没有回复,那么发起 ping 命令的节点就会认为目标节点疑似下线,也可以和哨兵一样称作主观下线,当然也需要集群中一定数量的节点都认为该节点下线才可以,我们来说说具体过程:


  • 1。当 A 节点发现目标节点疑似下线,就会向集群中的其他节点散播消息,其他节点就会向目标节点发送命令,判断目标节点是否下线

  • 2.如果集群中半数以上的节点都认为目标节点下线,就会对目标节点标记为下线,从而告诉其他节点,让目标节点在整个集群中都下线


如何提高 redis 的读写能力


这个问题也是我们之前抛出来的问题,我们放一张图大家就会很容易明白了:


image

提高写能力只需要横向扩容 master


提高读能力只需要横向扩容 slave


结语


关于这三种部署的方式,基本上在我知道的公司都毫无疑问直接选择 cluster 模式,当然具体的选择还是要看公司的规模了,毕竟技术服务于业务,选择合适于当前业务的,就是最好的。


下期见~


发布于: 2021 年 01 月 04 日阅读数: 33
用户头像

moon聊技术

关注

玩玩技术,聊聊人生,看看生活,搞搞理想 2019.03.19 加入

我是moon 文章首发于我的微信公众号:哪儿来的moon,欢迎大家关注 ! 关注后回复666 有一线大厂面试题赠送,助你成为offer收割机!

评论

发布
暂无评论
主从哨兵集群终于给你说明白了