​MongoDB 4.0 系列之 \b—— 事务实现解析(\b一)

01

多行事务

Mongodb4.0引入了多文档事务的特性,我们来看,4.0中是如何进行一个多文档事务的(js的mongoshell代码)。

Mongo进行一个多文档事务,必须和一个session对象绑定。通过 startTransaction/ CRUD / commitTransaction 三段式来进行。

可以看到,startTransaction方法并没有返回值,事实上,它只是设置了writeConern/readConcern等参数, 而mongo提供的和session相关的commands列表中,也只有session.CommitTransaction 没有session.StartTransaction。这意味着什么呢?这意味着一个session对象不能并发的进行多个事务,其实一个session对象本身就是一个事务,准确而言,session对象的生命周期等价于mongo底层的writeUnit的生命周期。我在之前一篇文章 Mongodb事务模型分析中介绍过,wt本身一直都有多行事务的能力。mongo3.x系列的单行事务,是把索引,数据,oplog的更新放在了一个wt事务里,每一次写/更新操作都是一个事务,而万变不离其宗,4.0中的多行事务,设计了一个session对象给用户,session对象维护了wt层的事务对象。一次session.CommitTransaction 相比于3.x系列,本质差别仅仅在于向wt层提交的数据变多了。

02

WT-3181

ApplyOplog的全局锁(PBWM)

说到这,似乎都说完了,其实不然,mongo4.0发布,wt层也配合做了改造WT-3181。我们知道,mongodb 的从节点拉取主节点的oplog进行并发回放,这里会带来一些问题SERVER-20328

oplog的顺序(oplog的ts字段的大小)和oplog的回放顺序(在wt层的提交顺序)是不一致的。这个不一致造成了从节点在回放oplog时必须加全局读锁。防止客户端看到并发写时不一致的数据状态。解决这个问题不一定需要WT-3181,比如我之前SERVER-20328提出过,在applyOplog之间对wt生成一个snapshot,将所有从上面的读引流到这个snapshot上,也可以解决这个问题,细节暂且不提,我们看看mongo4.0 是如何借助WT-3181消除从库回放oplog的全局锁的。

Mongo在每应用完一批oplogs之后,会调用setMyLastAppliedOpTimeForward 方法设置local_timestamp为这一批oplog中最后一条的ts。

而这一条oplog的ts通过WiredTigerSnapshotManager::setLocalSnapshot,利用WT-3181提供的set_timestamp特性与一个wt层的snapshot一一对应,这个snapshot在mongo层叫做localsnapshot。两个localsnapshot之间,是oplog的并行复制过程,如果过早对外可见,用户就有可能读到一个空洞。

而db_raii::AutoGetCollectionForRead方法里,判断allowSecondaryReadsDuringBatchApplication这个服务端参数,如果该参数打开,就会忽略PBWM全局锁,并通过设置kLastApplied将LocalSnapshot最为读的源snapshot。

结合上面两段代码,我们可以知道,mongodb4.0中,从节点的读是不会和并行回放oplog相互阻塞的,也不用担心会读到不一致的状态。抛开复杂琐碎的实现细节,其原理也很简单,即读最近的一致的snapshot。

stable_checkpoint与rollback

经过上面的一顿分析,感觉不管是从库读snapshot还是多文档事务,WT-3181都不是必要的。我们再来看看这个新特新:mongo基于WT的rollback_to_stable API,实现了更加优雅的rollback功能。

在说rollback之前,不得不先说说raft。

我们知道,mongodb自从3.x系列,主从复制就使用raft协议了。raft的核心概念有两个:状态机日志应用。所谓的状态机,对应到mongodb的概念里,就是wiredtiger的kvstore。所谓的日志应用,在mongo里就是主从oplog复制。mongo的oplog复制也是使用raft协议。raft里有一个commit-timestamp的概念,不严格来讲,指的是被大多数节点应用到的日志的timestamp。raft算法可以保证commit-timestamp 一定不会被回滚。

mongo里也保证,以 writeConcern:majority的方式写入的数据,一定不会丢失,否则,主从切换后,与新主不一致的oplog,必须要被rollback掉。

这里可以有一个反思: Q:既然raft可以保证 “commit-timestamp” 一定不会回滚,那么mongodb为何不仅仅应用commit-timestamp之前的oplog到状态机(wiredtiger-kvstore)中呢? 这样可以把”回滚”的动作严格隔离在日志复制层面?

A:commit-timestamp的推进需要通过节点之间的网络心跳来完成,延迟不一定可接受。Mongo提供了更灵活的writeConcern,可以让用户读到未被(raft)commit的写。

说完raft,我们再说说3.x系列的rollback是怎么做的。

从复制源不断同步新oplog的过程。该过程一般会出现这两种问题:

  1. 复制源写入过快(或者相对的,本地写入速度过慢),复制源的oplog覆盖了 本地用于同步源oplog而维持在源的游标。
  2. 本节点在宕机之前是Primary,在重启后本地oplog有和当前Primary不一致的Oplog。

这两种情况分别如下图所示:

3. x 系列处理rollback的方法,以3.2.10为例,如下:

这两种情况在bgsync.cpp:_produce函数中,虽然这两种情况很不一样,但是最终都会进入 bgsync.cpp:_rollback函数处理,对于第二种情况,处理过程在rs_rollback.cpp中,具体步骤为:

最简单的普通文档的CUD操作的rollback,都需要从其他节点同步最新数据进行覆盖。

rollback_to_stable

随着复制的推进,每个节点虽有延时,但是(raft的)commit-timestamp时间点必然是单调的。由raft协议的保证,commit-timestamp之前的日志必然不会被回滚。因此mongo的每个节点(通过setMyLastAppliedOpTimeForward方法)确定一个oplog的ts成为commit-timestamp之后,会通过wt的api set_timestamp设置该ts为最新的stable-timestamp ,mongodb主从切换后,首先通过wt的api rollback_to_stable恢复到最近的被(raft)提交的snapshot,rollback的动作就完成了。毫无疑问,这样做更干净优雅。

oldest-timestamp

除了stable-timestamp之外,wt-3181 还提供了oldest-timestamp这一概念。oplog的ts和wt的snapshot一一对应,看上去非常美好,但是无数的前车之鉴的事实告诉我们,任何mvcc的实现中,维护的版本过多,都是有代价的。wt的官方文档中,建议用户通过设置oldest_timestamp对不需要的版本进行清理。小于oldest-timestamp的时间戳的数据会被wt清理。mongo4.0中,也有利用该机制进行版本清理。

commit as-of timestamp

最后,wt-3181提供了”commit as of some timestamp”的功能Application-specified Transaction Timestamps,在mongo4.0中尚未看到有用,直觉上,这个功能会在mongo4.2的分布式事务中,协调分布式事务的时钟上派上用场。这个功能是干什么的呢?

举个例子:以同样支持snapshotIsolation的rocksdb为例,对db的并发写入,每一条记录,在数据库中会有一个唯一的seqid与该记录对应,作为此次写入的版本。这个seqid是数据库分配的(rocksdb最近推出的write_unprepared_transaction打破了这个规则,暂且不提),虽然实现上各有差别,但是一般都保证先提交的事务中包含的记录的seqid 比后提交的事务中包含的记录的seqid小。简而言之,seqid随着commit的wallclock 单调。而wt-3181的“commit as of some timestamp”则提供了另一种可能:用户虽然在wall-clock为18:00的时间提交了事务,但是可以强行设置事务的 commit_timestamp 属性为17:00,对于事务的snapshot-read而言,seqid 的递增是不会出现幻读的一个保证,而wt-3181打破了这一保证,实在值得更深入的研究与实验。

作者:孔德雨

  • MongoDB中文社区深圳分会主席
  • 现就职于腾讯互娱 技术运营部

原文发布于微信公众号 - Mongoing中文社区(mongoing-mongoing)

原文发表时间:2018-07-24

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java后端技术栈

记一次解决业务系统生产环境宕机问题!

Zabbix告警生产环境应用shutdown,通过堡垒机登入生产环境,查看应用容器进程,并发现没有该业务应用的相应进程,第一感觉进程在某些条件下被系统杀死了,然...

881
来自专栏北京马哥教育

让“懒惰” Linux 运维工程师事半功倍的 10 个关键技巧!

好的Linux运维工程师区分在效率上。如果一位高效的Linux运维工程师能在 10 分钟内完成一件他人需要 2 个小时才能完成的任务,那么他应该受到奖励(得到更...

4006
来自专栏企鹅号快讯

入门干货之用DVG打造你的项目主页-Docfx、Vs、Github

由于这三项技术涉及到的要点以及内容较多,希望大家有空能自己挖掘一下更多更深的用法。 0x01、介绍 VS,即VS2017以及以上版本,宇宙最好的IDE,集成了宇...

2116
来自专栏恰童鞋骚年

NoSQL初探之人人都爱Redis:(1)Redis简介与简单安装

  随着互联网Web2.0网站的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经显得力不从心,暴露了...

1152
来自专栏SDNLAB

ONOS 实战分享(一):项目建立、调试到热部署

以上是ONOS的架构图,相信大家已经熟记于心了 本文将在Distributed Core Tier,以开发一个控制器内的模块为例,带领大家从项目的建立,导入I...

4567
来自专栏北京马哥教育

IBM技术专家教你“懒惰”Linux管理员的10个关键技巧

作者:Vallard Benincosa, 来源: https://www.ibm.com/developerworks/cn/linux/l-10sysadt...

3035
来自专栏炉边夜话

对中断的一点思考

    对于X86的单处理器机器,一般采用可编程中断控制器8259A做为中断控制电路。传统的PIC(Programmable Interrupt Contr...

1272
来自专栏奇点大数据

Hbase优化

本文对hbase集群进行优化,主要涵盖硬件和操作系统,网络通信,JVM,查询,写入,核心服务,配置参数,zookeeper,表设计等多方面。 我...

4065
来自专栏FreeBuf

MacOS再次出现漏洞,号称牢不可破的系统也有弱点

本文讲述了我在苹果的macOS系统内核中发现的几个堆栈和缓冲区溢出漏洞,苹果官方将这几个漏洞归类为内核中的远程代码执行漏洞,因此这些漏洞的威胁级别非常高。攻击者...

1012
来自专栏数据和云

Oracle 12.2新特性掌上手册 - 第三卷 Sharding 的增强

编辑手记:Sharding技术我们谈了好久,想必大家并不陌生,该功能12.2最新版本中,也将变得越来越完善,今天我们一起来学习。 注:文章内容来自官方文档翻译。...

2733

扫码关注云+社区

领取腾讯云代金券