陆天炜: GoldenDB事务一致性处理机制升级历程
发布时间:2021-10-02 18:45:43 所属栏目:大数据 来源:互联网
导读:GoldenDB 是中兴通讯推出的一款自研的金融级交易型分布式数据。针对金融行业关注的数据库事务一致性问题,中兴通讯 GoldenDB 分布式数据库架构师陆天炜,在DTCC2019数据库大会上做了干货分享,重点介绍了 GoldenDB 的解决方案和对应的优化实践。 ▲中兴通讯G
5、事务隔离性的各种异常
我们如何处理事务隔离性的各种异常?首先,事务隔离性的各种问题都是并行调度处理不好导致,如果写写冲突,处理不当,会导致 ‘脏写’。比如: T1w、T2w、T1a,基于被回滚的数据做了update,造成了丢失回滚。再比如,写读冲突处理不当,导致‘脏读’,如果第一读,读到了未提交的数据,那就是脏读,如果多次读并读了不同版本的数据,那是不可重复读,由于事务并发导致前后读出的多个数据间不满足原有约束,那是读偏序,由于事务并发导致满足条件的结果集,变多或者变少,那是幻读。此外,还有读写冲突,处理不当导致 ‘脏写’ ,比如:T1r、T2w、T1w,T2w被覆盖,会导致丢失更新。最后是写偏序,这是一种违反语义的异常,在数据库层面看正常,但是达不到你要求的效果,就像黑白球,我有一黑一白两个球,事务一要把黑球改成白球,事务二要把白球改成黑球,当在快照级别隔离下,两个事务一起并发操作时,会导致最终事务一改了一个球,事务二改了一个球,一黑一白变成了一白一黑,这种情况是错误的,不满足于任意一种串行处理结果。以上这些异常都是并发调度没有处理好的结果。
在《A Critique of ANSI SQL Isolation Levels》论文中,总结了隔离级别及对应的现象。首先,R R级别的隔离解决不了幻读;其次,SI隔离解决不了写偏序。只有可串行化的隔离级别才能解决所有问题。有人说MySQL级别的R R隔离能解决幻读,其实MySQL级别的R R隔离并非论文里指出的R R级别,它实际上是SI的隔离级别。
那么,实现可串行化隔离级别的方式有哪些呢?第一种方式是严格按照串行顺序执行,这种方式肯定能保证隔离性。像Voltdb,号称是最fast的内存数据库,通过串行保证隔离性,通过在存储过程里执行保证了原子性。Redis是单线程跑,所以隔离性也没有问题,但是Redis只支持单语句事务,只能保证单语句的原子性。如果你需要Redis去做多行的事务,就需要自己去保证它的原子性。第二种,是运用SS2PL技术,加锁解决幻读 。比如:MySQL、SQL Server、Informix的隔离级别,实际上都是通过SS2PL技术做到可串行化的隔离级别。第三种,可串行化的快照隔离SSI,可解决写偏序问题。像PostgreSQL、FoundationDB,都是这种模式的隔离。这三个都是正确的隔离,而其他的隔离都是对性能的妥协,都是不正确的。
6、如何通过2PL进行事务并发控制?
这里主要看下如何通过2PL进行事务并发控制。Eswaran等人已经证明:遵守2PL算法的并发调度一定是可串行化的。什么是2PL?是指在数据库事务处理中的两阶段锁定,前一个阶段只能加锁,后一个阶段只能释放锁。如果所有的事务都能遵循这样一个原则去处理,并发起来的最终结果就等同于某一种串行化的并发调度结果了。不考虑事务Commit时的失败,那2PL这就够了,已经能保证事务的一致性了。但是这个可串行化的可能太多了,而且LockPoint比较难找,所以大部分厂商的实现方式都是SS2PL,都是把读锁和写锁的释放放到Commit阶段一起去执行。
问题是,从2PL到SS2PL到底有哪些限制?第一个是到S2PL,它做了第一个限制,先把写锁放到Commit,当一个事务对某个数据做了修改,但是没提交之前,所修改的数据的后像是不能被其他的事务读或者写的。SS2PL则在S2PL基础上,把读写也放到了最后。如果一个事务做了修改,那么所做修改的数据的前像是没有被其他事务给读过的,SS2PL最终达到的效果是,在所有的可串行化的并发调度里最终选择了某一种或者某几种,来保障事务的特性。
GoldenDB是怎么做事务的并发控制的?GoldenDB引入了全局事务管理器(GTM:Global Transaction Manager)实现全局事务的隔离性,并协助实现全局事务的原子性。
与SS2PL的比较,GTM封锁是在事务提交之前,所有的GTID相关的数据都不能被读取和修改。活跃事务列表里的GTID,相当于写锁。保障了写写和写读冲突下的数据一致性。另外,通过对读操作显式加锁,达到严格的SS2PL效果,保障了读写冲突下的数据一致性。而SS2PL只在事务提交的的时候才释放读锁和写锁。
GoldenDB还有一个预锁机制,用来解决如下问题:DBProxy查询到的活跃事务列表是旧的;DBProxy基于活跃事务列表做的判断是不准确的。GoldenDB的解决办法是,将所有的可能性冲突都认为是冲突。在返回活跃事务列表中,增加Max_GITD,Proxy判断如果GTID大于Max_GITD,也认为该事物是存在冲突的。
总结下来,对于事务处理中的写一致处理,涉及两个方面:一个是满足条件的记录上无锁, GTID不活跃,就可以直接修改数据,如果GTID活跃,这个时候需要通过预锁机制重新等待GTID被释放;另外是满足条件的记录上有锁,只能先走单机锁冲突的事务处理机制,等到锁释放了,再去判断GTID是否活跃。
对于事务处理中读一致性的处理:在脏读的场景下,不做任何判断,直接读,但是不推荐这种隔离级别;读已提交,如果有排它锁,证明这条数据有事务正在修改,直接读它的前像。如果无排它锁,GTID不活跃,没有单机事务,全局事务,直接读当前数据行。如果无排它锁,GTID活跃,说明没有单机事务,但是有全局事务,仍然不能读单机上的数据,需要去读 数据,这个前像数据的purge操作需要全局进行;在读已提交的场景下,有排他锁和无排他锁、GTID活跃场景的处理方式是一样的;在可重复读,有排它锁的情况下,会按照GTID版本号去找,找到快照开始时候的版本号。在可重复读的场景下,无排它锁,GTID不活跃,看上去没有问题,但是当前数据行有可能已经被提交过了,不能直接读,需要筛选版本判断是否需要找前像数据。无排它锁,GTID活跃,也是一样的,需要去找满足条件的GTID版本号的事务版本;可序列化级别也如此,加共享锁无冲突,GTID不活跃,可以直接读数据,如果GTID活跃,还可以读数据,但是要把活跃的冲突的GTID号给带上去,DBProxy根据预锁的机制,判断什么时候GTID能正常释放,如果正常释放,数据就可以读到。不能的话,需要把事务重启。在可序列化、加共享锁有冲突的场景下,仍然按照单机锁冲突的并发控制去处理。
事务处理模块优化有哪些实践经验?
在GoldenDB上有个GTM,是一个主要的优化点。怎么解决性能的瓶颈?首先,GTM被设计得很简单,只有三个流程,负责GTID的分配、回收,负责向计算节点提供活跃事务ID的查询的一个接口。GTID的分配和回收需要实时落盘,GTID相当于一个大号的Binlog ,一次写盘大概1μs,理论上限100万 TPS,能否突破?另外一个优化点是,Proxy和GTM之间的消息交互,对网络资源消耗过大,理论瓶颈1.25GB 带宽如何利用?
1、申请/释放GTID优化:Proxy批量请求
在释放GTID的时候,做了批量释放,能够做到减少交互次数,降低交互的数据量。在申请GTID的时候也做了批量申请,但是这里有个问题,在RC和串行隔离下面,可以任意使用批量申请,但是在RR隔离下面,是不能肆无忌惮的去使用批量申请的,在RR隔离级别下,是基于GTID的数据快照,要求GTID是严格单调的,所以在GTID申请的时候,还是要保障GTID单调,先来的事务先获得GTID。但实际上,由于系统的调度都是有误差的,比如Proxy1向GTM申请了GTID1,要去修改数据A, Proxy2向GTM申请了GTID2,也要修改数据A,虽然是Proxy1先申请,但是他不一定能最快地给数据I加上锁,所以靠分配GTID去严格保障时间顺序,有误差。也就是数据库调度会有问题,不能保证先来的请求一定会先被执行。所以,在一定误差范围内,分配的GTID可以不保证严格单调的,需要做额外处理。Proxy1先申请了1000个GTID,我需要他在很短的时间内把这1000个GTID给用掉,如果不用,需要有一个废弃的机制,相当于保证GTID申请的这些事务在误差内保证单调。这样可也减少交互次数,降低交互数据量。
2、查询GTID列表优化:Proxy进行组提交
Proxy北向是一个数据库连接池,跟应用的连接池做交互,有的执行线程是在做建链,有的是在做SQL解析,但是也有可能他们都在做GTID的查询,如果所有的线程都能直接和GTID去做交互,交互数据量会很大。所以,代理线程可汇总执行线程的请求,然后跟GTM做单独的交互,并且这个交互是单线程单工的,使用一份活跃事务列表应答。当在途请求没有返回时,代理线程不会发送新的查询请求。
3、查询GTID列表优化:GTM进行组提交
GTM和Proxy是一对多的,多个计算节点去访问同一个GTM,GTM在汇总多个Proxy请求以后,他也可以产生一个子线程出来。专门处理GTM返回请求的拼装,用同一份活跃事务列表应答不同Proxy发来的请求。 当子线程没有拼装完返回列表时,不会处理新的请求,这和Proxy处理机制类似。
4、组提交优化后对预锁机制的影响
有一个问题是,刚在Proxy上和GTM上都做了组提交,但对之前的预锁机制,对性能到底有没有影响。在优化之前的场景下,Proxy上所有向GTM发的查询请求,都需要过0.5ms(32k包大小万兆下的双向时延)才能收到响应,Proxy获取到的事务列表都落后GTM0.25ms;优化之后,Proxy上所有的请求,在0~0.5ms 内会收到响应,Proxy获取到的事务列表的时间没有变化,仍然是落后GTM0.25ms,经过组提交优化后,并未增加单消息处理时延。可能有人会问,如果以后性能要求更高的情况下,如何处理带宽占用的问题?我们可以通过自适应算法精简请求,依据请求数、统计时间和当前负载情况,实时修改发送周期。另外,可以通过数据压缩技术,通过记录GTID起始值(8Byte)和偏移量位图(255Byte),将数据包大小由32k降到1K以内,压缩后万兆的带宽允许提供1200个Proxy。所以,Proxy和GTM的带宽不会是性能瓶颈。 (编辑:无锡站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |