如何通过状态存储保证在分布式系统中保证银行交易的强事务属性?

如何通过状态存储保证在分布式系统中保证银行交易的强事务属性?显示全部

如何通过状态存储保证在分布式系统中保证银行交易的强事务属性?

收起
参与7

查看其它 1 个回答GoldenDB的回答

GoldenDBGoldenDB  产品经理 , 中兴通讯

1、事务理论的分布式延伸

关于 GoldenDB 的分布式事务处理机制,我们先看下事务处理在分布式架构有哪些延伸。我们理解的事务的一致性,其实是在事务内的原子性 ( 原子性 -A ) 和事物间的隔离性 ( 隔离性 -I ) ,以及故障时的持久性 ( 持久性 -D ) ,只有三者组合到一起才能保证数据的一致性 ( C ) 。

在分布式架构下,原子性的要求,是在多个数据分片上的多次操作,要么一起成功,要么一起失败;而在单机架构下,仅是一台机器上多条记录的多次操作;隔离性方面,要求多个计算节点上的不同连接,不会相互访问到在多个数据分片内未提交事务的数据;而单机数据库的隔离性,是指不同连接 ( 处理线程或进程 ) 不会相互访问到当前机器上未提交事务的数据;另外,从持久性角度看,分布式数据库的事务提交前必须将日志在分片主、 从节点都得到复制,主节点故障时,能在从节点上找回数据,继续完成事务;单机数据库的事务要求是,提交前必须先将日志落盘,机器宕机恢复后不丢失数据。

2、分布式事务的难点

那么,要实现分布式事务的实时一致性(保证 ACID ),难点在哪?其实包括两个部分:一是部分 DB 提交失败,如何保证全局事务的原子性 ( A ) ;另一部分是,并发访问时,每个事务都不知道其他事务的状态,如何保证事务之间的隔离性 ( I ) 。以转账交易为例 : 交易前 2 个账户资金余额各 100,事务 T1 从账户 1 转账 50 到账 户 2。需解决问题:在事务 T1 提交期间,由于 DB1 和 DB2 提交时间有空隙,若此时事务 T2 读取 2 个账户的余额,会发现余额之和是 50+100=150;存在事务 T1 对账户 1 上扣钱成功,给账户 2 加钱失败的情况。

3、如何保障原子性?

在业内,通常都用 2PC 协议保障原子性,在 MySQL 中就能看到很多场景,一个是 MySQL 里跨存储引擎的事务,二有存储引擎里 bin log 一致性的保障,都通过 2PC 实现。2PC 有自己的优势,参与者在投赞成票之前可以单方面取消事务,但是也有一些局限性。第一个问题,它本身是一个阻塞式的算法,进入 Prepare 以后一定要成功;第二个问题是,协同者的状态其实是一个单点,协调者挂了以后,参与者没有办法继续进行下去,参与者和协调者在某些状态会等待消息,需要启动定时监控;第三问题是,正常的提交流程,日志写入数量比较大,为 2N+3 ,消息数也多,为 4N ,其中 N 为参与节点的数量。

另外,还有很多通过增加消息中间件,或者使用 TCC 分布式事务框架,来实现事务原子性的方案。通过业务改造保障原子性,会有额外的问题:首先,是对业务的侵入性较大,所有业务都要增加反向操作逻辑 / 增加额外中间件;其次,处理过程中数据存在短暂不一致或导致更新延后,原子性不能 100% 保障;其三,无论 2PC、MQ 还是 TCC,都存在的一个问题,就是在 2 阶段提交 Commit 阶段,在 MQ 不断重试的阶段,在 TCC 的 Confirm 阶段如果出现了异常,没有办法解决,没有补救措施,必须人工干预。

至于,GoldenDB 事务原子性机制如何实现?总结起来就两句话:第一句是一阶段提交。GoldenDB 有一个全局事务,整体的提交是一阶段。把一个全局事务拆成由若干个子系统单独提交的一个个子事务,把这些子事务由计算节点交给对应 DB 去执行。执行之前,我们先在 GTM 那边把事务标记成活跃的状态;如果所有的 DB 都执行成功了,在 GTM 那边把事务标记为不活跃,全局事务就结束了;第二句是自动回滚补偿。遇到像转账失败的流程后,子事务在单机上会自动回滚,反馈给计算节点后,不是全部成功的,计算节点需要向数据节点下发回滚的消息。回滚补偿就是把下发回滚的操作做到数据库层面,业务层不需要做补偿交易。这种模式的优点,一是应用层无需增加额外补偿逻辑;二是,失败回滚是少数情况,整体性能高于两阶段提交,只需要一次交互就可以了,大大节省了单机资源。

4、已提交事务回滚实现和优化方式

在这一过程中,会涉及已提交事务回滚的实现和优化方式。整个过程要经历定位 - 遍历 - 生成 - 执行这样一个流程。通过全局事务 ID 所对应的 Binlog 提升回滚性能,数据行里会存有全局事务的 ID, 当你 Commit 的时候,全局事务 ID 会随着 Binlog 一起进入文件里。通过 Binlog 里面的 GTID 找到这个 GTID 所对应的所有 Binlog 的语句块,然后解析 Binlog,生成 SQL,立刻组成新的事务,重新执行一遍。对整个操作过程,GoldenDB 做了很多优化,包括表定义缓存、预分析并行查找、共享内存、key 值利用、SQL 格式,未来还要把正向 Binlog 直接生成反向 Binlog,不走 SQL 解析,直接走 MySQL 并行回放机制,直接应用到数据库。

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 最终达到的效果是,在所有的可串行化的并发调度里最终选择了某一种或者某几种,来保障事务的特性。

电信设备制造商 · 2020-03-16
浏览2543

回答者

GoldenDB
产品经理中兴通讯
擅长领域: 数据库服务器分布式系统

GoldenDB 最近回答过的问题

回答状态

  • 发布时间:2020-03-16
  • 关注会员:3 人
  • 回答浏览:2543
  • X社区推广