因果一致性、读取和写入关注点
使用MongoDB的因果一致性客户端会话,不同的读取和写入关注点组合提供不同的因果一致性保证。
以下表格列出了各种组合提供的具体保证
如果您想要具有数据持久性的因果一致性,如表所示,则只有具有"majority"
读取关注点的读取操作和具有"majority"
写入关注点的写入操作才能保证所有四个因果一致性保证。也就是说,因果一致性客户端会话只能保证以下操作的因果一致性
具有
"majority"
读取关注点的读取操作;换句话说,返回已被大多数副本集成员确认且持久化的数据的读取操作。具有
"majority"
写入关注点的写入操作;换句话说,请求确认操作已应用于副本集的大多数投票成员的写入操作。
如果您想要不涉及数据持久性的因果一致性(这意味着写入可能会回滚),则具有{ w: 1 }
写入关注点的写入操作也可以提供因果一致性。
注意
读取和写入关注点的其他组合可能在某些情况下也满足所有四个因果一致性保证,但并不一定在所有情况下都满足。
读取关注度“"majority"
”和写入关注度“"majority"
”确保即使在两个副本集成员暂时认为自己是主节点的情况下(例如网络分区的情况下),也能保持四个因果一致性保证。虽然两个主节点都可以使用{ w: 1 }
写入关注度完成写入,但只有一个主节点能够使用"majority"
写入关注度完成写入。
例如,考虑一个网络分区将一个五成员副本集分成两部分的情况
示例
上述分区情况下
带有“
"majority"
”写入关注度的写入可以在新主节点P
new上完成,但不能在旧主节点P
old上完成。带有
{ w: 1 }
写入关注度的写入可以在旧主节点P
old或新主节点P
new上完成。然而,当这些成员与副本集的其余部分重新建立通信时,对P
old的写入(以及复制到S
1的写入)将回滚。在旧主节点
P
new上成功使用“"majority"
”写入关注度写入后,带有“"majority"
”读取关注度的因果一致性读取可以观察到在P
new、S
2和S
3上的写入。一旦它们可以与副本集的其余部分通信并从副本集的其他成员同步,读取还可以观察到在旧主节点P
old和S
1上的写入。在分区期间对P
old和/或复制到S
1的任何写入都将回滚。
场景
为了说明读取和写入关注度的要求,以下场景中客户端向副本集发出一系列操作,这些操作具有不同的读取和写入关注度组合
读取关注 "majority"
和写入关注 "majority"
在因果一致会话中使用读取关注 "majority"
和写入关注 "majority"
提供以下因果一致性保证
✅读取自己的写入✅单调读取✅单调写入✅写入跟随读取
注意
场景 1(读取关注 "majority" 和写入关注 "majority")
在两个主副本的短暂期间,因为只有 P
new 可以使用 { w: "majority" }
写入关注来满足写入,客户端会话可以成功发出以下操作序列
序列 | 示例 |
---|---|
1. 在 P new 上使用写入关注 "majority" 写入 12. 使用读取关注 "majority" 读取 1 到 S 23. 在 P new 上使用写入关注 "majority" 写入 24. 使用读取关注 "majority" 读取 2 到 S 3 | 对于项目 A ,将 qty 更新为 50 。读取项目 A 。对于 qty 小于或等于 50 的项目,将 restock 更新为 true 。读取项目 A 。 |
✅ 读取自己的写入 | 读取 1 从 S 2 中的数据,反映了 Write 1 之后的某个状态。读取 2 从 S 3 中的数据,反映了 Write 1 后紧接着 Write 2 的某个状态。 |
✅ 单调读取 | 读取 2 从 S 3 中的数据,反映了 Read 1 之后的某个状态。 |
✅ 单调写入 | 写入 2 更新 P new 上的数据,反映了 Write 1 之后的某个状态。 |
✅ 写入跟随读取 | 写入 2 更新 P new 上的数据,反映了 Read 1 之后的某个状态(意味着较早的状态反映了 Read 1 读取的数据)。 |
注意
场景 2(读取关注点 "majority" 和写入关注点 "majority")
考虑一个替代序列,其中 Read 1 使用读取关注点 "majority"
转发到 S
1。
序列 | 示例 |
---|---|
1. 在 P new 上使用写入关注 "majority" 写入 12. Read 1 使用读取关注点 "majority" 到 S 1。3. 在 P new 上使用写入关注 "majority" 写入 24. 读取 2 使用读取关注点 "majority" 到 S 3。 | 对于项目 A ,将 qty 更新为 50 。读取项目 A 。对于 qty 小于或等于 50 的项目,将 restock 更新为 true 。读取项目 A 。 |
在这个序列中,Read 1 无法返回,直到 P
old 上的多数提交点已经前进。这不能发生,直到 P
old 和 S
1 可以与复制集的其他成员通信;在那时,P
old 已经退位(如果尚未退位),并且两个成员从复制集的其他成员那里同步(包括 Write 1)。
✅ 读取自己的写入 | Read 1 反映了 Write 1 之后的某个状态的数据,尽管在网络分区修复并且成员从复制集的其他成员那里同步之后。 读取 2 从 S 3 中的数据,反映了 Write 1 后紧接着 Write 2 的某个状态。 |
✅ 单调读取 | 读取 2 从 S 3 读取数据,反映了 Read 1 之后的某个状态(意味着较早的状态反映了 Read 1 读取的数据)。 |
✅ 单调写入 | 写入 2 更新 P new 上的数据,反映了 Write 1 之后的某个状态。 |
✅ 写入跟随读取 | 写入 2 更新 P new 上的数据,反映了 Read 1 之后的某个状态(意味着较早的状态反映了 Read 1 读取的数据)。 |
读取关注点 "majority"
和写入关注点 {w: 1}
在因果一致性会话中使用读取关注点 "majority"
和写入关注点 { w: 1 }
提供以下因果一致性保证(如果您想实现具有数据持久性的因果一致性)
❌读取自己的写入✅单调读取❌单调写入✅写入跟随读取
如果您想实现无数据持久性的因果一致性:
✅读取自己的写入✅单调读取✅单调写入✅写入跟随读取
注意
场景 3(读取关注点 "majority" 和写入关注点 {w: 1})
在双主切换的短暂期间,因为两个主节点 P
old 和 P
new 都可以满足写关注 { w: 1 }
,客户端会话可以成功执行以下操作序列,但不具有因果关系一致性 如果您希望具有数据持久性的因果关系一致性
序列 | 示例 |
---|---|
1. 以写关注 { w: 1 } 将 1 写入 P old2. 使用读取关注 "majority" 读取 1 到 S 23. 以写关注 { w: 1 } 将 2 写入 P new4. 读取 2 使用读取关注点 "majority" 到 S 3。 | 对于项目 A ,将 qty 更新为 50 。读取项目 A 。对于 qty 小于或等于 50 的项目,将 restock 更新为 true 。读取项目 A 。 |
在这个序列中,
读取 1 无法返回,直到
P
new 的多数提交点在写入 1 的时间之后向前推进。读取 2 无法返回,直到
P
new 的多数提交点在写入 2 的时间之后向前推进。当网络分区修复时,写入 1 将回滚。
➤ 如果您希望具有数据持久性的因果关系一致性
❌ 读取自己的写入 | 读取 1 从 S 2 读取数据,这些数据不反映写入 1 后的状态。 |
✅ 单调读取 | 读取 2 从 S 3 读取数据,反映了 Read 1 之后的某个状态(意味着较早的状态反映了 Read 1 读取的数据)。 |
❌ 单调写入 | 写入 2 在 P new 上更新数据,这些数据不反映写入 1 后的状态。 |
✅ 写入跟随读取 | 写入 2 在 P new 上更新数据,这反映了读取 1 后的状态(这意味着较早的状态反映了读取 1 读取的数据)。 |
➤ 如果您想实现无数据持久性的因果一致性
✅ 读取自己的写入 | 读取 1 从 S 2 读取数据,这些数据反映了写入 1 后的等效状态,然后是写入 1 的回滚。 |
✅ 单调读取 | 读取 2 从 S 3 读取数据,反映了 Read 1 之后的某个状态(意味着较早的状态反映了 Read 1 读取的数据)。 |
✅ 单调写入 | 写入 2 在 P new 上更新数据,这等效于写入 1 后的状态,然后是写入 1 的回滚。 |
✅ 写入跟随读取 | 写入 2 在 P new 上更新数据,这反映了读取 1 后的状态(这意味着其较早的状态反映了读取 1 读取的数据)。 |
注意
场景 4(读取关注“多数”和写关注 {w: 1})
考虑一个替代序列,其中 Read 1 使用读取关注点 "majority"
转发到 S
1。
序列 | 示例 |
---|---|
1. 以写关注 { w: 1 } 将 1 写入 P old2. Read 1 使用读取关注点 "majority" 到 S 1。3. 以写关注 { w: 1 } 将 2 写入 P new4. 读取 2 使用读取关注点 "majority" 到 S 3。 | 对于项目 A ,将 qty 更新为 50 。读取项目 A 。对于 qty 小于或等于 50 的项目,将 restock 更新为 true 。读取项目 A 。 |
在这个序列中
读取 1 无法返回,直到
S
1 的多数提交点向前推进。这不可能发生,直到P
old 和S
1 可以与其他副本集通信。这时,P
old 已经降级(如果尚未降级),写入 1 从P
old 和S
1 上回滚,并且两个成员从副本集的其他成员那里同步。
➤ 如果您希望具有数据持久性的因果关系一致性
❌ 读取自己的写入 | 读取 1 读取的数据不反映已回滚的写入 1 的结果。 |
✅ 单调读取 | 读取 2 从 S 3 读取数据,这些数据反映了读取 1 后的状态(这意味着较早的状态反映了读取 1 读取的数据)。 |
❌ 单调写入 | 写入 2 在 P new 上更新数据,这些数据不反映写入 1 后的状态,该写入 1 在写入 2 之前发生但已回滚。 |
✅ 写入跟随读取 | 写入 2 在 P new 上更新数据,这反映了读取 1 后的状态(这意味着其较早的状态反映了读取 1 读取的数据)。 |
➤ 如果您想实现无数据持久性的因果一致性
✅ 读取自己的写入 | 读取 1 返回的数据反映了写入 1 的最终结果,因为写入 1 最终回滚。 |
✅ 单调读取 | 读取 2 从 S 3 中读取数据,该数据反映了在读取 1 之后的状态(意味着早期状态反映了读取 1 读取的数据)。 |
✅ 单调写入 | 写入 2 在 P new 上更新数据,这等效于写入 1 后的状态,然后是写入 1 的回滚。 |
✅ 写入跟随读取 | 写入 2 在 P new 上更新数据,这反映了读取 1 后的状态(这意味着较早的状态反映了读取 1 读取的数据)。 |
读取关注点 "local"
和写入关注点 {w: 1}
在因果一致会话中,使用读取关注点 "local"
和写入关注点 { w: 1 }
不能保证因果一致性。
❌读取自己的写入❌单调读取❌单调写入❌写入跟随读取
此组合可能在某些情况下满足所有四个因果一致性保证,但在所有情况下不一定。
注意
场景 5(读取关注点 "local" 和写入关注点 {w: 1})
在此短暂期间,由于 P
old 和 P
new 都可以使用 { w: 1 }
写入关注点完成写入,客户端会话可以成功发出以下操作序列,但不一定是因果一致的
序列 | 示例 |
---|---|
对于项目 A ,将 qty 更新为 50 。读取项目 A 。对于 qty 小于或等于 50 的项目,将 restock 更新为 true 。读取项目 A 。 |
❌读取自己的写入 | 读取 2 从 S 3 读取的数据仅反映了写入 2 之后的状态,而不是写入 1 后跟写入 2 的状态。 |
❌单调读取 | 读取 2 从 S 3 读取的数据不反映读取 1 之后的状态(意味着早期状态不反映读取 1 读取的数据)。 |
❌单调写入 | 写入 2 在 P new 上更新数据,这些数据不反映写入 1 后的状态。 |
❌写入跟随读取 | 写入 2 更新 P new 上的数据,该数据不反映读取 1 之后的状态(意味着早期状态不反映读取 1 读取的数据)。 |
读取关注度 "local"
和写入关注度 "majority"
在因果一致性会话中使用读取关注度 "local"
和写入关注度 "majority"
提供以下因果一致性保证
❌读取自己的写入❌单调读取✅单调写入❌写入跟随读取
此组合可能在某些情况下满足所有四个因果一致性保证,但在所有情况下不一定。
注意
场景6(读取关注度 "local" 和写入关注度 "majority")
在此短暂期间,因为只有 P
new 能够使用 { w: "majority" }
写入关注度完成写入,客户端会话可以成功发出以下操作序列,但不具有因果关系一致性
序列 | 示例 |
---|---|
1. 在 P new 上使用写入关注 "majority" 写入 12. 使用读取关注点 "local" 从 S 1 进行读取 13. 在 P new 上使用写入关注 "majority" 写入 24. 使用读取关注点 "local" 从 S 3 进行读取 2 | 对于项目 A ,将 qty 更新为 50 。读取项目 A 。对于 qty 小于或等于 50 的项目,将 restock 更新为 true 。读取项目 A 。 |
❌读取自己的写入。 | 从 S 1 读取 1 次数据,该数据未反映写入 1 后的状态。 |
❌单调读取。 | 读取 2 从 S 3 读取的数据不反映读取 1 之后的状态(意味着早期状态不反映读取 1 读取的数据)。 |
✅单调写入 | 写入 2 更新 P new 上的数据,反映了 Write 1 之后的某个状态。 |
❌写入跟随读取。 | 写入 2 更新 P new 上的数据,该数据不反映读取 1 之后的状态(意味着早期状态不反映读取 1 读取的数据)。 |