事务开发:写操作事务
writeConcern
- 决定一个写操作落到多少个节点上才算成功
- 0:发起写操作,不关心是否成功
- 1~集群最大数据节点数:写操作需要被复制到指定节点数才算成功;
- majority:写操作需要被复制到大多数节点上才算成功
- 发起写操作的程序将阻塞到写操作到达指定的节点数为止
默认行为
- 3 节点复制集不作任何特别设定
w: “majority”
- 大多数节点确认模式
w: “all”
- 全部节点确认模式
j:true
- writeConcern 可以决定写操作到达多少个节点才算成功,journal 则定义如何才算成
功- true: 写操作落到 journal 文件中才算成功
- false: 写操作到达内存即算作成功
writeConcern 的意义
- 命令行
db.test.insert( {count: 1}, {writeConcern: {w: "majority"}})
db.test.insert( {count: 1}, {writeConcern: {w: 3 }})
db.test.insert( {count: 1}, {writeConcern: {w: 4 }})
db.test.insert( {count: 1}, {writeConcern: {w: 3}})
db.test.insert( {count: 1}, {writeConcern: {w: 3, wtimeout:3000 }})
- 对于5节点,3,4,5,majority是安全的
注意事项
- 虽然多于半数的 writeConcern 都是安全的,但通常只会设置 majority,因为这是
等待写入延迟时间最短的选择; - 不要设置 writeConcern 等于总节点数,因为一旦有一个节点故障,所有写操作都
将失败; - writeConcern 虽然会增加写操作延迟时间,但并不会显著增加集群压力,因此无论
是否等待,写操作最终都会复制到所有节点上。设置 writeConcern 只是让写操作
等待复制后再返回而已; - 应对重要数据应用 {w: “majority”},普通数据可以应用 {w: 1} 以确保最佳性能
事务开发:读操作事务之一 readPreference
问题
- 读取数据的过程从哪里读
- 由 readPreference 来解决
- 什么样的数据可以读?
- 第二个问题则是由 readConcern 来解决
什么是 readPreference
- readPreference 决定使用哪一个节点来满足正在发起的读请求,可选值包括:
- primary: 只选择主节点
- primaryPreferred:优先选择主节点,如果不可用则选择从节点
- secondary:只选择从节点
- secondaryPreferred:优先选择从节点,如果从节点不可用则选择主节点
- nearest:选择最近的节点
场景举例
- 用户下订单后马上将用户转到订单详情页——primary/primaryPreferred。因为此
时从节点可能还没复制到新订单 - 用户查询自己下过的订单——secondary/secondaryPreferred。查询历史订单对
时效性通常没有太高要求; - 生成报表——secondary。报表对时效性要求不高,但资源需求大,可以在从节点
单独处理,避免对线上用户造成影响
readPreference 与 Tag
- readPreference 只能控制使用一类节点
- Tag 则可以将节点选择控制到一个或几个节点
- 场景
- 一个 5 个节点的复制集, 个节点硬件较好,专用于服务线上客户, 个节点硬件较差,专用于生成报表
- Tag 来达到这样的控制目的
- 为 3 个较好的节点打上 {purpose: “online”};
- 为 2 个较差的节点打上 {purpose: “analyse”}
- 在线应用读取时指定 online,报表读取时指定 reporting
readPreference 配置
- MongoDB 的连接串参数
- mongodb://host1:27107,host2:27107,host3:27017/?replicaSet=rs&readPre
ference=secondary
- mongodb://host1:27107,host2:27107,host3:27017/?replicaSet=rs&readPre
- 通过 MongoDB 驱动程序 API
- MongoCollection.withReadPreference(ReadPreference readPref)
- Mongo Shell
- db.collection.find({}).readPref( “secondary” )
注意事项
- 指定 readPreference 时也应注意高可用问题。例如将 readPreference 指定 primary,则发生
故障转移不存在 primary 期间将没有节点可读。如果业务允许,则应选择 primaryPreferred; - 使用 Tag 时也会遇到同样的问题,如果只有一个节点拥有一个特定 Tag,则在这个节点失效时
将无节点可读。这在有时候是期望的结果,有时候不是 - Tag 有时需要与优先级、选举权综合考虑
事务开发:读操作事务之二 readConcern
什么是readConcern
- readPreference 选择了指定的节点后,readConcern 决定这个节点上的数据哪些
是可读的,类似于关系数据库的隔离级别 - 可选值包括
- available:读取所有可用的数据;
- local:读取所有可用且属于当前分片的数据
- majority:读取在大多数节点上提交完成的数据
- linearizable:可线性化读取文档
- snapshot:读取最近快照中的数据;
readConcern: local 和 available
-
复制集中 local 和 available 是没有区别的,区别主要体现在分片集上
-
场景:
- 一个 chunk x 正在从 shard1 向 shard2 迁移
- 整个迁移过程中 chunk x 中的部分数据会在 shard1 和 shard2 中同时存在,但源分片 shard1仍然是
chunk x 的负责方- 所有对 chunk x 的读写操作仍然进入 shard1
- config 中记录的信息 chunk x 仍然属于 shard1
- 此时如果读 shard2,则会体现出 local 和 available 的区别
- local:只取应该由 shard2 负责的数据(不包括 x)
- available:shard2 上有什么就读什么(包括 x);
-
注意事项
- 虽然看上去总是应该选择 local,但毕竟对结果集进行过滤会造成额外消耗。在一些
无关紧要的场景(例如统计)下,也可以考虑 available - MongoDB <=3.6 不支持对从节点使用 {readConcern: “local”};
- 从主节点读取数据时默认 readConcern 是 local,从从节点读取数据时默认
readConcern 是 available(向前兼容原因)。
- 虽然看上去总是应该选择 local,但毕竟对结果集进行过滤会造成额外消耗。在一些
readConcern: majority
-
只读取大多数据节点上都提交了的数据
-
场景
- 集合中原有文档 {x: 0};将x值更新为 1;
- 各节点上应用{readConcern: “majority”} 来读取数据
-
readConcern: majority 的实现方式
- 对于要求 majority 的读操作,它将返回 x=0;
- 对于不要求 majority 的读操作,它将返回 x=1;
- 如何实现
- 节点上维护多个 x 版本,MVCC 机制
- MongoDB 通过维护多个快照来链接不同的版本
- 每个被大多数节点确认过的版本都将是一个快照
- 快照持续到没有人使用为止才被删除
-
验证命令行
• db.test.save({“A”:1})
• db.test.find().readConcern(“local”)
• db.test.find().readConcern(“majority”)
readConcern: majority 与脏读
-
MongoDB 中的回滚
- 写操作到达大多数节点之前都是不安全的,一旦主节点崩溃,而从节还没复制到该
次操作,刚才的写操作就丢失了 - 把一次写操作视为一个事务,从事务的角度,可以认为事务被回滚了
- 写操作到达大多数节点之前都是不安全的,一旦主节点崩溃,而从节还没复制到该
-
可能发生回滚的前提下考虑脏读问题
- 如果在一次写操作到达大多数节点前读取了这个写操作,然后因为系统故障该操作回滚了,则发生了脏读问题
-
解决方案
- 使用 {readConcern: “majority”} 可以有效避免脏读
readConcern: 如何实现安全的读写分离
- 场景
- 向主节点写入一条数据
- 立即从从节点读取这条数据
- 如何保证自己能够读到刚刚写入的数据
- 不能解决问题的方式
db.orders.insert({ oid: 101, sku: ”kite", q: 1})
db.orders.find({oid:101}).readPref("secondary")
- 解决问题的方式
db.orders.insert({ oid: 101, sku: "kiteboar", q: 1}, {writeConcern:{w: "majority”}})
db.orders.find({oid:101}).readPref(“secondary”).readConcern("majority")
readConcern: linearizable
- 只对读取单个文档时有效
- 可能导致非常慢的读,因此总是建议配合使用 maxTimeMS
- 和 majority 最大差别是保证绝对的操作线性顺序 –
在写操作自然时间后面的发生的读,一定可以读到之前的写
readConcern: snapshot
- {readConcern: “snapshot”} 只在多文档事务中生效
- 不出现脏读
- 不出现不可重复读
- 不出现幻读
- 因为所有的读都将使用同一个快照,直到事务提交为止该快照才被释放
readConcern: 小结
- available:读取所有可用的数据
- local:读取所有可用且属于当前分片的数据,默认设置
- majority:数据读一致性的充分保证,可能你最需要关注的
- linearizable:增强处理 majority 情况下主节点失联时候的例外情况
- snapshot:最高隔离级别,接近于 Seriazable
事务开发:多文档事务
- 对事务的使用原则应该是:能不用尽量不用
MongoDB ACID 多文档事务支持
使用方法
try (ClientSession clientSession = client.startSession()) {
clientSession.startTransaction();
collection.insertOne(clientSession, docOne);
collection.insertOne(clientSession, docTwo);
clientSession.commitTransaction();
}
事务的隔离级别
- 事务完成前,事务外的操作对该事务所做的修改不可访问
- 如果事务内使用 {readConcern: “snapshot”},则可以达到可重复读Repeatable Read
事务写机制
- 当一个事务开始后,如果事务要修改的文档在事务外部被修改过,则事务修改这个文档时会触发 Abort 错误,因为此时的修改冲突了;
- 这种情况下,只需要简单地重做事务就可以了;
- 如果一个事务已经开始修改一个文档,在事务以外尝试修改同一个文档,则事务以外的修改会等待事务完成才能继续进行(write-wait.md实验)
Change Stream
- Change Stream 是 MongoDB 用于实现变更追踪的解决方案,类似于关系数据库的触发器
实现原理
- Change Stream 是基于 oplog 实现的
- 它在 oplog 上开启一个 tailable cursor 来追踪所有复制集上的变更操作
- 最终调用应用中定义的回调函数
- 被追踪的事件包括
- insert/update/delete
- drop
- rename
- dropDatabase
- invalidate : drop/rename/dropDatabase 将导致 invalidate 被触发,并关闭 change stream
Change Stream 与可重复读
- Change Stream 只推送已经在大多数节点上提交的变更操作
- 未开启 majority readConcern 的集群无法使用 Change Stream
- 当集群无法满足 {w: “majority”} 时,不会触发 Change Stream(例如 PSA 架构中的 S 因故障宕机)
Change Stream 变更过滤
- 如果只对某些类型的变更事件感兴趣,可以使用使用聚合管道的过滤步骤过滤事件
var cs = db.collection.watch([{
$match: {
operationType: {
$in: ['insert', 'delete']
} }
}])
- db.collection.watch([],
{maxAwaitTimeMS: 30000}).pretty()- [] 表示查询条件,这里为空数组,表示不对变化进行筛选,即监视集合中的所有变化。
- {maxAwaitTimeMS: 30000} 用于指定最大等待时间(单位为毫秒),即如果在指定时间内没有发生变化,则命令将会超时
- pretty() 用于将输出结果进行格式化,使其更易于阅读
Change Stream 故障恢复
- 从上次中断的地方继续获取变更流,只需要保留上次
变更通知中的 _id 即可 - 一次 Change Stream 回调所返回的数据。每
条这样的数据都带有一个 _id,这个 _id 可以用于断点恢
复
Change Stream 使用场景
- 在源集群中订阅 Change Stream,一旦得到任何变更立即写入目标集群
- 微服务联动——当一个微服务变更数据库时,其他微服务得到通知并做出相应的变更
- 其他任何需要系统联动的场景
注意事项
- Change Stream 依赖于 oplog,因此中断时间不可超过 oplog 回收的最大时间窗
- 在执行 update 操作时,如果只更新了部分数据,那么 Change Stream 通知的也是增量部分
- 同理,删除数据时通知的仅是删除数据的 _id
文章评论