加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 建站 > 正文

事务系统实现模式很简单?你确定没忽视这些差异?

发布时间:2019-02-20 23:13:47 所属栏目:建站 来源:hellocode
导读:本文试图讨论这几个问题: MySQL的redo log和binlog为什么要用XA? MongoDB的oplog是按照什么顺序复制? Raft真的只能串行Apply吗? 数据库的复制和事务是完全独立的两回事? 为什么MySQL不早点做一个Raft插件,直接用Raft实现高可用? 本文旨在阐述Fault-Toler

回到一开始的第一种方案,在一个节点实现了KV、Raft、Lock Table、Transaction Manager,看起来耦合度比较大了,我们能不能对其进行分层,进一步简化呢?例如Google的经典做法,基于GFS实现Bigtable,基于Bigtable实现Percolator,Layered设计易于迭代、易于开发、易于调试。

因此我们可以考虑把KV层单独抽离出来,基于KV去实现Lock Table、Txn Manager:

  • Lock Table:在原本的KV中增加一列,变成Key => {Value, Lock};
  • Txn Manager: 从事务修改的所有Key中选出一个Primary Key,用来记录事务状态,因此KV进一步变成 Key => {Value, Lock, TxnStatus};
  • MVCC:甚至我们不甘心于Single Version,还想用Multi Version的并发控制,那么KV就变成{Key, Version} => {Value, Lock, TxnStatus}。

看过Percolator、TiKV设计的应该会比较熟悉,它们就是基于一个高可用的KV,把事务的状态都下沉到KV中。这种设计很容易拓展到分布式事务的场景,如果KV能够scale,那么上层的事务也能够scale了。

5、基于单机事务引擎实现高可用事务

上面的方案看起来都比较简单,不过有一个细节不容忽视:锁基本都是在复制协议提交之后才会释放,换句话说事务持有的锁会从事务开始直到多个节点写完日志,经历多次网络延迟、IO延迟,并且在拥塞情况下会面临排队延迟的风险。而锁意味着互斥,互斥意味着事务吞吐降低。

翻译一下:

  • 并发且有冲突的事务,其提交顺序由Lock Table决定,并且和复制协议的Log顺序一致;
  • 事务的Serialization Order,和RSM 中的Order一致。

不过这里存在一个问题:

  • 锁一定要在复制协议提交之后才能释放吗?
  • 提前释放会破坏Order的一致性吗?
  • RSM的Order一定要和事务的Serialization Order一致吗?

暂且不做回答,我们再看最后一种方案,基于单机事务引擎的高可用事务。

事务系统实现模式很简单?你确定没忽视这些差异?

在正常的单机事务流程中,增加一个复制的环节:本地事务提交之后不是立即返回用户,而是写binlog,等待binlog复制到其他节点之后再返回用户。

这种方式的事务延迟,看起来还是本地事务的延迟,加上复制日志的延迟;但相比于之前的方案,本地事务可以先提交,锁可以提交释放,总体的事务吞吐相比之下会有所提升。

看起来甚至比之前的方案更加简单,事务和复制模块得到了完美的分离,但这里忽略了一个复杂的问题:

  • 基于哪个日志来复制,基于数据库的Journal,还是再写一个binlog?
  • 基于什么顺序进行复制,如果是基于Journal复制可以用Journal顺序,如果基于binlog,顺序又是什么?
  • 如果有两个日志,两个日志其实意味着Transaction Serialization Order和RSM的State Machine Order不一样,会不会产生事务的并发异常,或者导致State Machine不一致?

由于直接复制Journal会引起一系列复杂的耦合问题,大部分数据库都选择单独写一个binlog/oplog来实现复制,不过在实现时可以做优化,因为如果真的写两个log会有原子性的问题(一个写成功了另一个没写成功)以及IO放大的问题。

这里的设计空间比较庞大,不做详细讨论,仅仅考虑在简化的模型下复制顺序的问题。

事务系统实现模式很简单?你确定没忽视这些差异?

对于并发执行的事务,为了确定复制顺序,这里维护一个称之为OpTime的自增ID。后续的复制会按照OpTime的顺序,OpTime小的先复制。如果OpTime仅仅是在事务的开始和结束之间分配,会带来问题:

  • 有冲突且并发的事务T1先Commit,具有较大的OpTime,也就意味会被后复制;
  • 后Commit的事务T2先Replication Commit,而先Commit的事务T1可能因为复制失败而Rollback;
  • 对于事务来说,这种场景下出现的异常类似Read-Uncommitted,事务T2读到了未Commit的数据。

因此,OpTime的分配需要有更强的限制:对于并发且有冲突的事务,OpTime的顺序要和事务的Serialization Order一样:

事务系统实现模式很简单?你确定没忽视这些差异?

在S2PL的场景中,我们把OpTime分配放到Lock之后Commit之前,即可满足这个要求。因为按照S2PL的调度,事务的Commit-Point就是Lock完成和Unlock之间。对照上面的例子,事务T2的OpTime被推迟到T1之后,复制的顺序也会相应改变,不会发生先前的异常了。

事务系统实现模式很简单?你确定没忽视这些差异?

推广到其他的并发控制方法也是类似,例如上面的Snapshot Isolation。提交之前会检查[begin, end]是否有冲突,有冲突直接重启事务。相当于在[begin, end]区间内分配OpTime即可。

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读