Home Redis 剖析Redis集群之 主从复制

剖析Redis集群之 主从复制

0 72

在项目中用到了 Redis,为了水平扩展Redis 的处理能力(单机上的Redis能力受限于内存),保证Redis的高可用,所以需要搭建Redis集群

虽然之前已经系统的学习过Redis 的相关知识(极客时间的 Redis 课程),但对Redis的实际使用还有所欠缺,正好通过本次搭建集群整合Redis知识点,图中理论和图片来自极客时间。

 

 

关于Redis的介绍可以参考另一篇博文http://yangbili.co/wp-admin/post.php?post=1575&action=edit

 

使用Redis的什么情况下需要集群?

这个标题看上去有点好笑,Redis集群当然是在需要使用Redis集群的情况下使用;话虽如此说,但其实这句话包含很多东西。

第一,Redis不是只有一种模式的,也就是说Redis可以集群,也可以不集群

第二,你必须要使用Redis集群才去考虑它的集群(这句话用于劝告跟我一样的初学者,初学者有可能学习某项知识点,却不知道为什么要学习,或者说不知道学习了应该如何在实际中应用它)

一、实现Redis的水平扩展

好了,那么到底什么情况下需要使用Redis的集群呢?得从Redis的特点说起:redis是基于内存的,高性能,key-value,Nosql数据库;因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。基于内存却也有一个很大的缺点:数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

因此,我的理解是,当单个Redis实例(或服务)无法满足系统的读写要求,需要进行水平扩展的时候,采用Redis集群的策略。更形象一点就是,我们需要使用Redis存储1000万的数据,而单个的Redis实例因为内存限制无法存入这么多,所以我们使用Redis集群模式,启用多个RedisServer实例以满足需求。

二、强化Redis的读写能力,实现高可靠,保证某一个Redis服务实例挂掉不会影响系统运行

Redis的高可靠有两层含义,一是数据尽量少丢失,二是服务尽量少中断;AOF 和 RDB 保证了前者,而对于后者,Redis 的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。

结合实际来说,在我的项目中使用到了Redis进行数据的读写,刚开始我采用的是单个Redis服务实例,这种情况下,如果该Redis服务实例挂掉了,整个系统就会因此出现问题,也就是整个系统都不可用了。这样的情况在实际开发中是绝不允许的,在设计系统架构的时候就要充分考虑到尽力的保证系统的高可用性,也就是系统运行不中断。为了达到这个目的,我开始考虑使用Redis集群的策略。

Redis集群中有两种类型的节点:主节点(Master)、从节点(Slave)。

可以把集群理解为策略,而集群方案则是这个策略的具体实现。

 

Redis的集群方案(主从复制模式,哨兵模式,Cluster模式)

Redis支持三种集群方案

  • 主从复制模式
  • Sentinel(哨兵)模式
  • Cluster模式

一、主从复制模式

1.主从复制模式介绍

我们已知Redis通过增加副本冗余量将一份数据同时保存在多个实例上,这样即使一个实例出故障,其他实例也可以对外提供服务,不会影响业务使用。

多实例保存同一份数据,听起来很不错,但是我们必须要考虑一个问题就是:这么多副本,它们之间的数据如何保持一致呢?数据读写操作可以发给所有的实例呢?

实际上,Redis提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。

读操作:主库、从库都可以接收;

写操作:首先到主库执行,然后,主库将写操作同步给从库。

2.那么,为什么要采用读写分离的方式呢?

你可以设想一下,如果在上图中,不管是主库还是从库,都能接收客户端的写操作,那么,一个直接的问题就是:如果客户端对同一个数据(例如 k1)前后修改了三次,每一次的修改请求都发送到不同的实例上,在不同的实例上执行,那么,这个数据在这三个实例上的副本就不一致了(分别是 v1、v2 和 v3)。在读取这个数据的时候,就可能读取到旧的值。

如果我们非要保持这个数据在三个实例上一致,就要涉及到加锁、实例间协商是否完成修改等一系列操作,但这会带来巨额的开销,当然是不太能接受的。

而主从库模式一旦采用了读写分离,所有数据的修改只会在主库上进行,不用协调三个实例。主库有了最新的数据后,会同步给从库,这样,主从库的数据就是一致的。

 

3.主丛库间如何进行同步?(详细可看 https://time.geekbang.org/column/article/272852

具体工作机制为:

  • slave启动后,向master发送SYNC命令,master接收到SYNC命令后通过bgsave保存快照(RDB持久化),并使用缓冲区记录保存快照这段时间内执行的写命令
  • master将保存的快照文件发送给slave,并继续记录执行的写命令
  • slave接收到快照文件后,加载快照文件,载入数据(此三步为全量复制)
  • master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化
  • 此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致(后两步为增量复制)

总结来说,从库和主库建立连接,第一次复制采用全量复制,主库生成RDB文件(BGsave),同时记录在生成RDB期间执行的写命令,然后发送给从库,从库收到RDB文件后,首先清空数据库,在本地加载快照文件完成数据加载,因为RDB文件中不存在生成RDB期间执行的写命令,所以主库在发送完RDB文件后还需要发送这些写命令,从库接收这些写命令并重新执行这些操作,如此主从库实现同步。

三个阶段:建立连接,同步RDB文件,同步写命令

部署示例:https://www.zhihu.com/search?

type=content&q=Redis%E9%9B%86%E7%BE%A4

4.主从级联模式分担全量复制时的主库压力(主-从-从)

通过分析主从库间第一次数据同步的过程,你可以看到,一次全量复制中,对于主库来说,需要完成两个耗时的操作:生成 RDB 文件和传输 RDB 文件。

如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于 fork 子进程生成 RDB 文件,进行数据全量同步。(fork完后的子线程不会阻塞,但是fork出子线程的这个动作会阻塞)

fork 这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。此外,传输 RDB 文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。那么,有没有好的解决方法可以分担主库压力呢?

其实是有的,这就是“主 – 从 – 从”模式

在刚才介绍的主从库模式中,所有的从库都是和主库连接,所有的全量复制也都是和主库进行的。现在,我们可以通过“主 – 从 – 从”模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。

简单来说,我们在部署主从集群的时候,可以手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他的从库。然后,我们可以再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。

replicaof 所选从库的IP 6379

这样一来,这些从库就会知道,在进行同步时,不用再和主库进行交互了,只要和级联的从库进行写操作同步就行了,这就可以减轻主库上的压力,如下图所示:

级联的“主-从-从”模式

好了,到这里,我们了解了主从库间通过全量复制实现数据同步的过程,以及通过“主 – 从 – 从”模式分担主库压力的方式。那么,一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。

听上去好像很简单,但不可忽视的是,这个过程中存在着风险点,最常见的就是网络断连或阻塞。如果网络断连,主从库之间就无法进行命令传播了,从库的数据自然也就没办法和主库保持一致了,客户端就可能从从库读到旧数据。

接下来,我们就来聊聊网络断连后的解决办法。

 

5.主从库间网络断了怎么办?

 

 

6. 主从复制的优缺点

优点:

  • master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
  • master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求

缺点:

  • 不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复(哨兵可解决)
  • master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题
  • 难以支持在线扩容,Redis的容量受限于单机配置

 

个人总结:

Redis集群是为了水平扩展Redis的服务能力,而主从复制可以说是Redis集群实现的方案,我现在更加觉得Cluster集群和哨兵以及主从库复制不是相互对立的,而是一个整体的不同部分,这些不同的部分实现了Redis的高可靠性,高可用性;当然,你可以只实现其中某一部分。

本篇着重讲解的就是其中一个部分—主从复制,后续会讲到的哨兵模式是基于主从复制模式的。

主从复制,是通过增加副本冗余量来实现Redis的服务尽量少终端,以达到高可靠性;实现了副本冗余,就需要考虑各个副本分别负责什么,为了避免客户端对同一个数据进行修改,而不同的修改请求都发送到不同的实例上,在不同的实例上执行而导致的在不同实例上的副本不一样的情况,Redis的主从复制采用读写分离的分工;

读操作,主从都可以接收

写操作,由主库执行,再将写操作同步给从库

那怎么进行同步呢?

先发送RDB,再发送记录的写操作(第一次全量之后是增量,增量复制的时候采用长连接),同时为了减轻主库生成RDB和传输RDB的压力,可以采用 主-从-从的级联模式

那么如果在发送过程中网络中断了怎么办呢?

repl_backlog_buffer环形缓冲区,主库记录写的位置,从库记录自己读到的位置,相减再同步

 

补充:

 

提问:

主从库间的数据复制同步使用的是 RDB 文件,前面我们学习过,AOF 记录的操作命令更全,相比于 RDB 丢失的数据更少。那么,为什么主从库间的复制不使用 AOF 呢?

回答:

1、RDB文件内容是经过压缩的二进制数据(不同数据类型数据做了针对性优化),文件很小。而AOF文件记录的是每一次写操作的命令,写操作越多文件会变得很大,其中还包括很多对同一个key的多次冗余操作。在主从全量数据同步时,传输RDB文件可以尽量降低对主库机器网络带宽的消耗,从库在加载RDB文件时,一是文件小,读取整个文件的速度会很快,二是因为RDB文件存储的都是二进制数据,从库直接按照RDB协议解析还原数据即可,速度会非常快,而AOF需要依次重放每个写命令,这个过程会经历冗长的处理逻辑,恢复速度相比RDB会慢得多,所以使用RDB进行主从全量同步的成本最低。

2、假设要使用AOF做全量同步,意味着必须打开AOF功能,打开AOF就要选择文件刷盘的策略,选择不当会严重影响Redis性能。而RDB只有在需要定时备份和主从全量同步数据时才会触发生成一次快照。而在很多丢失数据不敏感的业务场景,其实是不需要开启AOF的。

 

参考资料:

https://www.zhihu.com/search?type=content&q=Redis%E9%9B%86%E7%BE%A4

Redis哨兵、复制、集群的设计原理与区别 – 知乎 (zhihu.com)

https://blog.csdn.net/shenjianxz/article/details/59775212

https://time.geekbang.org/column/article/272852

SIMILAR ARTICLES

发表评论

发表评论