Redis 双主实现

2024-06-02 13:58
文章标签 实现 redis 双主

本文主要是介绍Redis 双主实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

redis双主设计

背景

目前redis仅支持主从复制模式,可以支持在线备份、读写分离等功能,实际应用中通常通过sentinel服务做主从切换的管理,这增加了管理的复杂度和维护成本,基于此360基础架构组联合DBA从redis内部实现了双主功能。

主从复制介绍

redis支持树形的主从异步复制,并具有非阻塞、部分同步等特性,下面简单介绍下其实现原理以及目前redis主从复制模式在故障发生时的数据丢失情况。

实现原理

数据同步

slave开始连接到master时需要同步master已有的数据,具体过程如下图所示,主要有以下几个步骤:

  1. slave向master发送PSYNC命令,将缓存的master的runid和reploff发送给master。

  2. master根据slave发送的reploff判断需要开始同步的数据是否在当前缓冲区(积压空间)中,如果在的话完成和slave的连接建立并向slave发送CONTINUE回复标志,开始发送积压空间中的数据;否则,向slave发送FULLSYNC标志并开启子进程dump当前时刻的快照数据到本地rdb文件。

  3. slave接收psync命令的返回值,并判断是CONTINUE还是FULLSYNC,如果是CONTINUE则完成连接过程准备接收master数据;否则,触发接收master dump的rdb文件事件,等待master发送rdb文件。

  4. master将快照数据dump到本地文件后,向slave发送rdb文件

  5. slave接收完master发送的rdb文件后,清空本地库并重新加载接收的rdb文件,由于这时的数据变化较大,如果开启了aof选项则还需进行aof rewrite操作

  6. 开始传播master积压空间中的新数据。

数据传播

master实例会维护其slave实例列表,当有更改操作发生时,其会通过连接建立时创建的socket向所有slave实例发送操作命令进行数据传播,同时为了防止故障恢复slave重新连接master时每次都进行全量同步,master实例会内部维护一个缓冲区(积压空间)来缓存部分slave命令,master数据传播的具体过程为:

  1. 写入本地库

  2. 写入积压空间

  3. 触发写入slave实例事件,进行异步传播;如果此刻正在进行数据同步操作则将命令写入缓冲区,待同步操作完成后再触发向slave实例写入事件。

数据丢失

redis主从模式并不能保证数据的100%完整,在网络故障、主库宕机等情况下提升slave为master时可能会丢失部分数据,如下图所示,假如在某个时刻master的数据还未完全传播给slave时,master宕机等情况发生并将slave提升为master,这时原来master未传播的一部分数据将丢失。

双主复制设计

前提假设

由于时间、精力等因素,目前我们在进行双主设计时结合如下实际项目需求进行了一些设计折衷,后续我们会继续完善相关设计。

  • 上层保证某个时间点只有一个master在写

  • 故障时允许丢失少量数据

总体设计

为避免额外维护成本,双主模块完全在redis内部实现,双主两个实例各自创建一个socket进行彼此通信;加入双主复制后不会影响原有的主从复制模式,但如下图所示,主从复制实例可以通过我们新增的doublemasterof命令转化为双主复制,双主复制实例也可以通过原有的slaveof命令转换为主从复制实例。

同步策略

redis双主实例网络故障恢复或重启等情况下会进行重新连接以同步彼此数据,主从模式下同步策略很简单,只需要从库同步主库数据即可,而双主模式下我们必须根据一定的策略来选出一个实例作为数据同步的对象,我们考虑到两种具体同步策略:基于数据量和基于时间戳的同步策略。

数据量同步策略

基于数据量的同步策略可以理解为数据量少的实例去同步数据量多的实例,这种同步策略在故障发生时数据已经全部传播到另外一个实例的情况下,故障恢复后可以保证数据完整性,但如下图所示,假如原来操作在双主A上执行,某一时刻双主A上的操作还未同步到双主B上发生了网络故障,上层会切到B上继续写入,当写入了图中红色所示大小的数据后网络故障恢复,这时进行数据同步时就有可能丢失A或B上的部分数据。

时间戳同步策略

对于基于时间戳的同步策略,我们会在redis内部维护双主实例的最近更新时间戳,故障恢复进行数据同步时时间戳较旧的实例会同步时间戳较新的实例;和基于数据量同步策略一样当故障发生时如果数据已经全部传播到另外实例则故障恢复后可以保证数据完整性,否则,如下图所示故障恢复后将丢失双主A实例的部分数据。

我们的选择

根据业务需求,我们需要保证最新写入的数据不会丢失,所以具体实现上我们选择了基于时间戳的同步策略。

同步实现

我们在redis原有的全同步,部分同步的基础上增加了ignore resync策略以实现双主同步,具体实现如下图所示,有以下几个步骤:

  1. 双主实例A链接到另外一个实例B时会通过发送2mastersync命令,该命令会将A实例最后的更新时间戳发送给B

  2. B实例判断A的最后更新时间戳是否比自己新,如果比自己新则直接将A标示为ONLINE并向A发送IGNORE runid reploff信息,A接收到IGNORE信息后记录下runid reploff,即可完成与B的同步;否则,则按照主从同步方式进行全同步或部分同步。

数据传播

对一个双主实例的更改操作,redis内部会通过双主实例建立连接时创建的socket异步传播给另一个双主实例,这里要解决的问题是要避免数据再次传播回来,具体实现上我们通过双主实例的runid进行判断,每个双主实例内部会维护其另外一个实例的runid信息,当有更改命令要执行时,我们会通过runid来判断该命令是否是其双主实例传播过来的,如果是将不再回传。

复制偏移量维护

redis主从实现机制上,当通信模块接收到主库的更改命令时会直接在从库上增加其复制偏移量来记录数据同步的位置,而对于双主实例我们知道为避免数据循环传播,双主实例A传播给双主实例B的命令不会回传过来,那么该如何维护其复制偏移量呢?设计上我们考虑了两种策略:

策略一

如下图所示,双主A向双主B传播一条数据后,B会回复A一个ACK length确认,A接收到确认信息后将自己的复制偏移量增加length。

策略二

如下图所示,双主A再向B传播数据之前自己主动增加复制偏移量,双主B不会向双主A回复确认信息

策略一对比策略二进一步保证了数据的完整性,但同时带来了一定的网络开销,两种策略都不能完全保证复制偏移量再网路故障下的正确性(策略一在ACK丢失的情况下无法保证复制偏移量正确),结合目前的需求为了不影响性能我们选择了策略二。

小结

结合目前项目的需求我们在redis内部实现了双主功能,但是也有一些需要改进的地方,欢迎大家提出意见,后面我们会不断完善,敬请关注!

这篇关于Redis 双主实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1024162

相关文章

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

Python使用python-can实现合并BLF文件

《Python使用python-can实现合并BLF文件》python-can库是Python生态中专注于CAN总线通信与数据处理的强大工具,本文将使用python-can为BLF文件合并提供高效灵活... 目录一、python-can 库:CAN 数据处理的利器二、BLF 文件合并核心代码解析1. 基础合

Python使用OpenCV实现获取视频时长的小工具

《Python使用OpenCV实现获取视频时长的小工具》在处理视频数据时,获取视频的时长是一项常见且基础的需求,本文将详细介绍如何使用Python和OpenCV获取视频时长,并对每一行代码进行深入解析... 目录一、代码实现二、代码解析1. 导入 OpenCV 库2. 定义获取视频时长的函数3. 打开视频文

golang版本升级如何实现

《golang版本升级如何实现》:本文主要介绍golang版本升级如何实现问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录golanwww.chinasem.cng版本升级linux上golang版本升级删除golang旧版本安装golang最新版本总结gola