learn-tech/专栏/高并发系统实战课/04同城双活:如何实现机房之间的数据同步?.md
2024-10-16 11:38:31 +08:00

12 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        04 同城双活:如何实现机房之间的数据同步?
                        你好,我是徐长龙。今天我们来看看用户中心改造的另一个阶段:构建多机房。

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

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

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

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

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

核心数据中心设计

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

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

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

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

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

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

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

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

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

这里我给你一个常见的网络延迟参考:

同机房服务器0.1 ms 同城服务器100公里以内 1ms10倍 同机房) 北京到上海: 38ms380倍 同机房) 北京到广州53ms530倍 同机房)

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

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

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

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

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

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

跨机房同步神器Otter

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

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

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

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

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

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

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

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

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

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

具体表格如下所示:

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

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

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

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

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

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

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

总结

机房之间的数据同步一直是行业里的痛因为高昂的实现代价如果不能做到双活总是会有一个1:1机器数量的机房在空跑而且发生故障时没有人能保证冷备机房可以马上对外服务。

但是双活模式的维护成本也不低机房之间的数据同步常常会因为网络延迟或数据冲突而停止最终导致两个机房的数据不一致。好在Otter对数据同步做了很多措施能在大多数情况下保证数据的完整性并且降低了同城双活的实现难度。

即使如此在业务的运转过程中我们仍然需要人工梳理业务避免多个机房同时修改同一条数据。对此我们可以通过HttpDNS调度让一个用户在某一段时间内只在一个机房内活跃这样可以降低数据冲突的情况。

而对于修改频繁、争抢较高的服务,一般都会在机房本地做整体事务执行,杜绝跨机房同时修改导致同步错误的发生。

相信未来随着行业的发展,多活机房的同步会有更好的解决方案,今天的内容就讲到这里,期待你在留言区与我互动交流!

思考题

如果Otter同步的链路是环形的那么如何保证数据不会一直循环同步下去