一、背景

前面的文章中,我们介绍过Redis的持久化机制,它可以实现Redis实例数据的crash-safe。但是这里有一个问题,就是Redis其实还存在着单点故障问题,比如说Redis的硬盘坏掉了,那么Redis实例当中的所有数据都会丢失,另外如果读并发非常高,单实例的Redis处理能力可能不够。

基于对上述问题的解决,Redis为我们提供了主从复制机制,简单说来,主从复制主要是为了解决以下两个问题:

(1)、读写分离

在Redis的QPS很高的情况下,为了强化Redis的写入性能,可以将所有或大部分读请求压力分摊到一台或多台从服务器上去,这样主服务器就可以提升处理写入请求的性能。如果网络状况比较好,而且数据一致性要求不是特别的高,甚至可以考虑关闭主服务器的持久化功能,采用无盘复制的操作来进行数据同步(关于无盘复制,正面的小节中,我们会再来研讨),这样主服务器的写入性能将得到进一步的提升。

(2)、数据灾备

前面提到,其实任何机器都有发生硬件故障的可能,比如硬盘坏了,主机掉电,或者主机网络出现故障,都有可能导致Redis实例全部数据的丢失,这听起来像是一个灾难,但它却是有可能发生的。那么如果我们将Redis的数据被冗余存储到其它的Redis实例上,就可以解决因为主机故障引发的Redis数据丢失问题。

二、核心原理

OK,前面我们介绍了Redis推出主从复制机制的背景,那么新的问题可能就是,Redis究竟是如何实现主从复制的呢?

整体上来讲,Redis的主从复制,有这么几个大的步骤,如下图所示:

(1)、全量复制

为了加快复制速度,从服务器会先向主服务器请求发送RDB文件,用于快速实现首次全量同步,前面我们介绍过RDB持久化,RDB文件比AOF文件在数据快速恢复上,拥有更好的性能而且更节省带宽资源。

(2)、基于长连接的命令传播

由于第一步的是异步复制的,那么对于主服务器来说,在执行异步全量复制的同时,可能还会有一些其它的新数据写入。另外,由于全量异步复制的时候,主从服务器之间已经建立起长连接(因为后续还有增量数据要复制话,如果用短连接,在高QPS的时候,会有性能风险),那么这个时候,主服务器会通过长连接来将新写入的命令发送给所有的从服务器。

(3)、网络断开重连后的增量复制。

如果上述第(2)步中,网络发生了断连怎么办?那么Redis自2.8版本之后,提供了增量复制的支持。主服务器先在内部维护一个缓冲区,用于写入新数据,从服务器断连后再重新连接主服务器时,主服务器会从上次从服务器已经完成的位置,继续读取新数据发送给从服务器。

上面,我们已经了解了Redis主从同步的基本步骤,接下来我们再来看看关于主从复制的几个概念:

run_id

主服务器启动的时候,它会生成一个40位的随机字符串,它叫run_id。通常情况下,只要主服务重启一次,默认情况下这个run_id都会发生变化(当然,通过配置也可以让它不变)。那么这个run_id有什么作用呢?它代表了主服务器当前的Redis实例的一个唯一身份标识。这里可能会有疑问,为什么不用不变的机器id(比如说mac地址之类)来表示run_id呢?因为同步的时候,从服务器会用到这个run_id来确定要同步的主服务器的数据,如果用不变的机器id来表示run_id,那么如果主服务器重启后主服务器的数据可能已经发生了变化(而从服务器却无法感知这种变化),那么它还按照之前的slave_repl_offset来做增量同步,这样的话就可能导致主从数据的不一致。

replication buffer

replication buffer,也叫复制缓冲区,它是用来做什么的呢?在主从服务器之间完成了全量复制之后,主服务器还会基于长连接向所有从服务器发送新写入的数据,那么这些新写入的数据,就是通过这个复制缓冲区来进行发送的,每一个对应的从服务器在主服务器中都会有一个对应的基于内存的复制缓冲区。

repl_backlog_buffer

repl_backlog_buffer是什么呢?它是一个环形队列缓冲区,就是我们上面第(3)步断连后重连的增量复制当中的缓冲区。在主进程fork完成子进程之后,Redis会同时将新写入的数据写入到复制缓冲区和repl_backlog_buffer中。为什么要同时写呢?这是因为往各个复制缓冲区写是为了通过长连接将命令实时的发送给各从服务器。往 repl_backlog_buffer写,是为了解决从服务器断连后再重连的时候,可以知道上次复制到了哪儿?因为repl_backlog_buffer有一个起始的offset(这个起始的offset会在全量复制的时候发给从服务器),主服务器自己内部维护一个单调增大的master_repl_offset,用以表示在环形缓冲中新写入数据的最新偏移量,而每一个从服务器会维护一个slave_repl_offset,用以表示当前从服务器已经完成复制的最新偏移量。不难得出这样的一组关系,即:master_repl_offset >= slave_repl_offset。

redis为我们提供了psync命令,来让从服务器向主服务器发起数据的同步请求,下面,我们先给出一个Redis主从复制过程的示意图,如下:

从上图我们不难看出,整个Redis主从复制的详细步骤如下:

第1步:从服务器通过配置参数replicaof来指定它要从哪台服务器同步数据过来,参数值就是ip和port,用于确定主从关系;

第2步:发送psync ? -1命令请求首次全量同步,这里命令的参数有两个:?和-1,前面的"?"表示是首次全量同步的时候,从服务器并不知道主服务器的run_id是多少,故用"?"号代表首次全量同步。-1是表示slave_repl_offset(因该值本来是一个非负数),故而用-1代表全量同步;

第3步:主服务器告诉从服务器,可以全量同步,并发送主服务器的run_id和初始的repl_backlog_buffer偏移量给从服务器,后面从服务器每次会自己根据已经完成复制的数据来维护slave_repl_offset;

第4步:主服务器执行RDB操作,生成RDB文件,用于给从服务器进行全量同步,关于RDB的细节,这里不再展开,前面《深入理解Redis持久化》的文章中已经有讨论过;

第5步:主服务器与从服务器建立长连接,并向从服务器发送完整的RDB文件,RDB文件中不仅包含数据,还会包含checksum、run_id等信息;

第6步:从服务器接受到RDB文件之后,先存储磁盘;

第7步:为了保证数据跟主服务器的一致性,从服务器会将自己当前内存的数据进行清空;

第8步:从服务器从本地磁盘加载主服务器发送过来的RDB文件,完成首次全量数据的初始化;

第9步:因为在前面第4-8步的时候,主服务器的主进程一直还在处理新数据的写入,所以这里主服务器会将新写入的数据通过第5步建立起的长连接,通过各个从服务器对应的replication buffer将数据发送给各从服务器;

如果第9步主服务器与某个从服务在基于长连接传播新数据的时候,连接被断开了的话,那么就会有下面的第10步和第11步:

第10步:从服务器向主服务器发送命令:psync run_id slave_repl_offset,告知主服从器自己已经完成增量同步的偏移量;

第11步:主服务器根据slave_repl_offset的位置,将从slave_repl_offset开始之后的数据,逐步发送给从服务器。

三、存在的问题

上面这个复制的过程,其实有如下一些问题:

问题1:前面我们已经提到,由于主服务器的内存限制,如果replication buffer被写满了,怎么办?

如果replication buffer被写满,那么主服务器会强制断开与那个对应的从服务器的连接,而从服务器会再度重尝试重连,重新请求复制。如果总是从服务器复制跟不上,这个过程就可能重复出现,形成复制风暴。这个时候,我们可以这样去解决:

(1)、检查网络状况是否良好;

(2)、检查从服务器的复制情况是否正常;

(3)、适当调大replication buffer的大小,由client-output-buffer-limit指定;

(4)、若还不能解决问题,那说明主服务器的写QPS已经很高,目前的架构已经无法满足需求,可以考虑多Redis实例写入的架构,比如说Redis Cluster,当然,这个已经不属于本文要讲的内容了。后面会专门撰文研讨Redis集群相关的技术。

问题2:由于从服务器复制速度比较慢,导致repl_backlog_buffer中最先写入的数据被覆盖了(而且被覆盖掉的数据,从服务器还没有来得及同步),怎么办?

这个时候,如果repl_backlog_buffer中的数据被覆盖了,从服务器还没有来得及同步的话,说明这个时候,主从服务器之间的数据已经存在着不一致的问题了。那么这个时候,主服务器会强制重新开始全量复制。那这样的话,代价就比较大了,所以一般情况下,我们会根据机器配置的具体情况,尽可能调大一点的repl_backlog_buffer的大小,由repl-backlog-size参数指定,默认是1mb,我们比如说可以调整到2mb或4mb,甚至更多一点。尽可能降低从服务器由于偶尔断开长连接存在一定数据落后的情况下不得得重新全量同步的风险。

但是呢,这个参数一般也不设置特别大,因为设置得特别大的话,有可能导致从服务器的数据相对比主服务器落后了也不易发现。

问题3:如果主从服务器间的长连接断开之后,主服务器是如何感知的呢?而且如果有大量的从服务器被断连的话,对于主服务器有什么风险呢?

其实在主服务器的内部,把所有从服务器的连接信息,放入到一个链表的结构中进行维护,而从服务器每隔一秒会向主服务器发送sync ack,每次发送过来一个ack,主服务器就会更新链表中对应从服务器的的最近心跳时间。这样主服务器通过定时轮询链表中的从服务器最近心跳时间和当前时间进行对比,来判断是否需要断开与从服务器的长连接。从服务器最近心跳时间与当前时间的差值,由repl_timeout参数指定(后面配置参数小节会提到)。

如果存在大量的从服务器被断开连接,那么主服务器有可能会拒绝接受所有的写入请求,redis提供了两个参数来对大量断连的情况进行配置,提供了min-replicas-to-write和min-replicas-max-lag参数,这两个参数的具体含义后面介绍参数配置的时候会再介绍。

问题4:如果从服务器比较多,我们又希望减轻从服务器的复制压力,怎么办呢?

其实,我们上面,一直是介绍的主从复制模式,其实redis还为我们提供了另外一种复制方式,我们称它为“主-从-从”模式,也就是主服务器将数据复制到从服务器A,然后再由从服务器A将数据复制到从服务器B,如下图所示:

这种模式的好处就是主服务器的复制压力可以得到减轻,但是缺点就是对于从服务器B来说,它的数据更新会比较慢,适用于从服务器读一致性要求不那么高的场景。

问题5:通过上面的问题4,你可能已经发现了新的问题,那就是即使只采用主从复制,从服务器的数据仍然是一定程度的落后于主服务器,这个主从复制的延迟问题,怎么解决呢?

对于这个问题来说,它其实是一个无解的问题,我们只能缓解(而无法彻底解决)这个问题,如果业务要求必须读操作的一致性。那么除了业务代码中进行妥协,让这部分强一致性的读请求强制读主,看起来貌似也没有什么太多其它的好办法了。

四、配置参数

OK,聊完了Redis主从复制的核心原理,接下来,我们看看主从复制相关的参数配置:

replicaof

如果当前服务器为slave,那么这里配置的就是master的ip和端口,如:192.168.1.100 6379

masterauth

如果当前服务器为slave,那么这里配置的就是master的访问密码

masteruser

如果当前服务器为slave,那么这里配置的就是master的用户名

replica-serve-stale-data yes

当slave失去与master的连接,或正在拷贝中,如果为yes,slave会响应客户端的请求,数据可能不同步甚至没有数据,如果为no,slave会返回错误"SYNC with master in progress"

replica-read-only yes

如果当前服务器为slave,这里配置slave是否只读,默认为yes,如果为no的话,就是可读可写。

repl-diskless-sync no

从服务器全量复制的时候,需要传输主服务数据的“完全备份”,也就是将主服务器的RDB文件传输到从服务器,那么,这种RDB文件的传输有以下两种方式:

硬盘备份:redis的主服务器的主进程fork一个新的子进程,用于把RDB文件写到硬盘上。然后由主进程递增地将RDB文件传送给从服务器。

无硬盘备份:redis主服务器的主进程fork一个新的子进程,直接在内存中将生成中的RDB文件写到从服务器的套接字,不需要用到硬盘。

在硬盘备份的情况下,主服务器的子进程生成RDB文件。一旦生成,多个从服务器可以立即排成队列使用主服务器的RDB文件。

在无硬盘备份的情况下,一次RDB传送开始,新的服务器到达后,需要等待当前传输结束,才能开启新的传输。如果使用无硬盘备份,主服务器会在开始传送之间等待一段时间(可配置,以秒为单位),希望等待多个从服务器到达后并行传输。

在硬盘低速而网络高速(高带宽)情况下,无硬盘备份更好。

repl-diskless-sync-delay 5

无盘复制延时开始秒数,默认是5秒,意思是当PSYNC触发的时候,master延时多少秒开始向master传送数据流,以便等待更多的slave连接可以同时传送数据流,因为一旦PSYNC开始后,如果有新的slave连接master,只能等待下次PSYNC。可以配置为0取消等待,立即开始。

repl-diskless-load disabled

是否使用无磁盘加载,有三个选择:

disabled:不要使用无磁盘加载,先将RDB文件存储到磁盘

on-empty-db:只有在完全安全的情况下才使用无磁盘加载

swapdb:解析时在RAM中保留当前db内容的副本,直接从套接字获取数据。

repl-ping-replica-period 10

这里指定slave定期向master进行心跳检测的周期,默认10秒

repl-timeout 60

对master进行心跳检测超时时间,默认60秒

repl-disable-tcp-nodelay no

在slave和master同步后(发送psync/sync),后续的同步是否设置成TCP_NODELAY . 假如设置成yes,则redis会合并小的TCP包从而节省带宽,但会增加同步延迟(40ms),造成master与slave数据不一致。假如设置成no,则redis master会立即发送同步数据,没有延迟。

client-output-buffer-limit replica 256mb 64mb 60

对于slave client和MONITER client,如果client-output-buffer一旦超过256mb,又或者超过64mb

持续60秒,那么服务器就会立即断开客户端连接

repl-backlog-size 1mb

设置主从复制backlog容量大小。这个 backlog 是一个用来在 slaves 被断开连接时存放slave数据的buffer,所以当一个 slave 想要重新连接,通常不希望全部重新同步,只是部分同步就够了,仅仅传递 slave 在断开连接时丢失的这部分数据。这个值越大,salve可以断开连接的时间就越长。

repl-backlog-ttl 3600

配置当master和slave失去联系多少秒之后,清空backlog释放空间。当配置成0时,表示永远不清空。

min-replicas-to-write 3

min-replicas-max-lag 10

假如主redis发现有超过M个从redis的连接延时大于N秒,那么主redis就停止接受外来的写请求。这是因为从redis一般会每秒钟都向主redis发出PING,而主redis会记录每一个从redis最近一次发来PING的时间点,所以主redis能够了解每一个从redis的运行情况。上面这个例子表示,假如有大于等于3个从redis的连接延迟大于10秒,那么主redis就不再接受外部的写请求。上述两个配置中有一个被置为0,则这个特性将被关闭。默认情况下min-replicas-to-write为0,而min-replicas-max-lag为10。

cxgrid主从表 点+号展开_深入理解Redis主从复制相关推荐

  1. 复制给节点的命令_深入理解redis主从复制原理

    1.复制过程 从节点执行 slaveof 命令. 从节点只是保存了 slaveof 命令中主节点的信息,并没有立即发起复制. 从节点内部的定时任务发现有主节点的信息,开始使用 socket 连接主节点 ...

  2. 解决cxgrid主从表数据显示不全的问题

    将主从表按照keyfieldnames键值排序,自然可以解决该问题. 转载于:https://blog.51cto.com/kaixinbuliao/1092755

  3. redis集群扩容和缩容_深入理解Redis Cluster集群

    一.背景 前面的文章<深入理解Redis哨兵机制>一文中介绍了Redis哨兵集群的工作原理,哨兵集群虽然满足了高可用的特性,但是依然存在这样的问题:即数据只能往一个主节点上进行写入. 只能 ...

  4. mysql主从表单如何设计_如何快速的10分钟制作一张主从表单及功能

    //#region 奖励管理--主单列表//奖励管理--主单列表--页面加载 function Reward_MainList_Init() { func_InitPageDataSource(); ...

  5. 怎么运用索引查处mysql表中的数据_深入理解MySQL数据库索引原理及实现,快速检索数据库 MySQL数据库使用教程...

    免费学习推荐: 一.索引的概念 1.索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址 (类似于C语言的链表通过指针指向数据记录的内存地址) . 2.使用索引后可以不用 ...

  6. redis aof 备份和恢复_深入理解Redis持久化

    redis持久化的意义 持久化机制的介绍 RDB和AOF的基本介绍 RDB持久化机制的优点 RDB持久化机制的缺点 AOF持久化机制的优点 AOF持久化机制的缺点 RDB和AOF到底该如何选择 RDB ...

  7. redis的zset的底层实现_深入理解Redis Zset原理

    前言 最近把 AirNet 中的空气质量排行换成了用 Zset 实现,这篇笔记就来深入了解下 Zset 的底层实现原理. Zset 编码的选择 在通过 ZADD 命令添加第一个元素到空 key 时, ...

  8. devexpress 主从表中从主、从表行列值的获得

    一,主从表的设置 代码             DataTable dt = pb.GetItemInfoList(Port).Copy(); //返回一个TABLE             dt.T ...

  9. mongodb 存储过程 遍历表数据_三、redis数据存储之跳跃表(SKIP LIST)

    导读 前面文章[一.深入理解redis之需要掌握的知识点 ]中,我们对redis需要学习的内容框架进行了一个梳理.[二.redis中String和List两种数据类型和应用场景 ].[二.redis中 ...

最新文章

  1. 微软已经宣布自2009年4月14日起放弃对windows xp的主流支持
  2. matlab拟合函数,Matlab拟合自定义函数 - 计算模拟 - 小木虫 - 学术 科研 互动社区...
  3. Android 环境配置
  4. [Leetcode][第309题][JAVA][最佳买卖股票时机含冷冻期][动态规划][压缩空间]
  5. 编译Linux版本飞鸽传书的不完全解决办法
  6. 继承(四):new方法都与基类中方法无关
  7. 张一鸣、王欣和罗永浩的社交梦
  8. 潘多拉固件设置ipv6_k2p路由器PandoraBox潘多拉与openwrt固件配置ipv6地址方法
  9. 关于注册测绘师的点点滴滴
  10. 实现读取txt文本 统计文本单词出现次数
  11. Android项目实战系列—基于博学谷(一)项目综述
  12. Dubbo错误No provider available for the service
  13. allegro 导 bom
  14. 【K70例程】003读取LM75A温度传感器(I2C)
  15. AI 隐身术,让你在视频中消失的“黑魔法”,想拥有吗?
  16. 直播、录播、录视频等
  17. 服务器如何通过域共享文件夹,如何在域中共享文件夹
  18. 【Windows】win10多桌面与多任务
  19. 数据库设计的阶段及对应产物
  20. PSPNet 算法笔记

热门文章

  1. 每日一皮:当两个程序员结婚后...
  2. 精美图文讲解Java AQS 共享式获取同步状态以及Semaphore的应用
  3. 以为是行废代码,原来有这作用!
  4. 优先级队列(头条面试题)
  5. 程序员的核心竞争力究竟是什么?
  6. rm 空间不释放_rm删除文件之后,空间就被释放了吗?
  7. cmake重新编译matlab,ubuntu系统下cmake 编译matlab中mex文件
  8. {TypeError}argument for rectangle() given by name (‘thickness‘) and position
  9. pip Can't connect to HTTPS URL because the SSL module is not available
  10. linux python保存mp4