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双主模式配置方案及实现方法 Redis双主模式是一种高级的复制架构设计,允许两台Redis实例互为从关系。这种方式能够提供更高的可用性和更强的数据一致性保障[^3]。 #### 1. 基本原理 在双主模式下,每台Redis服务器既是节点也是从节点。这意味着它们可以互相接受写操作,并通过特定机制保持数据的一致性。为了支持这一特性,通常需要引入额外的逻辑来防止循环复制问题以及冲突解决策略[^5]。 #### 2. 实现步骤 以下是构建Redis双主模式要过程: - **建立初始连接** 使用`SLAVEOF`命令让一台Redis成为另一台的从属节点。例如,在Redis A上运行 `SLAVEOF B_IP B_PORT` 将使A成为B的从库;同样地,在B上执行相反的操作即可完成向绑定。 - **启用持久化选项** 推荐开启RDB快照或者AOF日志记录以便于灾难恢复时能快速重建状态。这一步骤对于维持长时间稳定的服务至关重要[^4]。 - **处理网络分区情况下的冲突** 当发生网路分割现象时(即两个站点之间暂时失去通讯),可能会造成方都修改相同key的情况。为此需定义明确规则决定最终保留哪一方更改结果。一种常见做法是在应用层面增加版本号控制或是时间戳比较机制[^2]。 - **脚本自动化管理工具编写** 创建shell脚本来简化日常运维工作比如启动停止服务、监控健康状况等。下面给出一个简单的例子用于启停Linux环境中的Redis进程: ```bash #!/bin/bash # init file for redis # chkconfig: - 80 12 # description: redis daemon PROCESS_NAME="redis" CONFIG_FILE="/path/to/your/config/file.conf" PID_FILE="/var/run/${PROCESS_NAME}.pid" case "$1" in start) echo "Starting $PROCESS_NAME..." /usr/bin/$PROCESS_NAME-server ${CONFIG_FILE} ;; stop) if [ ! -f "${PID_FILE}" ]; then echo "No pid found, process may not be running." else PID=$(cat ${PID_FILE}) kill -TERM ${PID} while [ -e "/proc/${PID}" ]; do sleep 1; done rm -rf ${PID_FILE} echo "$PROCESS_NAME stopped successfully." fi ;; restart|force-reload) $0 stop && $0 start ;; *) echo $"Usage: $0 {start|stop|restart|force-reload}" exit 1 esac ``` 请注意以上仅为示范用途的实际部署应考虑更多因素如安全性设置等等[^4]。 #### 3. 注意事项 尽管结构提供了诸多便利但也伴随着一定风险要包括但不限于以下几点: - 数据不一致的可能性增大; - 需要更多的资源消耗来进行同步活动; - 复杂度上升增加了调试难度[^3]。 综上所述,合理规划并实施有效的解决方案才能充分发挥其优势同时规避潜在隐患。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值