快速,持续,稳定,傻瓜式
支持Mysql,Sqlserver数据同步

[数据库同步技术有哪些]深入数据库同步技术(2)- 时序性保障

在线QQ客服:1922638

专业的SQL Server、MySQL数据库同步软件

[数据库同步技术有哪些]深入数据库同步技术(2)- 时序性保障

在上一部分《深入数据库同步技术(1)-基础篇》中,我们为大家介绍了数据库同步的一些基本概念和普遍存在的痛点问题,这些问题大家平时工作或多或少也会遇到,读起来有些乏善可陈。

所以,在这一部分,我们将为大家介绍数据同步中的另一个比较新鲜的话题:时序性保障。

时序性及其重要意义

什么是时序性?

时序性就是事件发生的先后顺序。对于数据库来讲,事件的最小单元就是事务(即Transaction,每提交一个事务就产生一个事件,无论事务涉及的数据量有多大或者参与的表有多少)。

作为一个正常的OLTP数据库,可能每时每刻在每张表上都在发生事务性操作。类似于事件溯源(Event Sourcing)机制,我们希望将源库上的事务按照发生的时间先后顺序(也就是事务的提交顺序),依次应用在目的库上,这样就会保证源表和目的表数据状态的一致性和事务的不可撕裂性。

所以,数据库的时序性有两个显著特征:一是以事务为单位,二是保持事务发生的先后顺序。

为什么时序性会如此重要?

普通情况下我们做数据同步,一个线程负责一张表的同步和数据处理,各个线程间互不干涉,也没有任何联系,这样会产生一些问题。

假设有一个用户表和一个用户投资记录表,很显然用户投资记录表中有一个外键字段指向用户表的用户ID。如果单纯使用每张表/每线程的模式,很可能用户投资记录已经同步到目的表了,但是用户表的记录还没有同步过去,在目的库形成”子先于父存在”的情况。这不但让目的表不能像源表那样强制添加主外键约束,还会对目的端应用层的增量查询和数据汇总造成困扰。如果我们在同步过程中保持了时序性,则不会有此类问题发生。

再举一个例子,出于修正数据的需要,假设源库在一个事务中对多张表做了更新操作,然后提交事务。作为同步工具,如果不以事务为单位进行同步,你会发现目的库上的数据是混乱的:某些表的记录可能已经更新成了最新值,某些表可能还是保留原来的值,这是因为你的同步进程把事务撕裂了。源库在历史上的任何时刻从来没有出现过像现在目的库这样的数据形态,换句话说,你破坏了源库数据的一致性。同步本身可以有延迟,但是,数据不能有不一致。如果你在源库的事务中把钱转给了张三,在目的库的某个时刻进行汇总查询,结果发现你的钱被扣除了,但是张三的账户并没有增加,这就比较尴尬了。

在上一部分“基础篇”中,我们也提到了数据总线(Data Bus)的概念,进而可以引申出数据发布、数据订阅、数据通知。在数据总线中流转的事件必须是按发生顺序排列好的,然后再依次发送给各方系统。所以时序性也是构建数据总线的前提条件。

总之,时序性是衡量一个同步系统好坏的重要指标,接下来我们看看如何实施它。

方案选型

02

关于数据同步时序性,市面上其实有一些现成的解决方案。

如果同步源是MySQL,可以使用封装好的阿里开源的Otter/Canal框架,其基本原理是伪装成MySQL的Slave数据库,在我们的数据聚合项目中也用到了该框架。

[数据库同步技术有哪些]深入数据库同步技术(2)- 时序性保障

图1:Canal框架原理图(来自官方)

如果同步源是Oracle,则实现时序性有几种流派:

(1)日志挖掘

可以使用免费的Logminer,对在线日志或归档日志进行挖掘,从而解析出Redo SQL在目的库进行重放(Replay),还可以使用商业版的Oracle GoldenGate,这两种方式都是基于日志挖掘方式,其缺点是需要在数据库上进行诸多配置,对数据库不透明。

(2)触发器(Trigger)

可以使用LinkedIn的Databus,它基于trigger + ora_rowscn机制,在源表上添加trigger,需要部署多个存储过程包,侵入数据库更深。同时因为基于触发器,对源表写入有性能影响,也增加了发生死锁的可能性,部署这套东西会引起DBA的反感。

(3)物化视图(Materialized View)

比如阿里的yugong(愚公)开源项目,需要在每张源表上开启物化视图日志,并创建物化视图读取增量更新,物化视图可以看做是变相的trigger,也不是特别好。

对于Oracle,我们的需求其实很简单:

(1)实现时序性。

(2)在纯应用层搞定(只要开放select权限就行),对数据库透明,无需在数据库上动刀增加额外配置,更不需要DBA参与。

我们强调纯应用层的解决方案,不提倡在数据库层面有大动作,这是因为:

数据库是别人的,不是你想加字段就加字段,想加trigger就加trigger,想改配置就改配置的;

DBA资源是需要跨团队协调的,包括使用选型方案产生的后续维护工作。DBA资源的可用性是在你的掌控范围之外的;

数据库出问题的时候(比如死锁了),你是有潜力当背锅侠的;

当项目假手于人,需要太多外部资源扶持,你的项目可能是遥遥无期的;

总起来说,凡事要靠自己,不能一上来就有太多假设因素,项目需要介入扶持的资源越多,就越具有不可控性。

(3)上手容易,配置同步任务简单。

(4)最好在同步中间环节留有扩展,可以进行数据预处理和后处理

可惜市面上没有任何一款开源产品符合我们的需求,我们才自研了da-syncer同步工具。针对Oracle源,在应用层实现时序性,是da-syncer有别于其他同步工具的明显标志,也是它的核心竞争力所在。其绝不仅仅是将数据从一个地方同步到另外一个地方,中间可以做一些数据清洗和ETL工作的常规同步工具。

事实上,da-syncer曾经有两个版本的迭代开发,v1版本就是这样实现常规同步功能的版本,当时只用了两个星期的时间就开发完成了。但为了在v2版本中实现时序性,我们却耗费了大量的精力去调研,反复推敲可行性,不断进行方案选型,最后我们才创新性地借助于Oracle的闪回版本查询(Flashback Version Query)机制实现了时序性,我们应该是第一个这么干的系统。

03

Oracle闪回版本查询简介

前面我们讲到,da-syncer主要是利用了Oracle提供的闪回版本查询机制(Oracle 10g以上版本有提供),现在我们就来聊聊它。

注意:SCN(System Change Number)是系统改变号,它是一个长整形数,在这里可以将其简单理解为Oracle内部管理的时间戳。更多内容,请自行Google相关文档。

为了让大家有一个感性认识,我们来做一个简单的练习。创建一张表,并执行一些SQL语句(每条SQL单独提交以形成独立事务):

create table fbvq_test (

id number(38,0) not null,

name varchar2(20) not null,

primary key(id)

);

insert into fbvq_test(id, name) values(1, ‘a1’);

update fbvq_test set name=’a2′ where id=1;

delete from fbvq_test where id=1;

insert into fbvq_test(id, name) values(2, ‘b1’);

update fbvq_test set name=’b2′ where id=2;

由于记录1最终被删除,所以如果执行如下常规查询,只会返回name值为b2的记录2(见图2):

select id, name from fbvq_test;

图2:执行常规查询

如果我们执行闪回版本查询,就可以很神奇地追溯发生的事务全部历史版本并查询回来(见图3):

select id, name, rawtohex(versions_xid) versions_xid,

versions_operation,versions_startscn,versions_endscn,

to_char(versions_starttime,’yyyy-mm-dd hh24:mi:ss’) versions_starttime,

to_char(versions_endtime,’yyyy-mm-dd hh24:mi:ss’) versions_endtime

from fbvq_test

versions between scn minvalue and maxvalue

order by versions_startscn asc;

图3:执行闪回版本查询

观察图3我们可以得出一些基本结论:

版本这个概念属于记录,随着记录上发生事务提交(插入、更新和删除),新版本不断形成。

伪列versions_startscn和versions_starttime是版本的开始或者说形成时候的SCN和时间戳,因为只有通过提交事务才能形成版本,所以它们也代表了事务提交时的SCN和时间戳。

伪列versions_endscn和versions_endtime是版本的结束SCN和时间戳。

从以上可知,每个版本都有生命周期,占据记录版本时间线上的某一时间段。所以当前版本的versions_endscn和versions_endtime恰好是下个版本的versions_startscn和versions_starttime。

伪列versions_xid和versions_operation是形成版本时的事务ID和发生的操作(I=Insert,U=Update,D=Delete,你没看错,Delete也可以被检索出来)。

形成该版本时的用户字段值以及上述伪列字段可以随闪回版本查询一起被检索出来。

接下来看一下闪回版本查询起关键作用的过滤语句:

versions between scn and

START_SCN和END_SCN分别指明了查询版本时的开始、结束SCN条件值。

如果两个条件分别指定了minvalue和maxvalue,则会把目前Oracle内部回滚段内所有可用的记录版本全部查询出来。

假设4条记录在时间线上的版本分布图如下,蓝色棱形代表记录插入,粉红色短竖线代表版本开始或结束,红色长竖线代表我们指定的START_SCN和END_SCN查询条件。

[数据库同步技术有哪些]深入数据库同步技术(2)- 时序性保障

图4:闪回版本查询示意图

那么记录1会查询出v1,所有伪列字段都为null,因为v1的versions_startscn比 START_SCN更靠前,versions_endscn比END_SCN更靠后。

记录2会查询出v1且v1的versions_startscn为null,因为v1的versions_startscn比 START_SCN更靠前;会查询出完整v2和v3;会查询出v4且v4的versions_endscn为null,因为v4后续的新版本还未形成。

记录3会查询出完整v1;会查询出v2且v2的versions_endscn为null,因为v2的versions_endscn比END_SCN更靠后。

记录4比较特殊,因为v1的versions_endscn(也是v2的versions_startscn)正好等于START_SCN,v3的versions_endscn(也是v4的versions_startscn)正好等于END_SCN。在这种情况下会查询出完整的v2和v3,会查询出v4且v4的versions_endscn为null。

假如现在有一个需求,要求查询出START_SCN和END_SCN之间形成的版本(即记录在该SCN间隔内有过事务提交行为),我们只需要根据条件过滤:versions_startscn >=START_SCN and versions_startscn !=END_SCN,则记录2的v2、v3、v4,记录3的v1、v2,记录4的v2、v3这些符合要求的版本都会被过滤出来。

注意:图4没有引入Delete操作,Delete操作形成的版本表现会稍有不同。

在进行闪回版本查询时,有一些需要特别关注的点:

(1)除了versions between scn and 语法,还可以使用versions between timestamp and ,SCN和TIMESTAMP在Oracle内部有映射,且通过scn_to_timestamp和timestamp_to_scn函数可以相互转换,但因为Oracle内部实际使用的是SCN机制,所以推荐使用SCN进行闪回版本查询。

(2)随着时间的流逝,之前可以查询到某条记录的版本数据可能会逐步消失,这是因为版本数据不会永久留存,它依赖于Oracle多个参数的设置和动态因素,比如:

undo_management:undo管理模式是否为auto。

undo_retention:版本数据在undo表空间的保留时长。

undo_tablespace:使用到的表空间,表空间大小。

可以使用show parameter显示以上参数当前的设置值。

如果在版本查询中undo表空间被其他事务覆盖,同样会出现”基础篇“中提到的:

ORA-01555:snapshot too old: rollback segmeng number xxx with name xxx too small

(3)START_SCN和END_SCN不能是随意的整型数,必须是一个合法的SCN,否则会出错:

ORA-08181:specified number is not a valid system change number

可以使用select timestamp_to_scn(systimestamp) from dual;获取当前时间戳对应的SCN。

(4)即便是合法的SCN,START_SCN也不能太旧,必须在undo_retention参数指定的时间范围内,否则会出错:

ORA-30052:invalid lower limit snapshot expression

(5)如果表上发生了DDL操作(比如添加/删除字段,修改字段类型/长度,Truncate,添加约束等),进行闪回版本查询会出现以下错误:

ORA-01466:unable to read data-table definition has changed

选项(2)、(3)和(4)提醒我们:我们需要在尽量靠近当前时间域内进行闪回版本查询,否则就会出现问题。

闪回版本查询在da-syncer中的应用

04

对闪回版本查询有基本了解后,怎么使用它呢?

我们的想法是把时间线进行切割分段,逐段同步该段内多张表不同记录形成的版本,在图5中,黄色三角形代表了所有涉及同步的源表在这些段内形成的版本记录。

[数据库同步技术有哪些]深入数据库同步技术(2)- 时序性保障

图5:时间线分段

将相同段内来自各个表的记录的版本按照versions_startscn从小到大排序,就知道在该段内先后发生了哪些事务提交,然后将事务一一应用于目的端,自然保障了时序性。

但这里有一个问题,上面曾经提到,在普通情况下,一个线程负责一张表的同步,各个线程毫无关联关系,所以就需要一个协调/调度线程,负责下达统一的SCN位点信息给处于”全局同步”阶段的同步线程,使负责各个表同步的线程步调一致地在相同的START_SCN和END_SCN范围内进行闪回版本查询,然后再组合、排序查询结果即可。所以,实际上我们将整个同步过程分为几个阶段:

0:新建

1:全量同步中

2:全量同步结束

3:增量同步中

4:正在请求加入全局同步中

5:全局同步中

整体的大概流程总结如下:

(1)当通过管理控制台新添加一个同步任务后,任务阶段是”0:新建”。

(2)启动新的同步线程,根据任务配置,进入”1:全量同步中”。这里面包含了对大表进行分段的处理过程。

(3)当全量同步完成后,同步线程将其设置成”2:全量同步结束 “。

(4)接下来进入增量同步,同步线程将其设置成”3:增量同步中”,可以选择不同的增量同步策略,比如选择last_modified_date字段,对于Oracle,还可以选择ora_rowscn机制,或greatest_function_index机制(通过在多个日期字段上创建greatest函数索引,以代替last_modified_date字段)。

(5)每次增量同步后,系统检测是否已经达到可以加入全局同步的条件,比如连续3次增量同步时间小于1分钟,且最大和最小耗时之差小于5秒,这表示增量同步已经进入稳定态,可以请求加入全局同步了,否则继续增量同步。

(6)如果(5)条件满足,同步线程设置成”4:正在请求加入全局同步中”,向协调/调度线程请求追赶SCN位点,协调/调度线程下达追赶SCN位点给同步线程,如果该位点跟自身已经到达的SCN相等甚至还小,需要等待下一轮请求;否则,通过增量同步过程追赶下达的SCN。

(7)如果追赶位点成功,则加入全局同步成功,同步线程设置成”5:全局同步中”,所有处于全局同步的同步线程接受协调/调度线程统一下达的SCN,然后进行闪回版本查询,协调/调度线程负责组装、排序所有同步线程的查询结果(这一步是保证时序性的关键),并应用到目的库。

(8)如果同步线程在同步中遇到异常,比如源表添加新字段产生了ORA-01466错误,则同步线程又降级成增量同步,进入上述的步骤(4),进而再逐步升级成全局同步。

好了,时序性的话题就介绍到这里了。在下一部分,我们将带你进入”深入数据库同步技术(3)-da-syncer介绍”。

12

往期精彩

[数据库同步技术有哪些]深入数据库同步技术(2)- 时序性保障

[数据库同步技术有哪些]深入数据库同步技术(2)- 时序性保障

相关推荐

咨询软件
 
QQ在线咨询
售前咨询热线
QQ1922638