异地多活的数据一致性简单设计
概述 异地多活,往往意味着夸机房读写延迟的增加,也就增加了读写失败的可能性,最终导致数据的延迟更长,同时,这种场景下也会影响在线系统的性能和时延。本文从数据低延迟、开发复杂度上考虑,总结了两种处理方式,分别是双写和双读,从而保证数据的最终一致性。对于异地多活的业务场景,往往也不需要保证强一致性,允许短时间的不一致性。例如对于外卖软件,在南方点了外卖,然后到北方出差,常规上也不可能短时间内(分钟级别)从南方飞到北方。 再举个极端的例子,我们所看到星空中的行星的光,也很多是很多年前从很远的宇宙发射过来的,你不可能在同一时间看到光。 再者,实现真正的异地多活(强一致,多节点写入)是个极其复杂的工程,需要底层数据库、业务上的支持,对于一致性要求没那么高的业务场景,我们可以选择稍微简单的方案实现。 双写 写入本机房后,还需要写入异地机房,同步方式可以有:
- 数据库本身支持了同步:这种情况往往需要增加第三方组件,例如阿里的otter组件支持了mysql的同步。业务代码只需要写一次,底层数据同步交给数据库,会出现短时间的两个机房数据不一致的情况,业务上往往能够接受。但极端情况也会出现异地对同一份数据进行写,导致写写冲突,这时候需要业务介入做抉择(常见的方式如订单系统后期的对账补偿)。如果对于数据库的操作是数据库级别的原子性操作,例如redis的incr命令,就可以避免写写冲突。
文章图片
文章图片
- 数据库本身不支持同步:这种情况需要业务代码双写,跨区写的失败率会变高,采取重试,但会加剧数据的延迟(如果延迟不高,也能接收)。同时,如果是在线系统,往往并发量比较大,所以还是得在业务层面加MQ,如加入第三方的MQ(如kafka),实现上就得实现producer和consumer逻辑,而且还需要额外对kafka进行维护,这也带来了系统的复杂性。简单做法是采用内存队列,直接写入内存队列,通过定时器定期消费内存队列数据。如果数据支持批量接口,采用批量写数据库,读的时候,只读本机房数据。这种方式,也会有问题:因为是内存队列,如果服务重启,还没来得及消费的数据会丢失;或者是多次写失败重试后依然失败,也会导致数据丢失(其实这种情况需要发出告警,人工介入了)。如果业务允许有一定的数据丢失的情况,但对时效性要求较高的,采用这种方式比较合理。
- 全量同步:实现简单,但只适合于数据量少,但如果数据太多,同步也会很慢,加大了延迟,有可能打满网卡导致影响整体服务环境。
- 增量同步:实现复杂,需要设置个游标,类似kafka的offset,记录本次同步到的点,如何标准游标是准确的呢?需要保证不多也不少,例如如果游标粒度设置的太大,同一个游标可能对应多个数据,这样可能导致捞过来的数据比原有的多。所以这种情况对游标的选择就比较重要了。
文章图片
-
- 异步:对于双写方案,采用异步写;对于双读方案,采用异步读更新(这种情况除非是增量更新,否则如果全量更新,也会导致性能和延迟的增加;但全量更新就要求数据不能太多,而且如果数据库是redis或者其他kv,需要提前知道对应的key)。
- 双队列缓存:双buffer是为了提高并发度,对于双写,可以只需要对内存中的写进行互斥,但对于数据的更新不会互斥,因为两者个用不同队列;对于双读,数据结构可以参考我之前发的doublybufferdata数据结构。对于队列,其实是传统MQ的替代,只是如果引入MQ,则需要带来额外的维护成本,所以可以简单的实现,用set或者map都可以。
推荐阅读
- python|吴恩达机器学习笔记——踩了最多坑的Pycharm 导入numpy,pandas和matplotlib
- python|Python dataframe 多条件筛选/过滤数据的方法及函数isin,query,contains,loc的使用介绍
- js 数组去重的方式
- C#|C# CM框架实现多页面管理的实例代码
- 多任务思想与聚类联邦学习
- 【SpringBoot学习笔记】配置多数据源
- R语言指数加权模型EWMA预测股市多变量波动率时间序列
- MyBatis中一对多的xml配置方式(嵌套查询/嵌套结果)
- 浅谈spring使用策略模式实现多种场景登录方式
- 阿里腾讯大裁员!最高裁员量达30%,多个业务线已初步敲定裁员名单...