共计 5089 个字符,预计需要花费 13 分钟才能阅读完成。
一、背景
公司基于业务倒退以及战略部署,须要实现在多个数据中心单元化部署,一方面能够实现多数据中心容灾,另外能够晋升用户申请访问速度。须要保障多数据中心容灾或者实现用户就近拜访的话,须要各个数据中心领有统一的全量数据,如果真正实现用户就近读写,也就是实现真正的业务异地多活,数据同步是异地多活的根底,这就须要多数据中心间数据可能双向同步。
二、原生 redis 遇到的问题
1、不反对双主同步
原生 redis 并没有提供跨机房的主主同步机制,仅反对主从同步;如果仅利用 redis 的主从数据同步机制,只能将主节点与从节点部署在不同的机房。当主节点所在机房呈现故障时,从节点能够降级为主节点,利用能够继续对外提供服务。但这种模式下,若要写数据,则只能通过主节点写,异地机房无奈实现就近写入,所以不能做到真正的异地多活,只能做到备份容灾。而且机房故障切换时,须要运维手动染指。
因而,想要实现主主同步机制,须要同步工具模仿从节点形式,将本地机房中数据同步到其余机房,其余机房亦如此。同时,应用同步工具实现跨数据中心数据同步,会遇到以下一些问题。
(1)数据回环
数据回环的意思是,A 机房就近写入的数据,通过同步工具同步到 B 机房后,而后又通过 B 机房同步工具同步回 A 机房了。所以在同步的过程中须要辨认本地就近写入的数据还是其余数据中心同步过去的数据,只有本地就近写入的数据须要同步到其余数据中心。
(2)幂等性
同步过程中的命令可能因断点续传等起因导致反复同步了,此时须要保障同一命令屡次执行保障幂等。
(3)多写抵触
以双写抵触为例,如下图所示:
DC1 写入 set a 1,同时 DC2 写入 set a 2,当这两条命令通过同步工具同步到对方机房时,导致最终 DC1 中保留的 a 为 2,DC2 中保留的 a 为 1,也就是说两个机房最终数据不统一。
2、断点续传
针对刹时的断开重连、从节点重启等场景,redis 为了进步该场景下的主从同步效率,在主节点中减少了环形复制缓冲区,主节点往从节点写数据的同时也往复制缓冲区中也写入一份数据,当从节点断开重连时,则只须要通过复制缓冲区把断开期间新增的增量数据发送给从节点即可,防止了全量同步,晋升了这些场景下的同步效率。
然而,该内存复制缓冲区一般来说不会太大,生产目前默认设置为 64M,跨数据中心同步场景下,网络环境简单,断线的频率和时长可能比同机房更频繁和更长;同时,跨数据中心同步数据也是为了机房级故障容灾,所以要求可能反对更长时间的断点续传,有限增大内存复制缓冲区大小显然不是一个好主见。
上面来看看咱们反对 redis 跨数据中心同步的优化工作。
三、redis 节点革新
为了反对异地多活场景,咱们对原生 redis 代码进行了优化革新,次要包含以下几个方面:
1、对 RESP 协定进行扩大
为了反对更高效的断点续传,以及为了解决数据回环问题,咱们在 redis 主节点中对每条须要同步给从节点的命令(大部分为写命令)减少了 id,并且扩大了 RESP 协定,在每条相干命令的头部减少了形如 #{id}\r\n 模式的协定。
本地业务客户端写入的数据仍然遵循原生 RESP 协定,主节点执行完命令后,同步到从节点的写命令在同步前会进行协定扩大,减少头部 id 协定;非本地业务客户端(即来自其余数据中心同步)写入的数据均应用扩大的 RESP 协定。
2、写命令实时写日志
为了反对更长时间的断点续传,容忍长时间的机房级故障,本地业务客户端写入的写命令在进行协定扩大后,会程序写入日志文件,同时生成对应的索引文件;为了缩小日志文件大小,以及进步通过日志文件断点续传的效率,来自其余数据中心同步过去的数据不写入日志文件中。
3、同步流程革新
原生 redis 数据同步分为全量同步和局部同步,并且每个主节点有一个内存环形复制缓冲区;首次同步应用全量同步,断点续传时应用局部同步,即先尝试从主节点环形复制缓冲区中进行同步,同步胜利的话则同步完缓冲区中的数据后即可进行增量数据同步,如果不胜利,则依然须要先进行全量同步再增量同步。
因为全量同步须要生成一个子过程,并且在子过程中生成一个 RDB 文件,所以对主节点性能影响比拟大,咱们应该尽量减少全量同步的次数。
为了缩小全量同步的次数,咱们对 redis 同步流程进行革新,当局部同步中无奈应用环形复制缓冲区实现同步时,减少先尝试应用日志 rlog 进行同步,如果同步胜利,则同步完日志中数据后即可进行增量同步,否则须要先进行全量同步。
四、rLog 日志设计
分为索引文件与日志文件,均采纳程序写的形式,进步性能,经测试与原生 redis 开启 aof 长久化性能统一;然而 rlog 会定期删除,原生 redis 为了避免 aof 文件有限收缩,会定期通过子过程执行 aof 文件重写,这个对主节点性能比拟大,所以本质上 rlog 对 redis 的性能绝对于 aof 会更小。
索引文件和日志文件文件名均为文件中保留的第一条命令的 id。
索引文件与日志文件均先写内存缓冲区,而后批量写入操作系统缓冲区,并每秒定期刷新操作系统缓冲区真正落入磁盘文件中。相比拟于 aof 文件缓冲区,咱们对 rlog 缓冲区进行了预调配优化,达到晋升性能目标。
1、索引文件格式
索引文件格式如下所示,每条命令对应的索引数据蕴含三局部:
pos:该条命令第一个字节在对应的日志文件中绝对于该日志文件起始地位的偏移
len:该条命令的长度
offset:该条命令第一个字节在主节点复制缓冲区中累积的偏移
2、日志文件拆分
为了避免单个文件有限收缩,redis 在写文件时会定期对文件进行拆分,拆分根据两个维度,别离是文件大小和工夫。
默认拆分阈值别离为,当日志文件大小达到 128M 或者每隔一小时同时并且日志条目数大于 10w 时,写新的日志文件和索引文件。
在每次循环解决中,当内存缓冲区的数据全副写入文件时,判断是否满足日志文件拆分条件,如果满足,加上一个日志文件拆分标记,下一次循环解决中,将内存缓冲区数据写入文件之前,先敞开以后的索引文件和日志,同时新建索引文件和日志文件。
3、日志文件删除
为了避免日志文件数量有限增长并且耗费磁盘存储空间,以及因为未做日志重写、通过过多的文件进行断点续传效率低下、意义不大,所以 redis 定期对日志文件和相应的索引文件进行删除。
默认日志文件最多保留一天,redis 定期删除一天以前的日志文件和索引文件,也就是最多容忍一天工夫的机房级故障,否则须要进行机房间数据全量同步。
在断点续传时,如果须要从日志文件中同步数据,在同步开始前会长期禁止日志文件删除逻辑,待同步实现后恢复正常,避免出现在同步的数据被删除的状况。
五、redis 数据同步
1、断点续传
如前所述,为了容忍更长时间的机房级故障,进步跨数据中心容灾能力,提升机房间故障复原效率,咱们对 redis 同步流程进行革新,当局部同步中无奈应用环形复制缓冲区实现同步时,减少先尝试应用日志 rlog 进行同步,流程图如下所示:
首先,同步工具连贯上主节点后,除了发送认证外,须要先通过 replconf capa 命令告知主节点具备通过 rlog 断点续传的能力。
从节点先发送 psync runId offset,如果是第一次启动,则先发送 psync ? -1,主节点会返回一个 runId 和 offset
如果可能通过复制缓冲区同步,主节点给从节点返回 +CONTINUE runId
如果不可能通过复制缓冲区同步,主节点给从节点返回 +LPSYNC
如果从节点收到 +CONTINUE,则持续接管增量数据即可,并持续更新 offset 和命令 id
如果从节点收到 +LPSYNC,则从节点持续给主节点发送 LPSYNC runId id
主节点收到 LPSYNC 命令后,如果可能通过 rlog 持续同步数据,则给从节点发送 +LCONTINUE runId;
从节点收到 +LCONTINUE 后,能够把 offset 设置为 LONG_LONG_MIN,或者后续数据不更新 offset;持续接管通过 rlog 同步的增量数据即可;
通过 rlog 同步的增量数据传输结束后,主节点会给从节点发送 lcommit offset 命令;
从节点在解析数据的过程中,收到 lcommit 命令时,更新本地 offset,后续的增量数据持续减少 offset,同时 lcommit 命令无需同步到对端(通过 id<0 辨认即可,所有 id<0 的命令均无需同步到对端)
如果不能,此时主节点给从节点返回 +FULLRESYNC runId offset;后续进行全量同步;
2、幂等性
迁徙工具为了进步性能,并不是实时往 zk 保留同步偏移 offset 和 id,而是定期(默认每秒)向 zk 进行同步,所以当断点续传时,迁徙工具从 zk 获取断线前同步的偏移,尝试向主节点持续同步数据,这两头可能会有局部数据反复发送,所以为了保证数据一致性,须要保障命令屡次执行具备幂等性。
为了保障 redis 命令具备幂等性,对 redis 中局部非幂等性命令进行了革新,具体设计革新的命令如下所示:
注:list 类型命令暂未革新,不具备幂等性
3、数据回环解决
数据回环次要是指,当同步工具从 A 机房 redis 读取的数据,通过 MQ 同步到 B 机房写入后,B 机房的同步工具又获取到,再次同步到 A 机房,导致数据循环复制问题。
对于同步到从节点以及迁徙工具的数据,会在头部增加 id 字段,针对不同起源的数据或者无需同步到远端的数据通过 id 来标识辨别;本地业务客户端写入的数据须要同步到远端数据中心,调配 id 大于 0;来源于其余数据中心的数据调配 id 小于 0;一些仅用于主从心跳交互的命令数据调配 id 也小于 0。
同步工具解析完数据后,过滤掉 id 小于 0 的命令,只须要向远端写入 id 大于 0 的数据,即本地业务客户端写入的数据。来源于其余数据中心的数据均不回写到远端数据中心。
4、过期与淘汰数据
目前过期与淘汰均由各数据中心 redis 节点分别独立解决,由过期与淘汰删除的数据不进行同步;即由过期与淘汰产生的删除命令其 id 调配为小于 0,并由同步工具过滤掉。
(1)同步产生的问题
为什么不同步过来?因为在内存中 hash 表外面保留的数据没有标记数据中心起源,过期与淘汰的数据有可能来自于其余数据中心,如果来自于其余数据中心的数据被过期或淘汰并且又同步到远端其余数据中心,就会呈现数据双写抵触的场景。双写抵触可能会导致数据不统一。
(2)不同步产生的问题
对于过期数据来说,不同步删除可能会导致不同数据中心数据显示不统一,然而肯定会最终统一,且不会呈现脏读;
对于淘汰数据来说,目前的不同步删除的计划,如果呈现淘汰,会导致不同数据中心数据不统一;目前只有通过运维伎俩,比方短缺预调配、及时关注内存使用率告警,来躲避淘汰数据景象产生。
5、数据迁徙
在 redis 集群模式中,个别是在产生横向扩容减少集群主节点数时,须要进行槽以及数据的迁徙。
redis 集群中数据迁徙以槽为维度进行迁徙,将槽中所有数据从源节点迁徙到指标节点,而后将槽号标记为由新的指标节点负责,同时每迁徙完一个 Key,会在源节点中进行删除,将 migrate 命令替换为 del 命令;同时迁徙数据是在源节点中给指标节点发送 restore 命令实现。
咱们数据迁徙的策略仍然是,各个数据中心独立的实现扩容与数据迁徙工作,迁徙过程产生的 del 和 restore 命令不进行跨数据中心同步;把替换后的 del 命令和发送给指标节点的 restore 命令都调配小于 0 的 id,于是同步过程中会由同步工具进行过滤掉。
六、redis 性能
经测试,redis 多活实例(默认开启 rlog 日志),绝对于原生 redis 实例(开启 aof 长久化)性能基本一致;如下图所示:
注:以上图表应用 redis benchmark 进行压测,压测时,客户端和服务端在同一个机器上
七、待优化项
1、多写抵触
多个数据中心同时写,key 抵触问题暂未解决。
后续解决方案为应用 CRDT 协定;CRDT(Conflict-Free Replicated Data Type)是各种根底数据结构最终统一算法的实践总结,能依据肯定的规定主动合并,解决抵触,达到强最终统一的成果。
目前解决方案为业务对写入不同机房的数据进行拆分,以保障不会呈现抵触。
2、list 类型幂等性
五种根本类型外面,list 类型大部分操作都是非幂等的,临时未做幂等性革新优化。不倡议应用或者业务本身保障应用 list 的数据操作幂等。
3、过期与淘汰数据一致性问题
正如前文所述,淘汰数据不进行跨数据中心同步会导致数据不统一,如果同步数据可能会呈现同一个 Key 多写抵触,也可能呈现数据不统一状况。
目前解决方案为业务尽量正当提前预估所需内存容量、短缺预调配、及时关注内存使用率告警,来躲避淘汰数据景象产生。
作者:罗明