[mongo]事务开发:写操作事务

事务开发:写操作事务

writeConcern

  • 决定一个写操作落到多少个节点上才算成功
    • 0:发起写操作,不关心是否成功
    • 1~集群最大数据节点数:写操作需要被复制到指定节点数才算成功;
    • majority:写操作需要被复制到大多数节点上才算成功
  • 发起写操作的程序将阻塞到写操作到达指定的节点数为止

默认行为

  • 3 节点复制集不作任何特别设定
  • image

w: “majority”

  • 大多数节点确认模式
  • image

w: “all”

  • 全部节点确认模式
  • image

j:true

  • writeConcern 可以决定写操作到达多少个节点才算成功,journal 则定义如何才算成
    • true: 写操作落到 journal 文件中才算成功
    • false: 写操作到达内存即算作成功
  • image

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 驱动程序 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(向前兼容原因)。

readConcern: majority

  • 只读取大多数据节点上都提交了的数据

  • 场景

    • 集合中原有文档 {x: 0};将x值更新为 1;
    • 各节点上应用{readConcern: “majority”} 来读取数据
    • image
    • image
  • 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 最大差别是保证绝对的操作线性顺序 –
    在写操作自然时间后面的发生的读,一定可以读到之前的写
    • image

readConcern: snapshot

  • {readConcern: “snapshot”} 只在多文档事务中生效
  • 不出现脏读
  • 不出现不可重复读
  • 不出现幻读
  • 因为所有的读都将使用同一个快照,直到事务提交为止该快照才被释放

readConcern: 小结

  • available:读取所有可用的数据
  • local:读取所有可用且属于当前分片的数据,默认设置
  • majority:数据读一致性的充分保证,可能你最需要关注的
  • linearizable:增强处理 majority 情况下主节点失联时候的例外情况
  • snapshot:最高隔离级别,接近于 Seriazable

事务开发:多文档事务

  • 对事务的使用原则应该是:能不用尽量不用

MongoDB ACID 多文档事务支持

  • image

使用方法

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 用于实现变更追踪的解决方案,类似于关系数据库的触发器
  • image

实现原理

  • 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值