本文来自 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 数据同步框架,在数据的一致性保障、数据的二次加工定制化、性能的线性扩大以及多数据源一直优化赋能,使其一直走向欠缺、通用。