本文来自OPPO互联网根底技术团队,转载请注名作者。同时欢送关注咱们的公众号:OPPO_tech,与你分享OPPO前沿互联网技术及流动。
1. 背景
随着业务的疾速倒退,对于很多公司来说,构建于单地区的技术体系架构,会面临诸如上面的多种问题:
- 基础设施的有限性限度了业务的可扩展性。业务扩充到单个数据中心撑不住,次要机房曾经不能再增加机器,但业务却一直要求扩大
- 机房、城市级别的故障灾祸,影响服务的可持续性。整个机房级别的故障时有发生,每次都引起业务的不可用,对公司的形象与支出都造成重大的影响。如2015年杭州某数据中心光缆被挖断,造成某产品业务几个小时的中断,导致重大的损失
- 跨地区的拜访,影响用户体验等。随着全球化脚步的迫近,试想用户客户端与服务一次交互,一次RTT起码须要10ms,若广州到北京网络提早个别为40ms,当用户客户端须要与服务器产生屡次交互时,对用户体验影响就很大,用户体验十分不敌对。
OPPO互联网业务倒退疾速,曾经扩大到寰球,随之带来的是技术上的多机房架构,对数据的寰球多机房同步在一致性、响应工夫、吞吐等方面有更高要求。为了解决以上问题带来的影响,本文将从异地多活底层,数据层面摸索。如何给下层业务提供一个平安、牢靠、稳固的数据环境,让下层业务能够集中精力专一业务开发,缩小对数据多活的关注。
1.1 面临的挑战
- 数据多地写入的抵触解决问题
- 远距离两地传输网络问题
- 同步过程中数据一致性问题
- 数据同步的幂等性问题
- 作为同步核心,对于数据复用问题
基于上述挑战,调研比照相干的几个支流开源产品,均有其对应的优劣。如某局部产品针对业务场景变动频繁的业务,数据复用问题无奈解决,对源端数据造成微小的压力。某局部产品针对抵触解决方案无奈达成最终的统一状态等。最终咱们基于开源产品的根底上自研 JinS 数据同步框架,全面解决上述问题。
1.2 多活准则
异地多活、同城双活、异地灾备等场景,下层业务有几个根本准则须要遵循:
- 业务内聚:每个残缺的业务流程,须要在一个机房中实现,不容许跨机房调用。只有保障这样一个前提,能力保证数据没有提早,业务数据的状态扭转足够快。
- 可用性优先:当产生故障切换机房时,优先保证系统可用。需容忍无限时间段内的数据不统一,在预先做修复
- 数据正确:在确保可用的根底下,须要防止数据出错,在产生切换或故障时,如果发现某些数据异样抵触导致无奈笼罩,会阻塞至人工染指,保证数据的正确(后续可间接针对数据锁定跳过操作)
1.3 产品性能
以后产品已实现性能:
- 双向同步:反对数据源间的增量实时同步,轻松实现异地多活等利用场景
- 单向同步:反对MySQL的单向同步,MySQL -> Redis 异构同步等,帮忙业务简便实现异地灾备,异构同步等场景
- 数据订阅:用于缓存告诉、业务异步解耦,异构数据源的数据实时同步以及复ETL等多种业务场景
- 一致性校验:可实现MySQL端到端的数据一致性校验以及修复
- 数据迁徙:可实现MySQL的数据迁徙,异构迁徙等场景
2. 一致性保障摸索
咱们将从三个维度的方向去摸索数据同步一致性。首先通过架构设计,保障系统的稳固、牢靠、可扩大。接着会通过以后业内通用的一致性计划,引出数据同步一致性的根本实现。最初通过具体计划施行的维度,来摸索施行过程中的细节。
2.1 架构设计
上图为数据同步的整体架构图。主流程中分为订阅模块和生产模块。订阅模块通过dump协定从源端MySQL 中拉取数据,生产端通过与订阅模块的通信,获取须要同步的数据,写入到指标端中。
- 订阅模块
订阅模块蕴含parser(dump拉取)、sink(过滤)、store(存储)、registry(注册)、monitor(监控)、protocol(协定)
- 生产模块
生产模块蕴含input(与订阅模块交互)、filter(过滤)、output(输入)、registry(注册)、monitor(监控)
- Manager
治理平台模块,负责实现流程一体化。服务部署,分类,监控,告警等流程治理
- Consul
注册核心,负责实现订阅、生产节点的服务主动注册,与prometheus实现监控主动发现,并于Manager模块实现kv告警指标存储
- Consul template
通过consul kv实现告警规定文件生成,并交付至prometheus
- Prometheus、Granfa、AlertManager
开源监控平台、开源监控平台可视化界面、开源告警平台
2.2 架构实际
因为数据同步中,数据一致性是重中之重。而随着业务的倒退,软件须要一直的迭代。如何从架构层面均衡好其中的关系,使软件在疾速迭代中还要保持稳定、牢靠,是架构施行过程中须要思考的问题。
在理解数据同步架构实际前,先理解一下微内核架构。
2.2.1 微内核架构
微内核架构,又称为插件化架构。例如Eclipse这类IDE软件、UNIX这类操作系统、淘宝APP客户端软件、甚至到Dubbo,采纳的都是微内核架构。
微内核架构的设计外围:
- 插件治理(如何加载插件以及插件加载机会等)
- 插件连贯(如何连贯到外围零碎,如Spring中的依赖注入,eclipse中的OSGI)
- 插件通信(如何实现数据的交互工作)
微内核架构,理论就是面向性能进行拆分的扩展性架构
- 外围零碎负责具体业务性能无关的通用性能(如插件加载、插件通信等)
- 插件模块负责实现具体业务逻辑
2.2.2 数据同步架构
数据同步中,生产模块参考微内核架构,将各个模块插件化。registry、monitor、input、filter、output都实现插件可插拔性能。
以Output举例,参考Java SPI、Dubbo SPI机制,基于 “接口 + 策略模式 + 配置文件” 的模式,实现Output的扩大。
配置文件如下:
通过读取配置文件中name的类型,实现对应插件的初始化。主流程中只须要实现接口调用,即可实现插件的调用。
以后生产模块中,已全面实现插件化。订阅模块依然有一部分未实现插件化革新。
2.3 一致性模型摸索
章节2.1,2.2中从架构的维度来保障整体的稳定性和扩展性,均衡了软件开发周期中的扩大与稳固之间的关系。上面从实现一致性的角度来摸索数据同步过程中如何达到数据的一致性。
2.3.1 分布式系统问题
随着摩尔定律遇到瓶颈,越来越多状况下要依附分布式架构,能力实现海量数据处理能力和可扩大计算能力。如果分布式集群无奈保障处理结果统一的话,那任何建设于其上的业务零碎都无奈失常工作。一致性问题是分布式畛域最根底也是最重要的问题。
因为咱们面临多节点的分布式架构,不可避免会呈现上图中所形容的问题,而这些问题正是形成一致性问题的次要成因。
实现相对现实的严格一致性代价是很大的,通常分为程序一致性和线性一致性。
- 程序一致性,抽象的说就是保障一个过程内的程序性,多个节点之间的一致性
- 线性一致性,难度更大,让一个零碎看起来像只有一个数据正本,所有操作都是原子性的,抽象说就是多个过程内也须要保障程序性
相似Google Spanner,通过 原子时钟 + GPS 的 trueTime 计划,实现分布式数据库的线性一致性。
一致性往往是指分布式系统中多个正本对外出现的数据状态,而共识算法,则是形容某一个状态达成统一后果的过程。因而咱们须要辨别,一致性形容的是后果的状态,共识只是实现某一状态统一的伎俩。分布式数据库在通过共识算法实现状态统一的前提下,还须要对多个状态进行程序辨认排序,这是一个相当简单的过程。
对应的共识算法有paxos、raft、zab等。
因为以后做的是预先的数据复制,在各个机房做了对应commit的前提下,再做数据复制,相似跨机房的主从复制。因而毋庸采纳相似分布式数据库实现数据的共识。没有了共识以及程序的引入,整体实现就绝对简略,只须要实现数据复制过程中的一致性即可。
2.3.2 CrashSafe的摸索
咱们参考 MySQL InnoDB 的 redo log 与 binlog 两个日志保障一致性,并实现对应crash safe,使用到数据同步保障一致性的场景中。
2.3.2.1 InnoDB CrashSafe保障
咱们先构想一下,MySQL 能够通过binlog复原到指定某一个时刻的数据,为什么binlog中的数据必然是你所想要的?
通过上图咱们看到,binlog 日志是 MySQL server 层实现的,而 InnoDB 存储引擎本人也实现了本人的redo、undo 日志。咱们临时不思考 InndoDB 是如何通过 redo、undo 日志实现事务的ACD个性。咱们先思考,MySQL 是如何实现 binlog 与 redo、undo 的一致性的。
当一条DML语句达到server层
- 先实现写 redo、undo,后写 binlog
数据写入 redo、undo 日志实现后,此时还未写入binlog,MySQL宕机重启,重启后因为InnoDB通过redo复原数据,而因为binlog没有写入该数据,若当前通过binlog复原数据,则造成数据不统一。
- 先实现写 binlog, 后写 redo、undo
数据写入binlog日志实现后,此时未写入redo、undo,MySQL宕机重启,重启后因为InnDB通过redo复原数据,没有宕机前写入的数据,而当前通过binlog复原数据,因为binlog曾经写入该数据,导致数据不统一。
从上述两种状况看,无论先写入binlog还是redo log,都会造成数据的不统一。因而为了保障redo与binlog的数据一致性,MySQL采纳2PC机制保障redo与binlog的一致性(具体2PC机制后续会有介绍),同时保障了两个日志的一致性后,redo log就能够实现其crash safe能力,无论写入在哪一刻宕机,都不会造成数据的不统一。
2.3.2.2 单向一致性
数据同步中,参照 MySQL 机制,通过 2PC 实现数据复制过程中的一致性,同时也实现了crash safe能力,如下图:
如上图所示,生产模块为2PC中的协调者,订阅模块为2PC中参与者
- 生产模块(协调者)通过rpc get申请到订阅模块
- 订阅模块(参与者)接管到生产模块的get申请后,将须要复制的数据发送给生产模块(协调者)实现prepare阶段,同时订阅模块会将此复制的数据写入内存中(redo log)
- 生产模块(协调者)承受到订阅模块(参与者)的数据后,即代表参与者曾经认同该操作,生产模块(协调者)即可拿着该数据实现目标数据源的写入操作,若写入胜利/失败,都将开启2PC的下一个阶段
- 生产模块(协调者)无论写入指标成功失败,都会返回给订阅模块(参与者,ack/rollback),订阅模块依据协调者的返回后果,决定内存中的redo log数据commit还是rollback,若commit,将该数据长久化
- 订阅模块(参与者)回复生产模块(协调者)胜利实现2PC
- 因为2PC次要用于解决分布式事务问题,它是一种阻塞性协定,次要是为了保证数据一致性因此造成的阻塞。而复制的场景中,因为只有一个参与者,因而咱们能够引入超时机制,而不会造成非阻塞引起的数据一致性问题
- redo log咱们采纳内存来实现,是因为MySQL的binlog中曾经帮咱们实现了对应的长久化存储,因而咱们能够借助binlog该机制,简化咱们的CrashSafe能力
2.3.2.3 双向一致性
通过2PC的机制,咱们能够实现单向同步过程中的数据一致性以及CrashSafe能力。咱们再来看看,双向同步中,因为有可能有两边同时批改同一条记录的场景,导致场景更加简单,咱们看如何解决双向过程中的一致性。
对于双向过程中,对同一记录的批改,因为某地的批改,在另外一个中央处于未可见的一段时间范畴,那咱们如何确认应该保留哪边批改的?
如上图所示,咱们能够分为事先管制以及预先解决。
1、事先解决,通常采纳共识算法来实现
流程图1
流程图2
咱们看通过共识算法的上述两个流程图
- 流程图1中 A地蕴含共识算法中(paxos)的 Proposer、Acceptor,B地中只蕴含共识算法中的Learner,假如A地写入,从图中可得数据将做 一次Paxos工夫 + 一次网络传输工夫
- 流程图1中 A地蕴含共识算法中(paxos)的 Proposer、Acceptor,B地中只蕴含共识算法中的Learner,假如B地写入,从图中可得数据将做 一次Paxos工夫 + 一次RTT网络传输工夫
- 流程图2中,因为A机房的一直扩容,必然造成有一天无奈再扩大,因此就会造成 Proposer、Acceptor跨机房,假如A地写入,从图中可得数据将做 一次Paxos工夫 + 一次RTT网络传输工夫
- 流程图2中,假如B地写入,从图中可得数据将做 一次Paxos工夫 + 2次RTT网络传输工夫
若北京广州机房网络提早40ms状况下,通过paxos协定会造成数据写入提早加大。以后MySQL本机房写入提早根本在1ms内
2、预先解决,因为数据在各地commit当前才做数据处理,因而必定会造成一段时间窗口的不统一,次要保证数据的最终一致性
因为CRDT计划须要数据结构的反对,咱们不革新MySQL,因而该计划间接略过。
Trusted Source计划:当呈现数据不统一时,永远以某一规定来笼罩数据(批改地、timestamp等)
问题如何判断数据不统一呢?业内通用计划会采纳构建抵触池
从上图能够看出,如果某端因为提早或者同步宕机造成构建抵触池失败,就会造成数据不统一。
单向回环计划:
将数据量较少端做一次环状同步,保证数据最终统一
从上述3个时序图能够得出,单向回环会保证数据最终一致性(图1),然而当某地同步提早或宕机场景下,有可能造成数据的版本交替景象(图2)、版本失落(图3)状况,然而最终两地数据肯定保障统一。
注: 后续为了优化版本交替以及版本失落的场景,会进行数据超过肯定阈值溯源反查,进步同步效率以及双向同步过程中的全局管制锁来解决
3. 最佳实际
前文中介绍了架构以及一致性算法的保障,咱们再来看看咱们理论使用中,如何将计划更好的落地。
3.1 文件存储
咱们晓得,以后大部分开源产品,数据订阅、同步为了能更快同步,通常会采纳全程不落地on fly同步,随着业务的变更频繁以及倒退,有可能造成一个数据库实例须要有多个上游业务应用。这样就须要启多个订阅来实现对应性能。结果随着业务的臃肿,对数据库的压力越来越大。
扩大文件存储的形式,能够通过文件队列做上游业务的散发,缩小对数据库的压力。
如上图EventStore模块,通过MMap文件队列,每个MMap文件对应一个索引文件。后续能够通过索引二分查问定位至具体文件地位。写入MMap文件队列为程序写入。
波及到文件的读写,就须要思考到文件的并发管制。以后支流的并发管制有如下:
- 读写锁
- COW(Copy On Write)
- MVCC
因为咱们的场景都是程序读写,MVCC能够首先疏忽。采纳读写锁的机制实践上是最简略。因而第一版的文件队列采纳的读写锁来实现,咱们看如下成果:
从下面两个图能够看到,上图的为批量操作数据库,下图为单事务操作数据库。业务更多的场景在单事务操作数据库,性能重大的有余,只有800多的qps,读写锁造成的性能齐全不满足需要。
因而咱们通过革新,去除读写锁。利用ByteBuffer与MMapByteBuffer同时映射至同一块物理内存来去除读写锁,ByteBuffer负责写入操作,MMapByteBuffer负责读,实现读写拆散,如下图所示:
最初通过ByteBuffer实现一个读写指针,实现物理内存的映射,映射后,MMapByteBuffer就能够牵强附会的读取对应写入的数据,而没有了读写锁了,读写指针代码如下:
因为ByteBuffer中蕴含HeapByteBuffer以及DirectByteBuffer,咱们创立时采纳DirectByteBuffer,避免数据流动造成过多的young gc。
最初咱们通过利用Linux中断机制,来最初优化磁盘的写入操作。
每个JVM过程实际操作的地址都是虚拟内存,而虚拟内存理论会通过MMU与物理内存地址关联起来。当咱们操作一个文件时,如果文件内容没有加载至物理内存,会产生一个硬中断(major page fault),若物理内存存在了,虚拟内存没有与物理内存关联,会产生一次软中断(minor page fault),而Linux中断关联,是以page的维度来产生关联的,一个page是4kb。因而咱们在写入文件刷盘时,就能够充分利用中断的机制,以page的维度来刷盘,当达到肯定的脏页后,咱们再产生flush操作,缩小硬中断的产生,代码如下:
相干优化实现后,咱们再来看单事务的测试成果,根本可达到2W qps的写入:
因为以后一次程序写入外,还须要写入索引文件,因而索引文件的性能造成整体写入性能不高,后续会持续针对这块继续优化。
3.2 relay log
有了上述的文件存储的优化后,咱们再来看同步过程中的网络优化
上图是MySQL主从同步的流程,从节点会通过I/O线程读取binlog的内容写入中继日志(relay)中,实现I/O与SQL Thread的拆散。让同步的流程更高,缩小网络带来的影响。
参考 MySQL 的relay流程,数据同步也在生产模块中实现了relay log的模块来保障跨网传输的优化,原理大抵相似。
在store中实现relay的机制,同时在下层读取时,用ringbuffer来做读取的缓存,进步读取效率。因为ABQ性能尽管高,然而ABQ在读写时,会有锁的竞争,因而两者性能差别较大,性能比照如下:
3.3 两阶段锁
在 InnoDB 事务中,行锁是在须要的时候加上的,加上应用完当前,也并非立即开释,而是须要等到事务完结当前才会开释。如下图所示:
这样的机制会造成,如果事务须要锁多个行,最好的优化伎俩是把抵触、并发的锁尽量往后放,缩小锁的工夫,如下图:
同样是一个事务的操作,只是转换一下程序,库存的行锁就缩小了锁期待的工夫。间接的进步了整体的操作效率。
基于两阶段锁的原理,为了缩小锁期待的工夫,数据同步在针对一个table,同一个pk的状况下,做了数据的合并操作,如将同一个pk的 insert、update语句,合并成一个insert语句等。合并实现后再进行同一个表的batch操作。
因为每个 DML 操作达到 MySQL须要通过server层的分析器去解析SQL,优化器去优化SQL等,通过batch,外部应用缓存的机制,缩小了解析等操作,性能会达到很大的晋升。
简略测试,insert 1000条记录,采纳batch模式大略500ms,而非batch模式须要13000ms。
不过因为引入了batch,也会引入其余问题,譬如当数据库中存在惟一键、外键时,有可能造成数据DML失败。后续会针对这种状况再做深刻优化。
3.4 性能指标
次要针对行业支流开源软件与商业产品与 JinS 数据同步框架的比照测试
论断:
- 比照开源产品,性能进步较为显著,根本达到一倍的性能晋升。
- 比照商业产品,性能较低,大略升高30%,然而从测试后果看,稳定性比商业产品更高。
4. 将来布局
JinS 数据同步框架上线以来,已撑持泛滥OPPO业务线的底层数据传输场景,在可用性上达到99.999%,跨机房均匀耗时达到秒级传输。解决了业务在不停服的前提下,实现数据迁徙、跨地区的实时同步、实时增量散发、异地多活等场景,使业务能轻松构建高可用的数据库容灾架构。
未来会不断完善JinS 数据同步框架,在数据的一致性保障、数据的二次加工定制化、性能的线性扩大以及多数据源一直优化赋能,使其一直走向欠缺、通用。