分布式Redis深度历险-复制

Redis深度历险分为两个部分,单机Redis和分布式Redis。java

本文为分布式Redis深度历险系列的第一篇,主要内容为Redis的复制功能。redis

Redis的复制功能的做用和大多数分布式存储系统同样,就是为了支持主从设计,主从设计的好处有如下几点:安全

  • 读写分离,提升读写性能
  • 数据备份,减小数据丢失的风险
  • 高可用,避免单点故障

《分布式Redis深度历险-复制》

旧版复制实现

Redis的复制主要分为同步和命令传播两个步骤:服务器

同步能够理解为全量,是将主服务器某一时刻的全部数据所有同步到从服务器。网络

命令传播能够理解为增量,当主服务器数据被修改时,主服务器向从服务器发送对应的数据修改命令。架构

 

同步

同步分为如下几个步骤:并发

1.从服务器向主服务器发送SYNC命令(执行SLAVE OF命令的第一步也会执行SYNC分布式

2.主服务器在收到从服务器命令时,会执行BGSAVE,也就是新开一个子进程将内存中的数据保存到RDB文件中。同时使用一个内存缓冲区记录从如今开始执行的写命令,该内存缓冲区的做用就是记录RDB文件生成期间的增量。高并发

3.向从服务器发送RDB文件性能

4.将缓冲区中的写命令发送给从服务器

同步能够分为两种状况,一种是从服务器第一次链接主服务器,另外一种是从服务与主服务器的网络连接断开了,从新连上主服务器并从新同步。

 

命令传播

命令传播实现逻辑比较简单,当主服务器执行了写命令后,为了保证从服务器与主服务器数据的一致性,主服务器会将写命令发送给从服务器,从服务器执行完收到的写命令后其数据就能和主服务器保持一致了(固然会有延时),注意,从服务器对于客户端来讲是只读的,所以从服务器的全部数据都是来自于主服务器的同步or命令传播。

 

旧版复制存在的问题

假设Redis主从服务器之间的网络环境不太可靠,咱们来看看上述复制方法会出现什么问题。假设有主服务器A和从服务器B,主服务器中目前存在1-10000共一万条数据。

1.初始链接,从服务器第一次从主服务器同步数据,同步完成后,从服务器也有1-10000共一万条数据。

2.主服务器新增10001,10002两条数据

3.经过命令传播,从服务器也新增10001,10002两条数据

4.这时候主从服务器之间的网络断开

5.主服务器新增数据10003,由于网络断开,因此从服务器感觉不到数据变化

6.网络恢复,从服务器从新链接上主服务器,并发送SYNC命令,进行同步操做

7.主服务器将全部数据发送给从服务器(1-10003)

从上述步骤中能够看到,当从服务器从新链接上主服务器时,会从新进行全量同步,形成大量没必要要的IO开销,若是网络环境不稳定时,会致使主服务器一直将内存中的数据写到磁盘再发送给从服务器。

 

新版复制实现

为了解决老版复制问题,Redis2.8对于复制功能进行了优化。实现以下:

1.主服务器会维护一个偏移量,每次向服务器传播N个字节的数据时,该偏移量就会加上N,好比说一开始是0,接受到一条set key1 value1后,其偏移量就为13(真实偏移可能不是13,只是举个例子)。//这里可能要看下代码确认

2.从服务器也维护一个偏移量,当从服务器收到到主服务器的N个字节数据时,该偏移量会加上N。

3.主服务器维护一个固定大小的缓冲区,每次接受到客户端写命令后,都会将对应命令往这个缓冲区写入。当写入内容超出固定大小后,会覆盖原来的数据。

4.主服务器有一个惟一id

5.从服务器链接上主服务时,会向主服务器发送上一次链接的主服务器的id以及偏移量,这里又分几种状况:

  1. 若是从服务器没传id或者id与当前主服务器不匹配,那主服务器将传送全量数据
  2. 若是从服务器的offset在缓冲区中不能找到(落后太多致使缓冲区已经被新数据覆盖了),那也会进行全量同步
  3. 若是offset能在缓冲区找到,则主服务从offset开始,将缓冲区的数据依次发送给从服务器。(有作pipeline的优化吗)

以上就是新版复制的大体思路,要注意的是,主服务器缓冲区的大小设置很关键,若是设置的太大会致使空间浪费,若是过小会致使网络环境很差时,其退化为老版复制。

以前我就踩过这样的坑:在上云时,redis集群在两个不一样机房,主从以前网络环境不太稳定,而redis机器上存储的value比较大,很容易就将缓冲区占满致使每次全量同步,造成恶性循环,从服务器落后不可读,主服务器不可写(当从Redis落后太多时,主Redis将拒绝写入,具体参数能够配置的,下文还会提到)

因此建议将缓冲区大小设置为平均重连间隔*每秒写入数据量*2

 

主从心跳机制

从服务器默认会每秒一次的频率向主服务器发送心跳:
REPLCONF AÇK <replication_offset>
replication_offset表明从服务器当前的复制偏移量。

心跳有三个做用:

1.检测主从服务器的网络链接

2.实现min-slaves功能

3.检测命令丢失

 

检测主从服务器的网络链接

主服务器会记录从服务器上次发送心跳是什么时间,根据这个时间,咱们能知道主从服务器之间的链接是否是出现了故障

 

实现min-slaves功能

Redis为了保证数据的安全性,能够配置当从服务器小于min-slaves-to-write个或者min-slaves-to-write个从服务器的延迟都大于等于min-slaves-max-lag时,主服务器拒绝写。

 

检测命令丢失

主从之间的复制,实际上是以主服务器做为从服务器的客户端来实现的(在Redis中,全部服务器之间的数据传递都是以该种方式)。假设主服务器向从服务器发送一条写命令,但网络出现异常,从服务器并无收到该命令。


这就会致使数据不一致的状态(你可能想主服务器发送命令时,若是从没返回失败,进行重发不就行了吗?若是说从成功执行了命令,可是再回复主的时候出现了问题,那主若是重发就会形成数据异常了)。因此主服务器会根据心跳信息来决定要发送的数据。看个例子:

初始,主服务器和从服务器偏移量都是100。

主服务器收到客户端的写命令,将偏移量改为110,同时向从服务器发送写命令,但因网络缘由,从服务器并无收到,其偏移量仍然是100。主服务器根据心跳发现从服务器的偏移量是100落后于本身,因此会将100-110的数据进行重发。

 

看到这里,你可能对于上述方案的正确性感到质疑:在从服务器接收到100-110的数据前,它发送心跳包告诉主服务器本身当前偏移为100,而后接收到了100-110的数据。这时下个心跳还没发出,主服务器认为从服务器落后于本身,再次发送100-110的数据,致使从服务器再次写入100-110的数据,致使数据异常!

 

若是你有想到这个问题,说明你是有在认真思考了~

实际上是不存在这种状况的,缘由是redis是单线程的!记住单线程三个字,再回头看一遍问题描述,相信你能想明白~

 

 

原文:Java架构笔记

免费Java高级资料须要本身领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。           
传送门:            https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
相关文章
相关标签/搜索