当前位置:主页 > 软件 > 正文

实用干货: MongoDB 4.0 事件实现解析

2019-07-01 来源:汽车保险网 编辑:admin

核心提示

【IT168技能】上个月尾MongoDBWolrd宣布公布MongoDB4.0,支撑复制集多文档事件,阿里云数据库团队研发工程师第一时间对事件功效的时间举行了源码阐明,解析事件实现机制

  【IT168 技能】上个月尾 MongoDB Wolrd 宣布公布 MongoDB 4.0, 支撑复制集多文档事件,阿里云数据库团队 研发工程师第一时间对事件功效的时间举行了源码阐明,解析事件实现机制。

  MongoDB 4.0 引入的事件功效,支撑多文档ACID特征,比方使用 mongo shell 举行事件操作

  > s = db.getMongo().startSession()

  session { "id" : UUID("3bf55e90-5e88-44aa-a59e-a30f777f1d89") }

  > s.startTransaction()

  > db.coll01.insert({x: 1, y: 1})

  WriteResult({ "nInserted" : 1 })

  > db.coll02.insert({x: 1, y: 1})

  WriteResult({ "nInserted" : 1 })

  > s.commitTransaction() (或者 s.abortTransaction()回滚事件)

  支撑 MongoDB 4.0 的其他语言 Driver 也封装了事件相干接口,用户需要创建一个 Session,然后在 Session 上开启事件,提交事件。比方

  python 版本

  with client.start_session() as s:

  s.start_transaction()

  collection_one.insert_one(doc_one, session=s)

  collection_two.insert_one(doc_two, session=s)

  s.commit_transaction()

  java 版本

  try (ClientSession clientSession = client.startSession()) {

  clientSession.startTransaction();

  collection.insertOne(clientSession, docOne);

  collection.insertOne(clientSession, docTwo);

  clientSession.commitTransaction();

  }

  Session

  Session 是 MongoDB 3.6 版本引入的观点,引入这个特征首要就是为实现多文档事件做筹办。Session 本质上就是一个「上下文」。

  在从前的版本,MongoDB 尽管理单个操作的上下文,mongod 办事进程吸收到一个请求,为该请求创建一个上下文 (源码里对应 OperationContext),然后在办事整个请求的历程中一直使用这个上下文,内容包括,请求耗时统计、请求占用的锁资源、请求使用的存储快照等信息。有了 Session 之后,就可以让多个请求共享一个上下文,让多个请求发生关联,从而有能力支撑多文档事件。

  每个 Session 包罗一个独一的标识 lsid,在 4.0 版本里,用户的每个请求可以指定分外的扩展字段,首要包括:

  lsid: 请求地点 Session 的 ID, 也称 logic session id

  txnNmuber: 请求对应的事件号,事件号在一个 Session 内必需单调递增

  stmtIds: 对应请求里每个操作(以insert为例,一个insert号令可以插入多个文档)操作ID

  现实上,用户在使用事件时,是不需要理解这些细节,MongoDB Driver 会主动处置惩罚,Driver 在创建Session 时分派 lsid,接下来这个 Session 里的以是操作,Driver 会主动为这些操作加上 lsid,假如是事件操作,会主动带上 txnNumber。

  值得一提的是,Session lsid 可以通过挪用 startSession 号令让 server 端分派,也可以客户端本身分派,如许可以节流一次收集开销;而事件的标识,MongoDB 并没有提供一个单独的 startTransaction的号令,txnNumber 都是直接由 Driver 来分派的,Driver 只需包管一个 Session 内,txnNumber 是递增的,server 端收到新的事件请求时,会自动的最先一个新事件。

  MongoDB 在 startSession 时,可以指定一系列的选项,用于节制 Session 的会见举动,首要包括:

  causalConsistency: 是否提供 causal consistency 的语义,假如配置为true,岂论从哪个节点读取,MongoDB 会包管 "read your own write" 的语义。参考 causal consistency

  readConcern:参考 MongoDB readConcern 道理解析

  writeConcern:参考 MongoDB writeConcern 道理解析

  readPreference: 配置读取时选取节点的法则,参考 read preference

  retryWrites:假如配置为true,在复制集场景下,MongoDB 会主动重试产生从头选举的场景; 参考 retryable write

  ACID

  Atomic

  针对多文档的事件操作,MongoDB 提供 "All or nothing" 的原子语义包管。

  Consistency

  太难诠释了,另有丢弃 Consistency 特征的数据库?

  Isolation

  MongoDB 提供 snapshot 断绝级别,在事件最先创建一个 WiredTiger snapshot,然后在整个事件历程中使用这个快照提供事件读。

  Durability

  事件使用 WriteConcern {j: ture} 时,MongoDB 必然会包管事件日记提交才返回,纵然产生 crash,MongoDB 也能按照事件日记来恢复;而假如没有指定 {j: true} 级别,纵然事件提交乐成了,在 crash recovery 之后,事件的也可能被回滚掉。

  事件与复制

  复制集设置下,MongoDB 整个事件在提交时,会记载一条 oplog(oplog 是一个平凡的文档,以是今朝版本里事件的修改加起来不能凌驾文档巨细 16MB的限定),包罗事件里全部的操作,备节点拉取oplog,并在当地重放事件操作。

  事件 oplog 示例,包罗事件操作的 lsid,txnNumber,以及事件内全部的操作日记(applyOps字段)

  "ts" : Timestamp(1530696933, 1), "t" : NumberLong(1), "h" : NumberLong("4217817601701821530"), "v" : 2, "op" : "c", "ns" : "admin.$cmd", "wall" : ISODate("2018-07-04T09:35:33.549Z"), "lsid" : { "id" : UUID("e675c046-d70b-44c2-ad8d-3f34f2019a7e"), "uid" : BinData(0,"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") }, "txnNumber" : NumberLong(0), "stmtId" : 0, "prevOpTime" : { "ts" : Timestamp(0, 0), "t" : NumberLong(-1) }, "o" : { "applyOps" : [ { "op" : "i", "ns" : "test.coll2", "ui" : UUID("a49ccd80-6cfc-4896-9740-c5bff41e7cce"), "o" : { "_id" : ObjectId("5b3c94d4624d615ede6097ae"), "x" : 20000 } }, { "op" : "i", "ns" : "test.coll3", "ui" : UUID("31d7ae62-fe78-44f5-ba06-595ae3b871fc"), "o" : { "_id" : ObjectId("5b3c94d9624d615ede6097af"), "x" : 20000 } } ] } }

  整个重放历程如下:

  获取当前 Batch (后台不停拉取 oplog 放入 Batch)

  配置 OplogTruncateAfterPoint 时间戳为 Batch里第一条 oplog 时间戳 (存储在 local.replset.oplogTruncateAfterPoint 荟萃)

  写入 Batch 里全部的 oplog 到 local.oplog.rs 荟萃,按照 oplog 条数,假如数目较多,会并发写入加快

  清算 OplogTruncateAfterPoint, 标识 oplog 完全乐成写入;假如在本步骤完成前 crash,重启恢复时,发明 oplogTruncateAfterPoint 被配置,会将 oplog 截短到该时间戳,以恢复到一致的状况点。

  将 oplog 划分到到多个线程并发重放,为了晋升并发效率,事件发生的 oplog 包罗的全部修改操作,跟一条平凡单条操作的 oplog 一样,会据文档ID划分到多个线程。

  更新 ApplyThrough 时间戳为 Batch 里末了一条 oplog 时间戳,标识下一次重启后,从该位置从头同步,假如本步骤之前失败,重启恢复时,会从 ApplyThrough 上一次的值(上一个 Batch 末了一条 oplog)拉取 oplog。

  更新 oplog 可见时间戳,假如有其他节点从该备节点同步,此时就能读到这部门新写入的 oplog

  更新当地 Snapshot(时间戳),新的写入将对用户可见。

  事件与存储引擎

  事件时序同一

  WiredTiger 很早就支撑事件,在 3.x 版本里,MongoDB 就通过 WiredTiger 事件,来包管一条修改操作,对数据、索引、oplog 三者修改的原子性。但现实上 MongoDB 颠末多个版本的迭代,才提供了事件接口,焦点难点就是时序问题。

  MongoDB 通过 oplog 时间戳来标识全局顺序,而 WiredTiger 通过内部的事件ID来标识全局顺序,在实现上,2者没有任何干联。这就导致在并发环境下, MongoDB 看到的事件提交顺序与 WiredTiger 看到的事件提交顺序纷歧致。

  为解决这个问题,WiredTier 3.0 引入事件时间戳(transaction timestamp)机制,应用法式可以通过WT_SESSION::timestamp_transaction 接口显式的给 WiredTiger 事件分派 commit timestmap,然后就可以实现指按时间戳读(read "as of" a timestamp)。有了 read "as of" a timestamp特征后,在重放 oplog 时,备节点上的读就不会再跟重放 oplog 有冲突了,不会因重放 oplog 而壅闭读请求,这是4.0版本一个伟大的晋升。

  /*

  * __wt_txn_visible --

  * Can the current transaction see the given ID / timestamp?

  */

  static inline bool

  __wt_txn_visible(

  WT_SESSION_IMPL *session, uint64_t id, const wt_timestamp_t *timestamp)

  {

  if (!__txn_visible_id(session, id))

  return (false);

  /* Transactions read their writes, regardless of timestamps. */

  if (F_ISSET(&session->txn, WT_TXN_HAS_ID) && id == session->txn.id)

  return (true);

  #ifdef HAVE_TIMESTAMPS

  {

  WT_TXN *txn = &session->txn;

  /* Timestamp check. */

  if (!F_ISSET(txn, WT_TXN_HAS_TS_READ) || timestamp == NULL)

  return (true);

  return (__wt_timestamp_cmp(timestamp, &txn->read_timestamp) <= 0);

  }

  #else

  WT_UNUSED(timestamp);

  return (true);

  #endif

  }

  从上面的代码可以看到,再引入事件时间戳之后,在可见性判断时,还会分外查抄时间戳,上层读取时指定了时间戳读,则只能看到该时间戳从前的数据。而 MongoDB 在提交事件时,会将 oplog 时间戳跟事件关联,从而到达 MongoDB Server 层时序与 WiredTiger 层时序一致的目的。

  事件对 cache 的影响

  WiredTiger(WT) 事件会打开一个快照,而快照的存在的 WiredTiger cache evict 是有影响的。一个 WT page 上,有N个版本的修改,假如这些修改没有全局可见(参考 __wt_txn_visible_all),这个 page 是不能 evict 的(参考 __wt_page_can_evict)。

  在 3.x 版本里,一个写请求对数据、索引、oplog的修改会放到一个 WT 事件里,事件的提交由 MongoDB 本身节制,MongoDB 会尽可能快的提交事件,完成写清求;但 4.0 引入事件之后,事件的提交由应用法式节制,可能呈现一个事件修改许多,而且很长时间不提交,这会给 WT cache evict 造成很大的影响,假如大量内存无法 evict,终极就会进入 cache stuck 状况。

  为了尽量减小 WT cache 压力,MongoDB 4.0 事件功效有一些限定,但事件资源占用凌驾必然阈值时,会主动 abort 来开释资源。法则包括

  事件的生命周期不能凌驾 transactionLifetimeLimitSeconds (默认60s),该设置可在线修改

  事件修改的文档数不能凌驾 1000 ,不行修改

  事件修改发生的 oplog 不能凌驾 16mb,这个首要是 MongoDB 文档巨细的限定, oplog 也是一个平凡的文档,也必需遵守这个束缚。

  Read as of a timestamp 与 oldest timestamp

  Read as of a timestamp 依靠 WiredTiger 在内存里维护多版本,每个版本跟一个时间戳关联,只要 MongoDB 层可能需要读的版本,引擎层就必需维护这个版本的资源,假如保留的版本太多,也会对 WT cache 发生很大的压力。

  WiredTiger 提供配置 oldest timestamp 的功效,许可由 MongoDB 来配置该时间戳,寄义是Read as of a timestamp 不会提供更小的时间戳来举行一致性读,也就是说,WiredTiger 无需维护 oldest timestamp 之前的全部汗青版本。MongoDB 层需要频仍(实时)更新 oldest timestamp,制止让 WT cache 压力太大。

  引擎层 Rollback 与 stable timestamp

  在 3.x 版本里,MongoDB 复制集的回滚行动是在 Server 层面完成,但节点需要回滚时,会按照要回滚的 oplog 不停应用相反的操作,或从回滚源上读取最新的版本,整个回滚操作效率很低。

  4.0 版本实现了存储引擎层的回滚机制,当复制集节点需要回滚时,直接挪用 WiredTiger 接口,将数据回滚到某个不变版本(现实上就是一个 Checkpoint),这个不变版本则依靠于 stable timestamp。WiredTiger 会确保 stable timestamp 之后的数据不会写到 Checkpoint里,MongoDB 按照复制集的同步状况,当数据已经同步到大大都节点时(Majority commited),会更新 stable timestamp,由于这些数据已经提交到大大都节点了,必然不会产生 ROLLBACK,这个时间戳之前的数据就都可以写到 Checkpoint 里了。

  MongoDB 需要确保频仍(实时)的更新 stable timestamp,不然影响 WT Checkpoint 举动,导致许多内存无法开释。

  漫衍式事件

  MongoDB 4.0 支撑副本集多文档事件,并打算在 4.2 版本支撑分片集群事件功效。下图是从 MongoDB 3.0 引入 WiredTiger 到 4.0 支撑多文档事件的功效迭代图,可以发明一盘大棋即将上线,敬请期待。

实用干货: MongoDB 4.0 事件实现解析