1.Mongo中的锁机制

意向锁(I锁)

意向锁协议,是一种对树形(层级)资源进行并发控制的协议。目的是为了更好地解决树形逻辑架构应用的并发控制问题。
换句话说,存在意向锁的应用,它的逻辑架构应该为一个树形的逻辑架构。就像mysql、以及今天要说的mongo。通俗来讲,在获取读写锁之前,需要先通过意向锁判定能否获取对应的锁
它由”操作约定”和”冲突矩阵”两部分组成。
mongo中意向锁的操作约定:

  1. 对一个节点加IX/X锁时,必须先(递归)获取其父节点的IX锁。
  2. 对一个节点加IS/S锁时,必须先(递归)获取其父节点的IS锁。

mongo中意向锁的冲突矩阵:
image.png

常规锁(S锁、X锁、IS锁、IX锁)

  1. S锁:读锁,对文档加上读锁后,其他事务可以读取该文档,但无法进行对该文档的写操作
  2. X锁:写锁,对文档加上写锁后,其他事务无法对该文档进行任何操作,只能等待获取锁后,进行操作
  3. IS锁:意向读锁,对资源存在读取意向,具体互斥原则参考上图
  4. IX锁:意向写锁,对资源存在写意向,具体互斥原则参考上图

    2.Mongo中的mvcc机制

    mvcc中文全称多版本并发控制, 与mysql中的mvcc实现原理大致一样
    目的都是为了读写互不阻塞以及一致性读。因为如果使用锁机制,是完全可以实现不同隔离级别的事务的,但是引入了锁机制以后,读写之间相互阻塞,并发性必然降低。因此才引入了mvcc机制。
    在WiredTiger里面, 事务的一致性主要是通过timestamp。
    每一个timestamp对应一个相应的snapshot,对于每一个事务操作在事务开始的时候, 得到事务的timestamp,在存储引擎里面会有一个snapshot与之对应。
    snapshot记录了该事务在运行过程中的状态, 并且记录了当前事务开始那一时刻,还没有提交的事务的[min_snap_id, max_snap_id],它是从创建snapshot的时候从global_snapshot里面copy出来的。
    在min_snap_id之前的事务是已经提交的事务, 对应的数据是可以读的, 大于min_snap_id且小于等于max_snap_id是正在运行事务,不可读
    在MongoDB4.0中, 不管有没有显式地调用一个事务, CRUD操作都会默认地给这些操作创建一个事务

    3.Mongo事务演化路线

  5. MongoDB 3.0版本引入WiredTiger存储引擎之后开始支持事务

  6. MongoDB 3.6之前的版本只能支持单文档的事务
  7. MongoDB 4.0版本开始支持副本集部署模式下的事务(此时才算真正意义上的引入了事务的概念)
  8. MongoDB 4.2版本开始支持分片集群中的事务。

    4.单文档事务

    MongoDB本身支持的事务类型为单文档事务,也就是说我们对于一个文档的操作是遵循原子性的。
    例如执行如下命令:db.test.update({name:”ftc”},{$set:{age:18,name:ftc_king}})
    如果在更新了age之后,mongo服务器宕机,那么关于age字段的变更会进行回滚操作。保证了数据的一致性和操作的原子性
    在业务允许的情况下,建议使用嵌入式数据模型进行数据的存储。这样可以依赖mongo本身的单文档事务机制,方便的实现属性的原子性变更。一切从简单性出发。如果业务上必须要使用引入式数据模型,也可以使用下文的多文档事务

    5.多文档事务

    MongoDB的多文档事务,包括单一集合的多文档事务,多个集合的多文档事务、多个数据库的多文档事务,甚至是多个分片的多文档事务
    mongo的事务是基于session的,每一个session对应。事务的开启,提交,回滚操作都是在一个session(回话)中进行。
    java操作代码示例:

    1. ClientSession session = client.startSession();
    2. try {
    3. session.startTransaction( ... some tranaction options ... ).build());
    4. // manipulate data
    5. session.commitTransaction();
    6. } catch (MongoCommandException e) {
    7. session.abortTransaction();
    8. } finally {
    9. session.close();
    10. }

    同时,对于分布式事务(副本集或分片模式),还需要注意以下参数

    writeConcern(写关注)

    因为副本集模式写入操作都是写入主节点,因此只需要关注什么时候数据算写入成功了即可
    writeConcern 决定一个写操作落到多少个节点上才算成功。writeConcern 的取值包括

  9. 0:发起写操作,不关心是否成功;

  10. 1~${maxInstanceCount}:写操作需要被复制到指定节点数才算成功;
  11. majority:写操作需要被复制到大多数节点上才算成功。发起写操作的程序将阻塞到写操作到达指定的节点数为止

通常应对重要数据应用 {w: “majority”},普通数据可以应用 {w: 1} 以确保最佳性能。
writeConcern写操作返回耗时受到同步及从节点性能影响,但并不会显著增加集群压力,因此无论是否等待,写操作最终都会复制到所有节点上。设置 writeConcern 只是让写操作等待复制后再返回而已

readPreference(读偏好)

通俗来讲就是从那个节点获取数据,包含以下参数

  1. primary: 只选择主节点;
  2. primaryPreferred:优先选择主节点,如果不可用则选择从节点、
  3. secondary:只选择从节点;
  4. secondaryPreferred:优先选择从节点,如果从节点不可用则选择主节点;
  5. nearest:选择最近的节点;

一般的业务场景来说,设置为secondary即可

readConcern(读关注)

通俗来讲就是读取什么数据,包含以下参数

  1. available: 读取所有可用数据;
  2. local:读取属于当前分片的所有可用数据;
  3. majority:读取大部分已提交的;
  4. inearizable:线性读;
  5. snapshot:快照读;

一般的业务场景来说,设置为majority即可
资料参考:点击这里