原生Redis跨数据中心双向同步优化实践

一、背景公司基于业务发展以及战略部署,需要实现在多个数据中心单元化部署,一方面可以实现多数据中心容灾,另外可以提升用户请求访问速度 。需要保证多数据中心容灾或者实现用户就近访问的话,需要各个数据中心拥有一致的全量数据,如果真正实现用户就近读写,也就是实现真正的业务异地多活 , 数据同步是异地多活的基础,这就需要多数据中心间数据能够双向同步 。
二、原生redis遇到的问题1、不支持双主同步原生redis并没有提供跨机房的主主同步机制 , 仅支持主从同步;如果仅利用redis的主从数据同步机制,只能将主节点与从节点部署在不同的机房 。当主节点所在机房出现故障时,从节点可以升级为主节点,应用可以持续对外提供服务 。但这种模式下,若要写数据,则只能通过主节点写,异地机房无法实现就近写入,所以不能做到真正的异地多活,只能做到备份容灾 。而且机房故障切换时,需要运维手动介入 。
因此,想要实现主主同步机制,需要同步工具模拟从节点方式 , 将本地机房中数据同步到其他机房,其他机房亦如此 。同时,使用同步工具实现跨数据中心数据同步,会遇到以下一些问题 。
(1)数据回环数据回环的意思是,A机房就近写入的数据,通过同步工具同步到B机房后,然后又通过B机房同步工具同步回A机房了 。所以在同步的过程中需要识别本地就近写入的数据还是其他数据中心同步过来的数据,只有本地就近写入的数据需要同步到其他数据中心 。
(2)幂等性同步过程中的命令可能因断点续传等原因导致重复同步了,此时需要保证同一命令多次执行保证幂等 。
(3)多写冲突以双写冲突为例 , 如下图所示:

原生Redis跨数据中心双向同步优化实践

文章插图
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文件 , 所以对主节点性能影响比较大,我们应该尽量减少全量同步的次数 。

推荐阅读