前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >​MongoDB 4.0 系列之 —— 事务实现解析(一)

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

作者头像
MongoDB中文社区
发布2019-04-22 16:01:43
1.3K0
发布2019-04-22 16:01:43
举报

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中文社区深圳分会主席
  • 现就职于腾讯互娱 技术运营部
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Mongoing中文社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • WT-3181
    • 3. x 系列处理rollback的方法,以3.2.10为例,如下:
      • rollback_to_stable
        • oldest-timestamp
          • commit as-of timestamp
          相关产品与服务
          云数据库 MongoDB
          腾讯云数据库 MongoDB(TencentDB for MongoDB)是腾讯云基于全球广受欢迎的 MongoDB 打造的高性能 NoSQL 数据库,100%完全兼容 MongoDB 协议,支持跨文档事务,提供稳定丰富的监控管理,弹性可扩展、自动容灾,适用于文档型数据库场景,您无需自建灾备体系及控制管理系统。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档