上文,咱们介绍了异地双活在哈啰四轮出行的落地。

本文次要讲述异地双活计划redis的热备、双写、双向同步的区别和优劣势。并且阐明了双写同步计划中redis集群主从数据同步的过程,以及中间件计划遇到的局部问题点,阐明最终计划的实现思路和计划。

redis的双活计划无非有以下三种:热备,双写,双向同步。下文会阐明三个计划的区别,并着重解说双向同步的计划。

热备

其中,热备因为备用redis集群平时不会被应用,只有双活故障切换才会应用,且该计划redis备机须要实时数据同步,则切换后的稳定性较低、并且须要同时保护两套集群,老本也不低。两个IDC同连一个redis集群,如果某个IDC间隔过大,则必然至多有一个IDC和redis集群间隔过大,则该计划的提早相比原生redis会大大增加(该提早同redis双写的提早)。因而在异地双活中根本不会思考,可做为后期的同城双活计划的适度计划,本文不会具体阐明。

双写

双写,顾名思义就是两个机房同时写入,即在双活我的项目中,一个redis的命令写入本机房redis集群的同时,会同时写入另外一个机房的redis集群。保障两个机房redis数据是统一的。如下图所示:

在我了解中,双写次要分为以下四个形式:同步双写、异步双写、redis集群维度同步双写、redis集群维度异步双写。

同步双写

同步双写,指的是同个redis命令同时写入两个机房,是同步执行的。

例如:用户发了一个逆风车乘客订单,该订单被路由到核心机房(HZUNIT),且发单胜利在业务上须要缓存订单数据到redis中,则该订单会先在核心机房(HZUNIT)写入一条缓存数据,同时会同步写入一条数据到异地的单元机房(SHUNIT)。

该计划存在以下几个问题:

  1. 保护老本

如果双写须要在业务代码层面保护,那代码侵入性会十分大。如果双写有个配置页面,配置须要双写的key,由中间件SDK实现双向,则每次新增或者批改双写的key都要去配置页面配置,前期保护中漏配危险十分大。

  1. 回滚问题

无论双写是在业务代码层面保护还是中间件SDK保护,那么当本机房操作胜利,异地机房操作失败后,业务代码或者中间件代码须要思考回滚逻辑,是跳过,还是回滚之前的命令,还是重试异地机房写入命令。如果抉择重试,那始终失败如何解决,怎么保证数据一致性都是比拟大的问题。

  1. 提早

提早是最致命的,家喻户晓redis的写入速度是十分高的,通常一个redis命令的写入是1ms以内,然而如果同步双写,则写入异地机房的时长会由两机房的物理间隔决定,依据饿了么双活的测算,北京-上海的提早大概为30ms,那这个异地的提早是30倍,假如业务原先写入该redis命令的工夫是1ms,那双写后变为31ms,显然这是无奈承受的。

异步双写

异步双写,指的是同个redis命令写入两个机房,对本机房是同步写入的,对异地机房是异步写入的。

例如:用户发了一个逆风车乘客订单,该订单被路由到核心机房(HZUNIT),且发单胜利在业务上须要缓存订单数据到redis中,则该订单会先在核心机房(HZUNIT)写入一条缓存数据,同时会异步写入一条数据到异地的单元机房(SHUNIT)。

该计划同样存在以下几个问题:

  1. 保护老本

与同步双写计划问题统一。

  1. 回滚问题

大部分与同步双写计划问题统一,且因为异步,一致性更无奈保障。

  1. 提早

该计划因为异地机房是异步写入,因而提早问题影响简直没有。(如数据要求强统一,则不适宜用异步双写)

集群同步双写

集群同步双写,指的是同个redis命令写入两个机房,是同步执行的。然而双写是redis集群维度的,即配置了该redis集群为双写,那么整个redis集群的任意写入命令都会进行同步双写操作。

该计划同样存在以下几个问题:

  1. 保护老本

该计划的保护老本只在于新增redis集群后须要配置一下,或者告诉中间件,保护老本较低。

  1. 回滚问题

与同步双写问题统一。

  1. 提早

与同步双写问题统一。

集群异步双写

集群异步双写,指的是同个redis命令写入两个机房,对本机房是同步写入的,对异地机房是异步写入的。然而双写是redis集群维度的,即配置了该redis集群为双写,那么整个redis集群的任意写入命令都会进行异步双写操作。

该计划同样存在以下几个问题:

  1. 保护老本

该计划的保护老本只在于新增redis集群后须要新增配置一下,或者告诉中间件,保护老本较低。

  1. 回滚问题

与异步双写问题统一。

  1. 提早

与异步双写问题统一。

论断

从以上阐明中能够看出redis集群维度同步双写绝对于同步双写,有了较低的保护老本,提早问题和回滚问题无奈解决。redis集群维度异步双写绝对于异步双写,有了较低的保护老本,回滚问题仍旧无奈解决。无论同步双写还是redis集群维度同步双写提早问题都是比拟大的,因而这两个计划都最好不必。异步双写和redis集群维度异步双写,数据一致性问题则是一个大问题,因而该计划在某些数据一致性的场景下也不可用。基于以上两种计划,redis双向同步计划应运而生。

双向同步

参考:redis设计与实现

redis双向同步,即redis写入代码在业务侧无任何区别,还是只写入本机房,只是底层通过中间件同步工作,同步到异地机房。如下所示:

该计划也会有数据一致性问题,然而该计划的数据同步是中间件双向同步利用假装为redis集群的slave节点,因而一致性的保障和redis集群的一致性保障是统一的,基本上能满足双活需要。

概念阐明

复制偏移量(offset)

  • 执行主从复制的单方都会别离保护一个复制偏移量
  • master 每次向 slave 流传 N 个字节,本人的复制偏移量就减少 N
  • slave 接管 N 个字节,本身的复制偏移量也减少 N。
  • 通过比照主从之间的复制偏移量就能够晓得主从间的同步状态
  • offset会始终自增,不会到backlog的size停下从0开始

复制积压缓冲区(backlog)

  • 为了解决断点重连全量同步问题设计
  • master往slave同步数据时,会将数据往backlog也写一份
  • 当master和slave网络抖动从新连贯后,slave会将本人的offset发送给master
  • master比照slave的offset和backlog的offset
  • 如果slave的offset在backlog,则将复制缓冲区以后的offset之后的数据同步给slave
  • 如果slave的offset不在backlog中,则进行全量同步

运行id(runid)

  • 基于backlog逻辑缺点设计
  • 每个redis server无论主从都有本人的runid
  • 由40位随机16进制字符组成。如:53b9b28df8042fdc9ab5e3fcbbbabff1d5dce2b3
  • 如slave断网重连后,slave会将offset和以后保留的runid发送给master
  • master比照slave传入的runid和本人的runid是否统一,从而判断是否同一个master

redis大版本解决问题

sync-->psync1-->psync2的进化

  • redis2.8版本以下:无论是第一次主从复制还是断线重连后再进行复制都采纳全量同步,因而如果redis主从节点网络抖动频繁,则会导致频繁的全量同比,导致整个集群压力较大。(同步形式sync)
  • redis2.8-4.0以下版本:反对断点续传,然而master切换或者复制积压缓冲区满了当前,无奈断点续传。(同步形式psync1)
  • redis4.0及以上版本:反对master切换的断点续传,然而复制积压缓冲区满了也无奈反对断点续传。(同步形式psync2)

sync

redis2.8版本以下,通过sync命令进行同步,集群无runid,backlog和offset,因而每次网络抖动导致的redis主从节点断开,等到主从节点从新连贯后,都会进行全量的数据同步,当初该版本应该简直没有在应用了,本文不做具体形容。

psync1

为了解决redis的断点续传而设计,通过runid和offset以及backlog,从而记录之前连贯断开或者故障前的同步进度,等从新连贯后依据offset和backlog断点续传。

无奈解决问题:

  • 断点后新写入的数据齐全笼罩backlog,导致原先断开连接前的offset不在backlog中
  • 主从切换后,因为主节点更换,也会引发全量同步

数据同步过程

数据全量同步过程如下所示:

  • 当主从从新连贯后,从服务器会通知主服务器,是否是第一次执行,如果是的话,则向主服务器发送psync ? -1 命令,该命令代表从服务器通知主服务器须要全量同步,则主服务器间接会把全量数据推给从服务器
  • 如果从服务器通知主服务器是断点续传,则会向主服务器发送psync 命令,通知主服务器须要断点续传
  • 当主服务判断offset不在backlog中,或者runid和本人id不统一,则会通知从服务器须要进行全量同步,并把全量数据返回给从服务器
  • 当主服务判断offset在backlog中,或者runid和本人id统一,则会进行断点续传

下图from:redis设计与实现

psync1命令的主从全量同步

当redis的主从关系是新的,会进行全量同步,或者之前执行过slaveof no one命令,全量同步的过程如下:

  • slave从未任何master同步过数据(新的slave)或者是之前执行过slaveof no one命令,在开始新的一次复制之时向master发送psync?-1命令被动申请全量同步
  • master执行bgsave,生成rdb文件,并发送数据到slave
  • master返回+FULLRESYNC和master的runId以及以后的offset
  • slave保留runId,并且将offset作为本人的初始化偏移量

psync1的增量同步(断点续传)

失常

  • 失常状况下,redis主从同步是从主节点同步给从节点。(主节点发送的时候会带上runid和offset)
  • 在同步过程中,master新写入的数据,会在backlog保留一份

异样

如因为网络起因导致连贯异样,slave会主动申请master从新建设连贯。

  • slave发送connect
  • slave发送psync runid offset
  • 判断offset是否在backlog
  • offset在backlog中:是的话会将backlog外面offset为slave offset之后的全副数据进行通报
  • offset不在backlog中:进行全量同步

psync2解决的问题

  • psync2次要是引入了replid(同runid)和replid2(之前的master的replid)
  • slave的简单积压缓冲区解决HA的全量同步问题
  • slave的复制积压缓冲区作用,切换成master后,也有offset
  • replid2的作用能够判断新的master和新的slave是否之前是一个集群,如果是一个集群则能够依据offset进行数据同步
  • 主从切换的时候,依据replid2和offset进行增量同步
  • 然而slave的offset大于以后backlog的最大offset,或者小于以后backlog的最小offset(不在backlog中)还是会进行全量同步

后面次要是阐明redis自身集群的主从数据是如何同步的,而中间件的双向同步则是基于假装未主从的slave进行的数据同步,前面则着重阐明多主多从的状况下,数据是如何调配到正确的节点(卡槽)

卡槽判断逻辑

下图from:redis设计与实现

三主三从集群数据同步

以三主三从为例,数据调配节点和同步的过程如下:

  • 客户端执行set k.v命令进行写入redis cluster(集群)
  • 如果master1在集群初始化承接的slotid为0-5000,master2为5001-10000,master3位10001-16383
  • redis cluster对key进行CRC16算法对16384取模,别离落入响应的master1或者master2或者master3
  • 如果取模后的值为4500,则会写入master1,而master1会将该命令发送给slave1

中间件计划

假装slave

假装的slave节点交互与 master 的交互细节

  • 发送 AUTH {password}
  • 发送 REPLCONF listening-port {port} , 告知master本人的监听端口
  • 发送 REPLCONF ip-address {address}, 告知master本人的ip
  • 发送 REPLCONF capa eof (应用 diskless-replication)
  • 发送 REPLCONF capa psync2
  • 发送 PSYNC {repl_id} {repl_offset} , 发送复制id(master runid) 和本人复制进度offset
  • 接管 Binary data
  • 接管 aof data

双向同步计划

redis的双写同步计划是基于两个单元同步实现的,是一个单元到核心的单向同步和核心到单元的单向同步,两个合并为一个双写同步计划。

根本阐明

  • center_cluster代表核心机房的redis的集群
  • center_master_node代表核心机房的redis的master节点
  • unit_cluster代表单元机房的redis的集群
  • unit_master_node代表单元机房的redis的master节点
  • center_replication代表中间件核心到单元的同步工作
  • unit_replication代表中间件单元到核心的同步工作

同步过程

以客户端写入在核心机房,同步到单元机房为例

  • 客户端执行写入命令set k.v
  • 该命令被调配到核心机房,center_cluster
  • 该命令依据slotid调配到对应的master节点
  • master节点会向中间件伪装成的slave节点发送数据,即向center_replication单向同步工作发送数据
  • center_replication同步工作会同步到unit-cluster
  • 后续则为unit-cluster单元机房redis集群本人的数据同步

问题--如何防循环复制

从同步过程能够看出,该双向同步是个环装,还是以下面例子为例,当center-master-node同步数据到unit-cluster后,unit-cluster也会有个unit-replication监听unit-master-node,进行同步到核心机房,则该命令会进行循环同步。

解法

通过固定前缀的防循环复制命令key,避免循环复制。

因为redis单线程的个性,在center-replation接管到主节点的命令后,会在set k,v命令后紧接跟着set一个固定前缀的key值(防循环复制key)。在unit-repliation接管到该节点的命令后,会getNext(),判断next命令是否防循环复制命令,如果是防循环复制key,则unit-repliation不会进行同步,否则的话,unit-repliation会进行同步。

问题:如何保障redis的原始命令的key和防循环复制的命令的slotid雷同(不同则getNext会拿到非防循环命令,导致无奈防循环复制)。

答:通过hashTag保障,具体可查问材料。

单向同步工作

单元同步工作是依据源和指标设置的,源即代表数据源的节点,该节点为单个master节点,指标即为指标的redis集群节点,为一个集群。即如果核心机房有三个主节点,则做核心到单元的单向同步,须要配置三个同步工作,每个同步工作的源节点为master1、master2、master3。指标节点均为正规单元机房redis集群。

问题:为何指标节点为redis集群?

因为单元和核心机房的主从数量不是一比一的,比方核心机房为三主三从,单元机房为2主2从。那么set k,v命令在核心机房属于master3,则在单元机房是属于master1或者master2。因而须要将指标数据同步到集群中,由集群的slot自行判断落入哪个master。

退一步说,即便核心机房为3主3从,单元机房也为3主3从,那么核心机房的master1不代表肯定对应单元机房的master1,因为slotid的范畴是和每个节点的性能也是相干的。

双向同步残缺计划

以下是我本人画的redis双向同步残缺计划,如下所示。

(本文作者:柳健强)

本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者应用。非商业目标转载或应用本文内容,敬请注明“内容转载自哈啰技术团队”。