关于mongodb:MongoDB-事务复制和分片的关系

4次阅读

共计 4163 个字符,预计需要花费 11 分钟才能阅读完成。

摘要:本文尝试对 Mongo 的复制和分布式事务的原理进行形容,在必要的中央,对实现的正确性进行论证,心愿能为 MongoDB 内核爱好者提供一些参考。

1. 前言

  • MongoDB 基于 wiredTiger 提供的泛化 SI 的性能,重构了 readHistory(readMajority)的能力
  • 基于 wiredTiger 提供的 AllCommittedTimestamp API, 重构了前缀统一的主从复制(Prefix-Consistent-Replication)
  • 引入混合逻辑时钟 (HLC),每个节点(Mongos/Mongod) 的逻辑时钟维持在靠近的值,基于此实现 ChangeStream, 联合 HLC 与 CLOCK-SI,实现分布式事务,HLC和泛化 SI,CLOCK-SI 两篇 Paper 能够作为了解 MongoDB 的设计的实践参考(这里并没有说 MongoDB 是 Paper 的实现)。

本文尝试对 Mongo 的复制和分布式事务的原理进行形容,在必要的中央,对实现的正确性进行论证,心愿能为 MongoDB 内核爱好者提供一些参考。

2.MongoDB 正本集事务介绍

  • MongoDB 正本集的事务
  • MongoDB 正本集的复制是基于 raft 协定,相比于 Paxos,raft 协定实现简略,然而 raft 协定只反对 single-master,对应的,MongoDB 的正本集是主从架构,而且只有 主节点反对写入操作。MongoDB 正本集的事务管理,包含冲突检测,事务提交等要害操作,都只在主节点上实现。也就是说正本集的事务在事务管理方面,跟单节点逻辑基本一致。
  • MongoDB 的事务,依然是实现了 ACID 四个个性,MongoDB 应用 SI 作为事务的隔离级别。

3.SI 的简介

  • SI,即 SnapshotIsolation,中文称为 快照隔离, 是一种 mvcc 的实现机制,它在 1995 年的 A Critique of ANSI SQL Isolation Levels 中被正式提出。因快照工夫点的选取上的不同,又分为 Conventional Si 和 Generalized SI。

CSI(Convensional SI)

  • CSI 选取以后最新的零碎快照作为事务的读取快照
  • 就是在事务开始的时候,取得以后 db 最新的 snapshot,作为事务的读取的 snapshot,
  • snapshot(Ti) = start(Ti)
  • 能够缩小写事务抵触产生的概率,并且提供读事务读取最新数据的能力
  • 个别咱们说一个数据库反对 SI 隔离级别,其实默认是说反对 CSI。比方 RocksDB 反对的 SI 就是 CSI,WiredTiger 在 3.0 版本之前反对的 SI 也是 CSI。

GSI(Generalized SI)

  • GSI 抉择历史上的数据库快照作为事务的读取快照,因而 CSI 能够看作 GSI 的一个特例。
  • 在复制集的状况下,思考 CSI,对于主节点上的事务,每次事务的开始工夫选取的零碎 最新的 快照,然而对于其余从节点来说, 并没有 对立的“ 最新的”快照这个概念。泛化的快照实际上是基于快照观测失去的,对于以后事务来说,咱们通过选取适合的 更早工夫的快照,能够让 从节点上的事务正确且无提早的执行。
  • 举例如下:
  • 例如以后数据库的状态是,S={T1, T2, T3}, 当初要开始执行 T4,
  • 如果咱们晓得 T4 要批改的值,在 T3 上没有被批改, 那么咱们在执行 T4 的时候,就能够依照 T2 commit 后的 snashot 进行读取。
  • 如何抉择更早的工夫点,须要满足上面的规定,

  • 符号定义
  • Ti: 事务 i
  • Xi: 被事务 i 批改过的 X 变量
  • snapshot(Ti): 事务 i 的选取的快照工夫
  • start(Ti): 事务 i 的开始工夫
  • commit(Ti): 事务 i 的提交工夫
  • abort(Ti): the time when Ti is aborted.
  • end(Ti): the time when Ti is committed or aborted.

公式解释

读规定

  • G1.1, 如果变量 X 被本事务批改了值且读取到了新的值,那么 读操作肯定在写操作前面;
  • G1.2, 如果事务 i 读取了事务 j 更新的变量的 X,那么肯定不会有事务 i 更新 X 的操作,在事务 i 读取了事务 j 更新的变量的 X 这个操作后面;
  • G1.3, 事务 j 的提交工夫早于事务 i 的快照工夫;
  • G1.4, 对于任意一个会更新变量 X 的事务 k,那么这个事务 k 肯定满足,要么事务 k 的提交工夫小于事务 j,要么这个事务 k 的提交工夫大于事务 i。

写规定

  • G2, 对于任意曾经在提交历史里的两个事务,Ci, Cj, 那么肯定能够保障当 事务 j 的 commit 工夫戳在 事务 i 的观测时间段内时(snapshot(Ti), commit(Ti)),那么他们更新的变量交加肯定为空。

PCSI(PREFIX-CONSISTENT SNAPSHOT ISOLATION SI)

  • GSI 只是定义了一个范畴的 range,都能够作为 SI 应用,并没有定义具体应该抉择哪个 SI。
  • PCSI 是为了复制集而设计的。对于一个事务 Ti 要开 S 节点开始运行,那么 S 节点将必须蕴含这个事务所须要的所有前置事务都必须运行且提交。
  • 相比拟于 GSI,PCSI 的读规定,额定减少了 P1.5 规定。

  • SI 的提交工夫戳设置,根据 A Critique of ANSI SQL Isolation Levels 中的形容,提交工夫戳的设置应该是枯燥递增的。新设置的工夫戳,应该大于零碎中曾经存在的开始工夫戳和提交工夫戳。
  • SI 读取工夫戳的设置,必须保障比以后零碎中正在运行的事务的最小的提交工夫戳还要小,因为一旦大于以后零碎中正在运行事务的最小的提交工夫戳,那么这个读事务读取到的数据就是未定义的,取决于读事务启动的工夫,而不是 snapshot 的工夫,这违反了 一致性的要求。举例如下
  • 以后曾经实现的事务是 T1,正在运行的事务是 T2,将要运行的读事务是 T3, 如果 T3 的读工夫戳大于 T2 事务提交工夫戳,并且 T2 事务正在运行,等到 T2 事务执行完后。咱们察看这个 database,就会发现 他违反了 GSI,

事务执行程序如下所示是:

T1 commited and commitTs(1) -> T2 start -> T2 set commitTs(2) -> T3 start -> T3 set snapshotTs(3) -> T3 commit -> pointA -> T2 commit -> pointB

那么可知,T3 事务理论读取的值是 T1 事务的值。但依据 pointB 点来看 GSI 的读规定 1.4 的要求,会发现,如果 T3 读到 T1 的事务的批改,那么必然要求,T3 和 T1 之间没有空洞。但实际上 T2 是落在了 T3 和 T1 之间的,也就是说,违反了 GSI 1.4 的读规定。

  • 所以咱们必须规定, SI 读取工夫戳的设置,必须保障比以后零碎中正在运行的事务的最小的提交工夫戳还要小。

4.MongoDB 正本集工夫戳利用

MongoDB 4.0 的复制也是利用工夫戳个性解决了 3.x 系列 MongoDB 从节点复制造成从节点性能降落的要害计划。

  • MongoDB oplog 乱序问题
  • MongoDB 主备节点的数据同步并不基于 WiredTiger 的 wal 日志来做的。相同,mongodb 会将每次操作的数据变更写入到一个叫做 oplog 的汇合里。
  • oplog 这个汇合,尽管名字带有 log,但实际上,它是一个 MongoDB 的表,对 oplog 的写入,并不是 append 的形式批改的,而是呈现出一种尾部乱序的形式。
  • 对于 oplog 来说,oplog 的读取程序是依照 TS 字段来排序的,跟下层的提交程序无关。所以存在后开始的事务,在 oplog 先读取的场景。
  • oplog 空洞
  • 因为呈现了乱序,所以从节点在读取 oplog 的时候,就会在某些工夫点呈现空洞。举例如下:
  • 工夫点 1: oplog 程序为: Ta -> Tb, 此时零碎中还有一个事务 Tc 在运行
  • 工夫点 2: oplog 程序为: Ta -> Tc -> Tb, 当 Tc 运行完结后,因为 ts 的程序,看起来是将 Tc 插入到了 Ta 和 Tb 之间。
  • 那么当 从节点 在工夫点 1 reply 到 Tb 的时候,实际上是漏了 Tc 的,这个就是 oplog 的空洞,他产生的起因是因为,从节点如果每次读取 oplog 最新的数据,就有可能会失去一个不间断的数据, 例如 工夫点 1 上 Ta-> Tb. 这就是 oplog 空洞。
  • 在具体复制逻辑中,咱们必须想方法来从节点读取到含有空洞的 oplog 数据。这也是 GSI 的要求,snapshot 的选取不能含有空洞。
  • 因为 oplog 的 Ts 是 mongo 下层给的,咱们很容易晓得哪些事务有哪些 ts,咱们再将这个 ts 作为事务的 commitTs 放到 oplog 存储的事务里,这样咱们读取 oplog 的程序事务的可见性程序相一致了,在这种状况下,咱们就能够 依据 沉闷事务列表,就能够将 oplog 分为两个局部,
  • 假如沉闷 commitTs 列表的事务是 {T10, T11, T12}, 沉闷事务列表是 {T10, T11, T12, T13, T14}, 那么意味着,目前有 T10, T11, T12, T13, T14 再运行,并且 T10, T11, T12 曾经设置了 commitTs,又因为 下面探讨的 commitTs 是枯燥递增的,那么咱们可知, T13, T14 的 commitTs 肯定大于 maxCommitTs(T10, T11, T12), 而且咱们还可知,minCommitTs(T10,T11,T12) 就是全局最小的 commitTs,而小于这些的 commitTs 的事务,因为不在 沉闷事务列表里了,示意曾经提交了,那么咱们能够晓得,oplog ts 在 全局最小的 commitTs 之前的,就是都提交了的,oplog 依照 commitTs 排序后,如下所示

… Tx | minCommitTs(T10,T11,T12) | …

咱们能够晓得 T9,或者说小于 minCommitTs(T10,T11,T12) 都是无空洞,因为零碎不会再提交小于 minCommitTs(T10,T11,T12) 的事务到 oplog 里了,所以从节点能够间接复原这里的数据。

  • 下面说的 oplog minCommitTs(T10,T11,T12) 在 mongodb 里,就是非凡的 timestamp,这个后文会讲。
  • 通过下面的计划,咱们能够解决空洞的问题。这个时候,从节点每次复原数据的时候,将读取的 snapshot,设置为上一次复原的 Ts(同样也是无空洞的 Ts),这样的话,从节点的复原数据和读取数据也就做到了互不抵触。从而解决了 3.x 系列的 从节点同步数据造成节点性能降落的问题。

点击关注,第一工夫理解华为云陈腐技术~

正文完
 0