专栏首页MongoDB中文社区Mongo4.2分布式事务实现Overview

Mongo4.2分布式事务实现Overview

整体流程

Mongos侧

相关代码:transaction_router.cpp/h

在Mongos侧,每个OperationCtx会有一个TransactionRouter,记录事务上下文。

TransactionRouter::get(opCtx)

每次事务内CRUD操作,会被TransactionRouter记录,特别是参与此次事务的shard,会被记录在_participants中。

TransactionRouter::attachTxnFieldsIfNeeded(opCtx, shardId, cmObj) {_participants.emplace_back(shardId);}

当事务提交时,TransactionRouter在所有参与者中选择(第一个)一个coordinator,并将两阶段提交的工作 hand off 给 coordinator。

TransactionRouter::_handOffCommitToCoordinatoropCtx) { CoordinateCommitTransaction cmd;  cmd.setParticipants(_participantList);  sendTo(_coordinator, cmd).waitForReply();}

因此,Mongos不处理二阶段提交,甚至configSvr也不参与。二阶段提交的工作完全由多个shard阶段之间协作完成。基于TimestampOracle的分布式事务需要中心节点分配事务Id或者时间戳,但是基于HLC的系统就不需要。

Shard侧

相关代码:transaction_coordinator.cpp/h,transaction_coordinator_util.cpp/h

shard侧在收到CoordinateCommitTransactionCmd后,会有如下操作:

  • 将{lsid,txnNumber, participants} 持久化到config.transaction_coordinators表中
  • 广播prepare命令{lsid, txnNumber, participants} - 收集prepareresponse,如果deadline之前没有全部返回,则prepare失败 - 只要有一个参与者反对提交,就prepare失败 - commitTimestamp = max(prepareTimestamp)
  • 将prepare结果COMMITorABORT更新到step1写入的记录中 - 一旦这一步决定commit,二阶段提交就必须成功
  • 假设prepare结果是commit,广播通知participants进行commit - 没有超时,如果某个participant一直没返回(挂了/网络故障),就一直等直到有返回结果
  • 删除config.transaction_coordinators表中的对应记录

Oplog格式更改

为了支持超过16MB的事务,一个事务内的多条操作会记录在多条Oplog里。每条Oplog新增字段如下:

  • prepare: {true/false} // 表示这条Oplog是否是分布式事务的prepare阶段产生的,与非事务产生的oplog区分
  • prevOpTime // 用以指向本事务内上一条Oplog,用于做prepare阶段的事务的恢复
  • lsid/txnNumber //多文档事务特有
  • o.commitTimestamp/o.commitTransaction

举个例子,一个shard内,有两个分布式事务,产生了五条Oplog db.oplog.rs.find().sort({$natural:-1})

{ts:5,o:{commitTimestamp:4,commitTransaction:1}, prevOpTime:4, lsid:1, txnNumber:1}{ts:4, applyOps:[...], prevOpTime:1,prepare:true, lsid:1, txnNumber:1}{ts:3,o:{commitTimestamp:2,commitTransaction:1}, prevOpTime:2,lsid:0, txnNumber:2}{ts:2, applyOps:[...], prevOpTime:-1,prepare:true,lsid:0, txnNumber:2}{ts:1, applyOps:[...], prevOpTime:-1,prepare:true, lsid:1, txnNumber:1}

总结:每个事务的Oplog不再放在一个OplogEntry里,甚至不再连续存放,而是通过prevOpTime串起来。

二阶段提交的故障恢复

分为Coordinator的故障和Participant的故障

Coordinator的故障

二阶段提交的信息持久化在transaction_coordinators表中。TransactionCoordinatorService::onStepUp从表里恢复 所有pending状态的事务,继续执行

auto coordinatorDocs = txn::readAllCoordinatorDocs(opCtx) for (const auto& doc : coordinatorDocs) {auto coordinator = std::make_shared<TransactionCoordinator>(); coordinator.continueCommit(doc);}

Participant的故障

事务在prepare时,会将修改记录记录到oplog表中,上文我们已经分析过prepareLog的格式,因此所有prepare成功的事务的Oplog都已经持久化,就差一条CommitLog了。在ReplicationCoordinatorImpl::startup中,会重新构建 所有处于prepare状态的事务。每个事务,顺着prevOpTime就可以找到所有的Oplog,通过Oplog恢复出事务的状态。

for (const auto& doc : config.transactions.find()) { lastOplogTime = doc.lastOplogTime vector<OplogEntry> v;while(auto t = oplog.rs.find(lastOplogTime)) { v.push_back(t);lastOplogTime = v.prevOpTime}reconstructTransaction(v.reverse());}

上文说过,coordinator会等待所有participant返回commit结果,所以participant重启后,coordinator会重试让participant提交处于prepare状态的事务。

可以看到,与Mysql的本地XA不同的是,mongo分布式事务的prepare阶段是通过Oplog保证持久化的,而不是wal 和oplog的协同,wiredTiger层在prepare阶段不保证持久化,那wiredTiger的prepare在干嘛呢?

wiredTiger层

建议先读这篇这篇

我们知道,对于wiredTiger,未提交事务是不会被evict的,也不会进入checkpoint,在ARIES算法中,这叫做nosteal模式。可是,在极端情况下,prepare状态可能会驻留很长时间,prepare状态的事务会不会被evict出去呢?为 了防止缓存压力,wiredTiger允许prepare状态的事务被evict出去(注意:不是evict到已提交事务的btree表空间)。

另外,(可能是)为了防止快照数量膨胀,wiredTiger在prepare阶段就释放快照,我们知道wiredTiger的SI是通过 事务开始时拷贝全局快照来实现,提前释放快照会导致未提交的事务过早的被看到,wiredTiger于是又加了一个WT_PREPARE_CONFLICT状态,考虑如下的时序:

txn1.begin() txn1.insert("a", "b")txn1.prepare(prepare_ts = 10)txn2.begin(read_ts = 11) txn2.read("a")  // WT_PREPARE_CONFLICT txn3.begin(read_ts = 9)txn3.read("a")  // WT_NOT_FOUND

由于txn1在prepare阶段就已经释放了快照。因此txn2开始时,txn1的修改对其是可见的,虽然可见,但是出于一种 特殊的 PREPARE_CONFLICT状态,Mongo层在处理PREPARE_CONFLICT状态的数据时,会不停重试,mongo将所有对wiredTiger的读操作通过wiredTigerPrepareConflictRetry封装起来。

int ret = wiredTigerPrepareConflictRetry(opCtx, [&] { return c->search(c); });

然而,直接略过这条记录不行吗?为什么Mongo一定要不停retry呢?因为这样违背SI。快照已经在事务prepare时释 放了,而事务的commitTimestamp > prepareTimestamp,因此对于一个SI的读事务,直接忽略另一个prepare状态的记录,会导致另一个事务提交后,对本事务可见,或者不可见。举个例子如下,txn2是快照隔离级别的,两次读a的结果必须一致。因此第一次读a需要重试。

// 如果txn2 直接不读其他出于prepare状态的记录。txn1.begin() txn1.insert("a", "b")txn1.prepare(prepare_ts = 10)txn2.begin(read_ts = 11) txn2.read("a") txn1.commit(commit_ts = 11) txn2.read("a")

那么,能不能不要prepare阶段释放快照,而是commit后释放快照呢?如果这样,wiredTiger其实根本不需要提供出prepare接口出来。

孔德雨

本文分享自微信公众号 - Mongoing中文社区(mongoing-mongoing),作者:孔德雨

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring分布式事务实现

           分布式事务是指操作多个数据库之间的事务,spring的org.springframework.transaction.jta.JtaTransac...

    阳光岛主
  • Mycat 分布式事务的实现

    随着并发量、数据量越来越大及业务已经细化到不能再按照业务划分,我们不得不使用分布式数据库提高系统的性能。在分布式系统中,各个节点在物理上都是相对独立的,每个节点...

    博文视点Broadview
  • 分布式事务的实现原理

    事务是数据库系统中非常有趣也非常重要的概念,它是数据库管理系统执行过程中的一个逻辑单元,它能够保证一个事务中的所有操作要么全部执行,要么全不执行;在 SOA 与...

    lyb-geek
  • 分布式事务的实现原理

    事务是数据库系统中非常有趣也非常重要的概念,它是数据库管理系统执行过程中的一个逻辑单元,它能够保证一个事务中的所有操作要么全部执行,要么全不执行;在 SOA 与...

    哲洛不闹
  • Spring分布式事务实现概览

    分布式事务,一直是实现分布式系统过程中最大的挑战。在只有单个数据源的单服务系统当中,只要这个数据源支持事务,例如大部分关系型数据库,和一些MQ服务,如activ...

    IT技术小咖
  • Spring+Mybatis+Atomikos实现分布式事务

    上一篇写了分布式事务产生背景以及解决方案,今天来实践一下,用Spring+Mybatis+Atomikos实现分布式事务。

    Liusy
  • 分布式事务的实现思想

    分布式事务的基本概念与本地事务类似,都保证了 ACID 特性(见[本篇第二章](# 二. 事务的特性))。随着数据的规模越来越大,就出现了对业务的解构,包括数据...

    剑影啸清寒
  • 分布式事务之事务实现模式与技术(四)

    Orderservice监听新订单队列中的消息,获取之后新增订单,成功则往新订单缴费队列中写消息,中间新增订单的过程使用JTA事务管理,当新增失败则事务回滚,...

    蒋老湿
  • 分布式事务实战

    微服务倡导将复杂的单体应用拆分为若干个功能简单、松耦合的服务,这样可以降低开发难度、增强扩展性、便于敏捷开发,从而被越来越多的开发者和公司推崇运用。但系统微服务...

    yuanyi928
  • 如何利用事务消息实现分布式事务?

    一说起事务,容易联想到数据库。我们日常使用事务的场景,绝大部分都是在操作数据库的时候。像 MySQL、Oracle这些主流的关系型数据库,也都提供了完整的事务实...

    王小明_HIT
  • 使用Atomikos实现JTA分布式事务

    本文全面的介绍了JTA分布式事务模型和接口规范,以及开源的分布式事务解决方案Atomikos。笔者认同"talk is cheap,show m...

    田守枝
  • 分布式事务之数据库事务与JDBC事务实现(一)

    介绍事务的四大原则,并通过实例介绍数据库实现事务的方法,以及使用JDBC实现事务的方法。

    蒋老湿
  • 分布式事务实战(三) -Spring事务及代码案例实现

    JavaEdge
  • 关于分布式事务的实现梳理

       在实际开发过程中,往往会遇到微服务架构中(数据分区存储),用户的一个操作,会设计到多个模块的数据落地或者更新查找,并且每个模块数据都是存储在不同的数据库,...

    小小许
  • 分布式事务之TCC服务设计和实现

    TCC是服务化的两阶段编程模型,其Try、Confirm、Cancel 3个方法均由业务编码实现;

    Bug开发工程师
  • 事务及分布式事务

    事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。 这些操作要么都做,要么都不做,是一个不可分割的工作单位。

    早安嵩骏
  • Lcn分布式事务流程实现(启动事务协调者)

    Lcn本身不会产生事务,也不会涉及到某些业务代码!他对事务的操作本身就依赖一个事务协调者服务

    止术
  • 分布式事务

    https://juejin.im/post/5b5a0bf9f265da0f6523913b

  • 分布式事务

    不知道你是否遇到过这样的情况,去小卖铺买东西,付了钱,但是店主因为处理了一些其他事,居然忘记你付了钱,又叫你重新付。又或者在网上购物明明已经扣款,但是却告诉我没...

    用户3467126

扫码关注云+社区

领取腾讯云代金券