Redis详解(4)- 主从复制原理

2022-04-14 15:13:47 浏览数 (1)

1、概述

Master节点在平时提供服务,另外一个或多个Slave节点在平时不提供服务(或只提供数据读取服务)。当Master节点由于某些原因停止服务后,再人工/自动完成Slave节点到Master节点的切换工作,以便整个Redis集群继续向外提供服务。

2、主从复制工作过程

Redis的主从复制功能除了支持一个Master节点对应多个Slave节点的同时进行复制外,还支持Slave节点向其它多个Slave节点进行复制。这样使得我们能够灵活组织业务缓存数据的传播,例如使用多个Slave作为数据读取服务的同时,专门使用一个Slave节点为流式分析工具服务。Redis的主从复制功能分为两种数据同步模式进行:全量数据同步和增量数据同步。

全量数据同步:

先执行一次全同步 — 请求master BgSave出自己的一个RDB Snapshot文件发给slave,slave接收完毕后,清除掉自己的旧数据,然后将RDB载入内存。

增量数据同步: 再进行增量同步 — master作为一个普通的client连入slave,将所有写操作转发给slave,没有特殊的同步协议。

上图简要说明了Redis中Master节点到Slave节点的全量数据同步过程。当Slave节点给定的run_id和Master的run_id不一致时,或者Slave给定的上一次增量同步的offset的位置在Master的环形内存中无法定位时(后文会提到),Master就会对Slave发起全量同步操作。这时无论您是否在Master打开了RDB快照功能,它和Slave节点的每一次全量同步操作过程都会更新/创建Master上的RDB文件。在Slave连接到Master,并完成第一次全量数据同步后,接下来Master到Slave的数据同步过程一般就是增量同步形式了(也称为部分同步)。增量同步过程不再主要依赖RDB文件,Master会将新产生的数据变化操作存放在一个内存区域,这个内存区域采用环形构造。过程如下

为什么在Master上新增的数据除了根据Master节点上RDB或者AOF的设置进行日志文件更新外,还会同时将数据变化写入一个环形内存结构,并以后者为依据进行Slave节点的增量更新呢?主要原因有以下几个:

  • 由于网络环境的不稳定,网络抖动/延迟都可能造成Slave和Master暂时断开连接,这种情况要远远多于新的Slave连接到Master的情况。如果以上所有情况都使用全量更新,就会大大增加Master的负载压力——写RDB文件是有大量I/O过程的,虽然Linux Page Cahe特性会减少性能消耗。
  • 另外在数据量达到一定规模的情况下,使用全量更新进行和Slave的第一次同步是一个不得已的选择——因为要尽快减少Slave节点和Master节点的数据差异。所以只能占用Master节点的资源和网络带宽资源。
  • 使用内存记录数据增量操作,可以有效减少Master节点在这方面付出的I/O代价。而做成环形内存的原因,是为了保证在满足数据记录需求的情况下尽可能减少内存的占用量。这个环形内存的大小,可以通过repl-backlog-size参数进行设置。

Slave重连后会向Master发送之前接收到的Master run_id信息和上一次完成部分同步的offset的位置信息。如果Master能够确定这个run_id和自己的run_id一致且能够在环形内存中找到这个offset的位置,Master就会发送从offset的位置开始向Slave发送增量数据。那么连接正常的各个Slave节点如何接受新数据呢?连接正常的Slave节点将会在Master节点将数据写入环形内存后,主动接收到来自Master的数据复制信息。

3、主从复制配置

slave可以在配置文件、启动命令行参数、以及redis-cli执行SlaveOf指令来设置自己是slave。 测试表明同步延时非常小,指令一旦执行完毕就会立刻写AOF文件和向Slave转发,除非Slave自己被阻塞住了。 比较蠢的是,即使在配置文件里设了slavof,slave启动时依然会先从数据文件载入一堆没用的数据,再去执行slaveof。 “Slaveof no one”,立马变身master。 2.8 版本将支持PSYNC部分同步,master会拨出一小段内存来存放要发给slave的指令,如果slave短暂的断开了,重连时会从内存中读取需要补读 的指令,这样就不需要断开两秒也搞一次全同步了。但如果断开时间较长,已经超过了内存中保存的数据,就还是要全同步。 Slave也可以接收Read-Only的请求。

Redis提供的主从复制功能的配置信息,在Redis主配置文件的“REPLICATION”部分。以下是这个部分的主要参数项说明:

  • slaveof <masterip> <masterport>:如果您需要将某个节点设置为某个Master节点的Slave节点,您需要在这里指定Master节点的IP信息和端口信息。这个设置项默认是关闭的,也即是说Master节点不需要设置这个参数。另外,除了通过配置文件设置外,您还可以通过Redis的客户端命令进行slaveof设定。
  • slave-serve-stale-data:当master节点断开和当前salve节点的连接或者当前slave节点正在进行和master节点的数据同步时,如果收到了客户端的数据读取请求,slave服务器是否使用陈旧数据向客户端提供服务。该参数的默认值为yes。
  • slave-read-only 是否将salve节点设置为“只读”。一旦设置为“只读”,表示这个Salve节点只会进行数据读取服务,如果客户端直接向这个Salve节点发送写数据的请求,则会收到错误提示。建议采用默认的“yes”值进行设定。
  • repl-diskless-sync:上文已经介绍过Redis的主从复制功能基于RDB,后者的过程是将数据刷入RDB文件(实际上是Linux的Page Cache区域),然后基于RDB文件内容的更新情况和Salve当前已同步的数据标记点来进行Salve上的数据更新。所以这个过程实际会增加一定的数据延迟,消耗一定的处理资源。基于这个情况,Redis中提供了一种不经过物理磁盘设备就进行主从数据同步的技术,称为diskless。但是直到Redis version 3.2这个技术也一直处于试验状态,所以并不推荐在生产环境下使用:“ WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY”。
  • repl-diskless-sync-delay:这个参数只有在上一个参数设置为“yes”时才起作用,主要是设置在进行两次diskless模式的数据同步操作的时间间隔。默认为5秒。
  • repl-ping-slave-period:Slave节点向Master节点发送ping指令的事件间隔,默认为10秒。
  • repl-timeout:这是一个超时间,当某些操作达到这个时间时,Master和Slave双方都会认为对方已经断开连接。实际上您可以将这个时间看成是一个租约到期的时间。那么这个操作时间会影响哪些操作呢?A、向Slave进行的数据同步操作本身不能超过这个时间;B、Slave向Master发送一个PING指令并等待响应的时间;C、Master向Slave发送PONG回复并等待ACK的时间。
  • repl-disable-tcp-nodelay:这个选项的默认值为no,它对优化主从复制时使用的网络资源非常有用。要明白这个参数的含义,就首先要解释一下tcp-nodelay是个什么玩意儿?TCP数据报的报文头包含很多属性,这些属性基本上起到记录和保证传输目的、传输状态的作用,但没有数据报的所携带的业务数据(称之为有效载荷)。那么很明显,20个字节内容的信息分成20个数据报进行传输和只用一个数据报进行传输,需要占用的网络资源就完全不一样。JohnNagle在1984年发明了一种减轻网络传输压力的算法,就是为了解决这个问题(算法的名字就叫做“Nagle”,后续的技术人员又做了很多改进和升级)。其基本思路就是将要发送的内容凑够一定的数量后,再用一个数据报发送出去。如果该属性设置为yes,Redis将使用“Nagle”算法(或类似算法),让数据报中的有效载荷凑够一定数量后,在发送出去;设置成no,Redis就不会这么做。
  • repl-backlog-size:上文已经介绍过了Redis中为了进行增量同步所准备的环形内存区域,以及Redis这样做的原因额,所以这里就不再赘述了。这个选项就是用来设置环形内存的大小的,这个选项的默认值为1MB;正式的生产环境下可以稍微加大一些,例如5MB。
  • slave-priority:当前Slave节点的优先级权重。我们后文会介绍一款Redis自带的监控和故障转移工具:Redis Sentinel,这个工具允许一个Master节点下有多个Slave节点,并且可以自动切换Slave节点为Master节点。如果Slave节点的优先级权重值越低,就会再切换时有限成为新的Master节点。
  • min-slaves-to-write和min-slaves-max-lag:为了尽可能避免Master节点对应的多个Slave节点在数据复制过程中数据差异被越拉越大。Redis服务提供了一组拒绝数据写操作的策略,这个策略可以解释为:当Master上在min-slaves-max-lag时间(单位秒)间隔后,任然有min-slaves-to-write个Slave和它正常连接,那么Master才允许进行数据写操作。

1、主库master配置:

Master服务器不需要针对主从复制做任何的设置(这不包括对主从复制过程的配置优化)。

2、从库slave配置:

Slave节点上我们只需要做一件事情,就是打开slaveof选项:

# slaveof选项的设置,给定master节点的ip和port就可以了

#192.168.61.140就是master节点

slaveof 192.168.10.10 6379

接着,我们马上就可以看看同步效果了。首先确保您的master节点使工作正常的,然后就可以启动Slave节点了

3、Redis-sentinel- Fail-over

Redis-sentinel是2.6版开始加入的另一组独立运行的节点,提供自动Fail Over的支持。

1 、主要执行过程

Sentinel每秒钟对所有master,slave和其他sentinel执行Ping,redis-server节点要应答 PONG或-LOADING或-MASTERDOWN. 如果某一台Sentinel没有在30秒内(可配置得短一些哦)收到上述正确应答,它就会认为master处于sdown状态(主观Down) 它向其他sentinel询问是否也认为该master倒了(SENTINEL is-master-down-by-addr ), 如果quonum台(默认是2)sentinel在5秒钟内都这样认为,就会认为master真是odown了(客观Down)。 此时会选出一台sentinel作为Leader执行fail-over, Leader会从slave中选出一个提升为master(执行slaveof no one),然后让其他slave指向它(执行slaveof new master)。

2、 master/slave 及 其他sentinel的发现

master 地址在sentinel.conf里, sentinel会每10秒一次向master发送INFO,知道master的slave有哪些。 如果master已经变为slave,sentinel会分析INFO的应答指向新的master。以前,sentinel重启时,如果master已经 切换过了,但sentinel.conf里master的地址并没有变,很可能有悲剧发生。另外master重启后如果没有切换成slave,也可能有悲 剧发生。新版好像修复了一点这个问题,待研究。

另 外,sentinel会在master上建一个pub/sub channel,名为”sentinel:hello”,通告各种信息,sentinel们也是通过接收pub/sub channel上的 sentinel的信息发现彼此,因为每台sentinel每5秒会发送一次自己的host信息,宣告自己的存在。

3、自定义reconfig脚本

sentinel在failover时还会执行配置文件里指定的用户自定义reconfig脚本,做用户自己想做的事情,比如让master变为slave并指向新的master。 脚 本的将会在命令行按顺序传入如下参数: <master-name> <role(leader/observer)> <state(上述三种情况)> <from-ip> <from-port> <to-ip> <to-port> 脚本返回0是正常,如果返回1会被重新执行,如果返回2或以上不会。 如果超过60秒没返回会被强制终止。 觉得Sentinel至少有两个可提升的地方:

一是如果master 主动shutdown,比如系统升级,有办法主动通知sentinel提升新的master,减少服务中断时间。 二是比起redis-server太原始了,要自己丑陋的以nohup sentinel > logfile 2>&1 & 启动,也不支持shutdown命令,要自己kill pid。

4、Client的高可用性

基 于Sentinel的方案,client需要执行语句SENTINEL get-master-addr-by-name mymaster 可获得当前master的地址。 Jedis正在集成sentinel,已经支持了sentinel的一些指令,但还没发布,但sentinel版的连接池则暂时完全没有,在公司的项目里 我参考网友的项目自己写了一个。

淘 宝的Tedis driver,使用了完全不同的思路,不基于Sentinel,而是多写随机读, 一开始就同步写入到所有节点,读的话随便读一个还活着的节点就行了。但有些节点成功有些节点失败如何处理? 节点死掉重新起来后怎么重新同步?什么时候可以重新Ready? 所以不是很敢用。

另外如Ruby写的redis_failover,也是抛开了Redis Sentinel,基于ZooKeeper的临时方案。

Redis作者也在博客里抱怨怎么没有人做Dynamo-style 的client。

4、问题

1、 Trouble Shooting again

有时候明明master/slave都活得好好的,突然间就说要重新进行全同步了:

1.Slave显示:# MASTER time out: no data nor PING received…

slave 会每隔repl-ping-slave-period(默认10秒)ping一次master,如果超过repl-timeout(默认60秒)都没有收 到响应,就会认为Master挂了。如果Master明明没挂但被阻塞住了也会报这个错。可以适当调大repl-timeout。

2.Master 显示:# Client addr=10.175.162.123:44670 flags=S oll=104654 omem=2147487792 events=rw cmd=sync scheduled to be closed ASAP for overcoming of output buffer limits.

当 slave没挂但被阻塞住了,比如正在loading Master发过来的RDB, Master的指令不能立刻发送给slave,就会放在output buffer中(见oll是命令数量,omem是大小),在配置文件中有如下配置:client-output-buffer-limit slave 256mb 64mb 60, 这是说负责发数据给slave的client,如果buffer超过256m或者连续60秒超过64m,就会被立刻强行关闭!!! Traffic大的话一定要设大一点。否则就会出现一个很悲剧的循环,Master传输一个大的RDB给Slave,Slave努力的装载,但还没装载 完,Master对client的缓存满了,再来一次。

平时可以在master执行 redis-cli client list 找那个cmd=sync,flag=S的client,注意OMem的变化。

0 人点赞