上文,咱们介绍了异地双活在哈啰四轮出行的落地。
本文次要讲述异地双活计划 redis 的热备、双写、双向同步的区别和优劣势。并且阐明了双写同步计划中 redis 集群主从数据同步的过程,以及中间件计划遇到的局部问题点,阐明最终计划的实现思路和计划。
redis 的双活计划无非有以下三种:热备,双写,双向同步。下文会阐明三个计划的区别,并着重解说双向同步的计划。
热备
其中,热备因为备用 redis 集群平时不会被应用,只有双活故障切换才会应用,且该计划 redis 备机须要实时数据同步,则切换后的稳定性较低、并且须要同时保护两套集群,老本也不低。两个 IDC 同连一个 redis 集群,如果某个 IDC 间隔过大,则必然至多有一个 IDC 和 redis 集群间隔过大,则该计划的提早相比原生 redis 会大大增加(该提早同 redis 双写的提早)。因而在异地双活中根本不会思考,可做为后期的同城双活计划的适度计划,本文不会具体阐明。
双写
双写,顾名思义就是两个机房同时写入,即在双活我的项目中,一个 redis 的命令写入本机房 redis 集群的同时,会同时写入另外一个机房的 redis 集群。保障两个机房 redis 数据是统一的。如下图所示:
在我了解中,双写次要分为以下四个形式:同步双写、异步双写、redis 集群维度同步双写、redis 集群维度异步双写。
同步双写
同步双写,指的是同个 redis 命令同时写入两个机房,是同步执行的。
例如:用户发了一个逆风车乘客订单,该订单被路由到核心机房(HZUNIT),且发单胜利在业务上须要缓存订单数据到 redis 中,则该订单会先在核心机房(HZUNIT)写入一条缓存数据,同时会同步写入一条数据到异地的单元机房(SHUNIT)。
该计划存在以下几个问题:
- 保护老本
如果双写须要在业务代码层面保护,那代码侵入性会十分大。如果双写有个配置页面,配置须要双写的 key,由中间件 SDK 实现双向,则每次新增或者批改双写的 key 都要去配置页面配置,前期保护中漏配危险十分大。
- 回滚问题
无论双写是在业务代码层面保护还是中间件 SDK 保护,那么当本机房操作胜利,异地机房操作失败后,业务代码或者中间件代码须要思考回滚逻辑,是跳过,还是回滚之前的命令,还是重试异地机房写入命令。如果抉择重试,那始终失败如何解决,怎么保证数据一致性都是比拟大的问题。
- 提早
提早是最致命的,家喻户晓 redis 的写入速度是十分高的,通常一个 redis 命令的写入是 1ms 以内,然而如果同步双写,则写入异地机房的时长会由两机房的物理间隔决定,依据饿了么双活的测算,北京 - 上海的提早大概为 30ms,那这个异地的提早是 30 倍,假如业务原先写入该 redis 命令的工夫是 1ms,那双写后变为 31ms,显然这是无奈承受的。
异步双写
异步双写,指的是同个 redis 命令写入两个机房,对本机房是同步写入的,对异地机房是异步写入的。
例如:用户发了一个逆风车乘客订单,该订单被路由到核心机房(HZUNIT),且发单胜利在业务上须要缓存订单数据到 redis 中,则该订单会先在核心机房(HZUNIT)写入一条缓存数据,同时会异步写入一条数据到异地的单元机房(SHUNIT)。
该计划同样存在以下几个问题:
- 保护老本
与同步双写计划问题统一。
- 回滚问题
大部分与同步双写计划问题统一,且因为异步,一致性更无奈保障。
- 提早
该计划因为异地机房是异步写入,因而提早问题影响简直没有。(如数据要求强统一,则不适宜用异步双写)
集群同步双写
集群同步双写,指的是同个 redis 命令写入两个机房,是同步执行的。然而双写是 redis 集群维度的,即配置了该 redis 集群为双写,那么整个 redis 集群的任意写入命令都会进行同步双写操作。
该计划同样存在以下几个问题:
- 保护老本
该计划的保护老本只在于新增 redis 集群后须要配置一下,或者告诉中间件,保护老本较低。
- 回滚问题
与同步双写问题统一。
- 提早
与同步双写问题统一。
集群异步双写
集群异步双写,指的是同个 redis 命令写入两个机房,对本机房是同步写入的,对异地机房是异步写入的。然而双写是 redis 集群维度的,即配置了该 redis 集群为双写,那么整个 redis 集群的任意写入命令都会进行异步双写操作。
该计划同样存在以下几个问题:
- 保护老本
该计划的保护老本只在于新增 redis 集群后须要新增配置一下,或者告诉中间件,保护老本较低。
- 回滚问题
与异步双写问题统一。
- 提早
与异步双写问题统一。
论断
从以上阐明中能够看出 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 双向同步残缺计划,如下所示。
(本文作者:柳健强)
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者应用。非商业目标转载或应用本文内容,敬请注明“内容转载自哈啰技术团队”。