在做同城多活方案中如何实现机房之间的数据同步?

news/2025/2/4 1:34:59/文章来源:https://www.cnblogs.com/o-O-oO/p/18697924

一、前言

现在是数据的时代,如何发挥数据的价值,让系统具备更多的数据处理能力。如何完善响应的数据架构。多机房建设是其中的解决策略。

二、背景

在业务初期,考虑到投入成本,很多公司通常只用一个机房提供服务。但随着业务发展,流量不断增加,我们对服务的响应速度和可用性有了更高的要求,这时候我们就要开始考虑将服务分布在不同的地区来提供更好的服务,这是互联网公司在流量增长阶段的必经之路。

之前我所在的公司,流量连续三年不断增长。一次,机房对外网络突然断开,线上服务全部离线,网络供应商失联。因为没有备用机房,我们经过三天紧急协调,拉起新的线路才恢复了服务。这次事故影响很大,公司损失达千万元。

经过这次惨痛的教训,我们将服务迁移到了大机房,并决定在同城建设双机房提高可用性。这样当一个机房出现问题无法访问时,用户端可以通过 HttpDNS 接口快速切换到无故障机房。

为了保证在一个机房损坏的情况下,另外一个机房能直接接手流量,这两个机房的设备必须是 1:1 采购。但让其中一个机房长时间冷备不工作过于浪费,因此我们期望两个机房能同时对外提供服务,也就是实现同城双机房双活。

对此,我们碰到的一个关键问题就是,如何实现同城双活的机房数据库同步?

1、 核心数据中心设计

因为数据库的主从架构,全网必须只能有一个主库,所以我们只能有一个机房存放更新数据的主库,再由这个机房同步给其他备份机房。虽然机房之间有专线连接,但并不能保证网络完全稳定。如果网络出现故障,我们要想办法确保机房之间能在网络修复后快速恢复数据同步。

有人可能会说,直接采用分布式数据库不就得了。要知道改变现有服务体系,投入到分布式数据库的大流中需要相当长的时间,成本也非常高昂,对大部分公司来说是不切实际的。所以我们要看看怎么对现有系统进行改造,实现同城双活的机房数据库同步,这也是我们这节课的目标。

核心数据库中心方案是常见的实现方式,这种方案只适合相距不超过 50 公里的机房。

在这个方案中,数据库主库集中在一个机房,其他机房的数据库都是从库。当有数据修改请求时,核心机房的主库先完成修改,然后通过数据库主从同步把修改后的数据传给备份机房的从库。由于用户平时访问的信息都是从缓存中获取的,为了降低主从延迟,备份机房会把修改后的数据先更新到本地缓存。

与此同时,客户端会在本地记录下数据修改的最后时间戳(如果没有就取当前时间)。当客户端请求服务端时,服务端会自动对比缓存中对应数据的更新时间,是否小于客户端本地记录的修改时间。

如果缓存更新时间小于客户端内的修改时间,服务端会触发同步指令尝试在从库中查找最新数据;如果没有找到,就把从主库获取的最新数据放到被访问机房的缓存中。这种方式可以避免机房之间用户数据更新不及时的问题。

除此之外,客户端还会通过请求调度接口,让一个用户在短期内只访问一个机房,防止用户在多机房间来回切换的过程中,数据在两个机房同时修改引发更新合并冲突。

总体来看,这是一个相对简单的设计,但缺点也很多。比如如果核心机房离线,其他机房就无法更新,故障期间需要人工切换各个 proxy 内的主从库配置才能恢复服务,并且在故障过后还需要人工介入恢复主从同步。

此外,因为主从同步延迟较大,业务中刚更新的数据要延迟一段时间,才能在备用机房查到,这会导致我们业务需要人工兼顾这种情况,整体实现十分不便。这里我给你一个常见的网络延迟参考:

  • 同机房服务器:0.1 ms

  • 同城服务器(100 公里以内) :1ms(10 倍 同机房)

  • 北京到上海: 38ms(380 倍 同机房)

  • 北京到广州:53ms(530 倍 同机房)

注意,上面只是一次 RTT 请求,而机房间的同步是多次顺序地叠加请求。如果要大规模更新数据,主从库的同步延迟还会加大,所以这种双活机房的数据量不能太大,并且业务不能频繁更新数据。

此外还要注意,如果服务有强一致性的要求,所有操作都必须在主库“远程执行”,那么这些操作也会加大主从同步延迟。

除了以上问题外,双机房之间的专线还会偶发故障。我碰到过机房之间专线断开两小时的情况,期间只能临时用公网保持同步,但公网同步十分不稳定,网络延迟一直在10ms~500ms 之间波动,主从延迟达到了 1 分钟以上。好在用户中心服务主要以长期缓存的方式存储数据,业务的主要流程没有出现太大问题,只是用户修改信息太慢了。

有时候,双机房还会偶发主从同步断开,对此建议做告警处理。一旦出现这种情况,就发送通知到故障警报群,由 DBA 人工修复处理。

另外,我还碰到过主从不同步期间,有用户注册自增 ID 出现重复,导致主键冲突这种情况。这里我推荐将自增 ID 更换为“由 SnowFlake 算法计算出的 ID”,这样可以减少机房不同步导致的主键冲突问题。

可以看到,核心数据库的中心方案虽然实现了同城双机房双活,但是人力投入很大。DBA 需要手动维护同步,主从同步断开后恢复起来也十分麻烦,耗时耗力,而且研发人员需要时刻关注主从不同步的情况,整体维护起来十分不便,所以我在这里推荐另外一个解决方案:数据库同步工具 Otter。

2、 跨机房同步神器:Otter

Otter 是阿里开发的数据库同步工具,它可以快速实现跨机房、跨城市、跨国家的数据同步。如下图所示,其核心实现是通过 Canal 监控主库 MySQL 的 Row binlog,将数据更新并行同步给其他机房的 MySQL。

因为我们要实现同城双机房双活,所以这里我们用 Otter 来实现同城双主(注意:双主不通用,不推荐一致要求高的业务使用),这样双活机房可以双向同步:

如上图,每个机房内都有自己的主库和从库,缓存可以是跨机房主从,也可以是本地主从,这取决于业务形态。Otter 通过 Canal 将机房内主库的数据变更同步到 Otter Node内,然后经由 Otter 的 SETL 整理后,再同步到对面机房的 Node 节点中,从而实现双机房之间的数据同步。

讲到这里不得不说一下,Otter 是怎么解决两个机房同时修改同一条数据所造成的冲突的。

在 Otter 中数据冲突有两种:一种是行冲突,另一种是字段冲突。行冲突可以通过对比数据修改时间来解决,或者是在冲突时回源查询覆盖目标库;对于字段冲突,我们可以根据修改时间覆盖或把多个修改动作合并,比如 a 机房-1,b 机房-1,合并后就是-2,以此来实现数据的最终一致性。

但是请注意,这种合并方式并不适合库存一类的数据管理,因为这样会出现超卖现象。如果有类似需求,建议用长期缓存解决。

Otter 不仅能支持双主机房,还可以支持多机房同步,比如星形双向同步、级联同步(如下图)等。但是这几种方式并不实用,因为排查问题比较困难,而且当唯一决策库出现问题时,恢复起来很麻烦。所以若非必要,不推荐用这类复杂的结构。

另外,我还要强调一点,我们讲的双活双向同步方案只适合同城。一般来说,50~100公里以内的机房同步都属于同城内。

超过这个距离的话,建议只做数据同步备份,因为同步延迟过高,业务需要在每一步关注延迟的代价过大。如果我们的业务对一致性的要求极高,那么建议在设计时,把这种一致性要求限制在同一个机房内,其他数据库只用于保存结果状态。

那为什么机房间的距离必须是 100 公里以内呢?你看看 Otter 对于不同距离的同步性能和延迟参考,应该就能理解了。

具体表格如下所示:

为了提高跨机房数据同步的效率,Otter 对用于主从同步的操作日志做了合并,把同一条数据的多次修改合并成了一条日志,同时对网络传输和同步策略做了滑窗并行优化。

对比 MySQL 的同步,Otter 有 5 倍的性能提升。通过上面的表格可以看到,通过 Otter实现的数据同步并发性能好、延迟低,只要我们将用户一段时间内的请求都控制在一个机房内不频繁切换,那么相同数据的修改冲突就会少很多。

用 Otter 实现双向同步时,我们的业务不需要做太多改造就能适应双主双活机房。具体来说,业务只需要操作本地主库,把“自增主键”换成“snowflake 算法生成的主键”、“唯一索引互斥”换成“分布式互斥锁”,即可满足大部分需求。

但是要注意,采用同城双活双向同步方案时,数据更新不能过于频繁,否则会出现更大的同步延迟。当业务操作的数据量不大时,才会有更好的效果。

说到这里,我们再讲一讲 Otter 的故障切换。目前 Otter 提供了简单的主从故障切换功能,在 Manager 中点击“切换”,即可实现 Canal 和数据库的主从同步方式切换。如果是同城双活,那关于数据库操作的原有代码我们不需要做更改,因为这个同步是双向的。

当一个机房出现故障时,先将故障机房的用户流量引到正常运转的机房,待故障修复后再恢复数据同步即可,不用切换业务代码的 MySQL 主从库 IP。切记,如果双活机房有一个出现故障了,其他城市的机房只能用于备份或临时独立运行,不要跨城市做双活,因为同步延迟过高会导致业务数据损坏的后果。

最后,我再啰嗦一下使用 Otter 的注意事项:第一,为了保证数据的完整性,变更表结构时,我们一般会先从从库修改表结构,因此在设置 Otter 同步时,建议将 pipeline同步设置为忽略 DDL 同步错误;第二,数据库表新增字段时,只能在表结尾新增,不能删除老字段,并且建议先把新增字段同步到目标库,然后再同步到主库,因为只有这样才不会丢数据;第三,双向同步的表在新增字段时不要有默认值,同时 Otter 不支持没有主键的表同步。

原创 细节探索者 疯狂打码中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/878467.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在 Ubuntu 22.04 上运行 Filebeat 7.10.2

环境 操作系统:阿里云 Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-83-generic x86_64) 软件版本:Filebeat 7.10.2 用户:root 运行下载从这里下载 filebeat 7.10.2。配置简单配置一下 filebeat.yml,从标准输入采集,写入到标准输出 : filebeat.inputs: - type: stdinoutput.con…

Dify AWS:0代码搭建「AI翻译中台」

0 前言 基于Dify现有能力,已能对不少业务场景提供帮助,但对一些特定诉求,还要借助其扩展机制,本文利用翻译场景举例详细说明。 1 翻译场景复杂性分析 翻译是从简单到复杂各级都存在的场景,比较简单的翻译可能一句简单 Prompt,但对复杂、效果要求较高翻译场景,可能需要一…

25.2.3小记

抽象(abstract) 1.抽象函数不需要大括号 2.抽象的函数只能包括在抽象的类中(或者说只要一个类里有一个抽象函数,那整个类就是抽象的,因为若非抽象则定义对应类的变量之后本应该可以调用name.work类似的函数,但抽象函数因为没有body所以无法调用)其中定义的变量可以管理任…

0x80070035错误怎么解决?Win11/Win10访问NAS smba共享文件夹提示无法找到路径[Path not found]

1. 问题 Win11/Win10访问NAS smba共享文件夹提示无法找到路径[Path not found]2. 解决办法 2.1 设置网络专用网络2.2 设置高级共享2.3 设置组策略-启用-不安全的来宾登录2.4 设置组策略-Microsoft网络客户端:对通信进行数字签名(始终)-改为禁用3. 验证 现在再去访问,你会发现…

DeepSeek,你是懂.NET的!

这两天火爆出圈的话题,除了过年,那一定是DeepSeek!你是否也被刷屏了?DeepSeek 是什么DeepSeek是一款由国内人工智能公司研发的大型语言模型,拥有强大的自然语言处理能力,能够理解并回答问题,还能辅助写代码、整理资料和解决复杂的数学问题。与OpenAI开发的ChatGPT相比,De…

Welcome

This is your new vault. Make a note of something, [[create a link]], or try the Importer! When youre ready, delete this note and make the vault your own.

遭受大量境外网络攻击,郭盛华公开发声支持DeepSeek

近日,据央视新闻报道,近期DeepSeek线上服务受到大规模恶意攻击。DeepSeek这次受到的网络攻击,IP地址都在美国。 世界级的电脑天才,国际知名网络安全组织东方联盟创始人《郭盛华》罕见公开发声:“支持DeepSeek!”,他还透露:“参加了DeepSeek保卫战,坚决捍卫国产AI技术的…

E96 Tarjan缩点+树上背包 P2515 [HAOI2010] 软件安装

视频链接: 参考1:D14【模板】强连通分量 Tarjan 算法 - 董晓 - 博客园 参考2:E76 树上背包 P1064 [NOIP2006 提高组] 金明的预算方案 - 董晓 - 博客园 P2515 [HAOI2010] 软件安装 - 洛谷 | 计算机科学教育新生态// Tarjan缩点+树上背包 O(n*m) #include <iostream> …

verilog 编写猫狗过河实验

使用verilog语言在FPGA上完成猫狗过河游戏开发。源代码地址:https://github.com/penggeon/catanddog效果演示见: https://www.bilibili.com/video/BV1n24y147S1警告: 仅给出了实验过程的源代码,需手动复制粘贴至自己项目中。 主模块默认名为 catanddog,若需使用请自行修改。…

7.sql注入.md

注入的本质:sql:把用户输入的数据当做sql代码执行XSS:用户输入的数据当做前端代码执行XXE:用户输入的数据当做XML代码执行代码执行:用户输入的数据当做后端代码执行命令执行:用户输入的数据当做系统命令执行sql关键点:1.用户能够控制输入----存在注入点2.原本程序要执行…