事务功能使用及原理介绍(二)|学习笔记

本文涉及的产品
云数据库 MongoDB,通用型 2核4GB
简介: 快速学习事务功能使用及原理介绍

开发者学堂课程【MongoDB 快速入门:事务功能使用及原理介绍】学习笔记,与课程紧密联系,让用户快速学习知识。

课程地址https://developer.aliyun.com/learning/course/49/detail/1005


事务功能使用及原理介绍(二)


三、事务的原理

1.MongoDB事务的特征

一是具有原子性的 All or Nothing Execution;二是它的“读”,会产生Snapshot隔离,在上面写是不会影响后面数据;三是Read Your Own Write,是指事务在写数据时,第一条操作是写操作,后面的读操作是可以直接读到自己写的数据,这个事务的写操作还没有提交也是可读的,但这个事务之外的事务是无法读取到这个数据,只能等到事务提交后才可以读取。

图片29.png

2.MongoDB事务冲突

使用过程中难免遇到写冲突的事务场景。之前讲过写冲突是一个transaction error,只要不断重试就是可以成功的。

写冲突的内部原理(下图左侧):首先是黄色框里对事务进行一个写操作,会占用一个文件的锁;红色框是同时进行一个写操作对文档进行一个修改,并且不能获得文档1的锁,所以就会把事务abort进行回滚。若此时有个非事务性的写操作,同时对文档1 进行写操作,一样无法解锁,这个事务不会直接回滚,但操作会阻塞到到TXN事务提交,除非阻塞时间超过MaxTimeMs,这时就还会报错。

TXN Write 无法获取write lock,事务abort全部回滚

Operation Non-Transaction Write 无法获取write lock ,操作会阻塞到TXN事务提交,除非阻塞时间超过MaxTimeMs

图片30.png

3.MongoDB存储引擎的事务能力

MongoDB能支持事务主要是因为它使用了一个支持单机事务的存储引擎WiredTiger,它在4.0以后决定使用Timetamp来决定事务的顺序性,使得事务的副本集操作成为可能。下图中左侧内容表示它主要分为Server层和WiredTiger层,在读的时候会产生一个snapshot。图中左上角可以看到,之前提交的1,2写操作和新写的3操作,它是读不到3操作的,这个实现是依赖于WiredTiger多版本并发控制MVCC技术,同时也提供了事务的冲突检测功能。数据和索引在wiredTiger里面的B数中存储的。

图片31.png

WiredTiger对数据存储实现的一个叙写,它是由中间页和两个子页组成,中间页是表达从小到大的一个列表,每个keys下面都对应的是一个子页,每个下面都是包含kv的一个匹配对和列表,每一次会对一个kv进行更新,它的value就会在下方产生一个updatelicheck结构,这个结构里面会记录它的一个信息以及提交时间和一些数据,当有多个操作对同一个value进行更新的时候就会组成一个update list。在里面实现多版本数据,通过updatelist实现互斥,比如在更新的时候会让遍历updatelist来测验是否有冲突。冲突的规则就是判断前一个操作是prepare状态还是并发修改的状态。前面的更新还没有提交的话,它当前的更新就触发了一个写冲突,当前的操作就要进行回滚,prepare状态是在分布式事务中的应用场景,分布式状态中它会先把一个写操作以prepare的形式提交,就会产生prepare状态等同于prepare状态的操作是没有提交的,就会触发写冲突。当没有出发写冲突就吹进行一个css操作,把新更新的数据组合成update check结构,原子操作插入到update list最前面。所以update就是从new list to old list的顺序。读操作也是根据updatelist的操作遍历时间点,当读的过程中发现了prepare的修改也会产生冲突,此时mongodb对Prapare修改造成的读冲突会自动重试。

这就是WiredTiger对事务的一个支持,通过对读检测和写检测保证是符合snapshot隔离的

图片32.png

4.MongoDB副本集的跨行事务

非事务的读请求,就是读操作在getfind和getmind操作的实现原理就是下图左侧的伪代码形式。

图片33.png

首先是判断读操作返回的back数据是否满,未满就会按照过滤条件一行一行取出数据并放到batch中,但是每放入一个就会有一个tempyield中断,就会先检查中断里面 有没有一个care操作,有的直接退出结束遍历,或是保留一个concern状态并释放锁和当前的snapshot,同时会获取一个新的snapshot,之后回到concern状态。这个yield就像等于释放了一个事务开始时的snapshot产生了一个新的snapshot,就会导致非事务的读请求没有snapshot的隔离,在一次读请求中间出现多次yield,释放snapshot并读取另一个snapshot所以一次对请求中可能会出现多次读取数据的情况,也会在多个读请求中出现这个问题(参考下图的右上角)非事务读请求会多触发一次find,它是时刻间的一个操作是可以读到12之间的,之后在触发GetMore操作,同样的遍历就会读到事务3的操作。

图片34.png

非事务读请求没有snapshot隔离

MongoDB在一个读请求期间会多次yield,释放wired Tiger snapshot

MongoDB在多个读请求同样会使用多个wired Tiger snapshot

读事务是可以保证事务隔离的,一个find和一个getMore是不会触发到12的事务场景,只会使用一个snapshot,不会触发yeild场景,不会释放一个snapshot并创建一个新的snapshot

读事务在一个读请求只会使用一个wiredTiger snapshot

图片35.png

读事务在多个读请求利用logicalsession保留context,同时只会使用一个wired Tiger snapshot,也就是下图,即使是在一个事务里面,它的读操作、find还有GetMore访问的都是t2和t3之间的时间点数据,所以就只能读到1、2的数据,不会出现前面读到3而没有3 的数据的情况。

该事务对存储引擎Cache压力,造成原因是在一个事务开始读之后会产生一个snapshot,这个时间点后触发了更新。比如事件从time=0开始进行,time=0之后又进行了写操作,time=1触发了写操作,然后做一个修改;time=2做一个触发进行另外的修改,就会产生一个很长的update list 的链表,这个数据都是放在内存里的,当更新量越来越大的时候,链表所占内存就会越来越多,此时就会对Cache造成很大压力,所以链表就只能到这个事务的开始之前链表的结束之后(比如被commit报回以后)这个链表才会被自动回收。

图片36.png

Cache压力来自于事务snapshot之后的写请求

事务的整个生命周期会使使用新的snapshot

Update Structure在snapshot被evict后才能清理

5.如何避免存储引擎Cache压力?

TransactionLifetimeLimitSeconds设为默认60s,不要轻易修改,使用默认60s就是说事务没有结束后MongoDB 会自动的close掉事务,避免事务的写操作数据量过多。

提交Read-Only事务,不能因为事务没有写操作就不提交,即使事务会被系统在MongoDB之后的60秒后进行释放,但这个60s时间过长,还是会对内存造成过大的压力。

中止不需要的事务,避免open一个事务的时间过长

事务的修改的文档<1000Documents且<16MBoplog,在4.0版本时还会有16.0MB的限制

6.MongoDB集群的跨行事务

集群的事务参数和副本集的事务参数是一致的,事务参数:readConcern:snapshot,writeConcern:mjority,readPreference:primary

为了使多个shard保持数据的一致性,需要产生一个分布式的snapshot,相当于在多个shard之间产生一个数据一致的snapshot分布式snapshot隔离级别:可重复读取多个shard数据一致的副本集snapshot

多个shard数据一直通过缓和逻辑时钟来实现(如下图左侧),pt有物理时钟,c是逻辑时钟,中间的是逻辑的一个操作数。当两个shard有联系时就会产生一个因果关系,先看10s时shard0,shard1同步了一条信息,shard0的物理时钟是10 ,但shard1、2、3是0,当shard0往shard1同步一条数据之后,shard1的物理时钟变为1,但是它的收到逻辑时钟收到消息的时钟就是1的最大值,而content会进行加1就变成11;当消息进行shard之间多次传递后到所有shard会产生一个一致的逻辑时钟;当使用逻辑时钟0,1等于0,c=10时间点做一个snapshot,就会发现数据就是一致的,就是黑线上的时间点,它的时间点可以保证分布式时间点数据的一致。所有shard节点触发一个snapshot,混合逻辑时钟可以解决分布式场景下,物理时钟不一致无法定序的问题。图片37.png

采用两阶段提交

参与者Prepare:生成PrepareTs

参与者Commit:使用协调者收集的 max(PrepareTS)作为CommitTS

协调者:收集决策、记录Commit Log

参与者:执行事务、记录Prepare Log

故障回复:config节点保存分布式事务的状态

协调者状态记录于config.transaction_coordinators

参与者状态记录与config.transaction表

采用两阶段的方式实现,比如用户访问Mongos,访问的第一个shard作为一个协调者,这个协调者会与其他的shard进行一个通信,其他shard作为一个参与者,一开始参与者通信就是作为prepare提交,生成PrepareTs,协调者收到preparets的返回就会收集起来选择一个最大值作为CommitTS,再把CommitTs作为提交时间传给其他参与者进行二次提交,这两次提交就保证了分布式事务的实现。协调者主要就是发送提交已经手机决策的操作,会记录提交的一个日志,参与者主要是记录一个Prepare的日志。两个节点之间可能会有故障有节点崩溃的场景,在cf场景会保存分布式事务的状态,协调者状态记录于config.transaction_coordinators,参与者状态记录与config.transaction表,这个好处在于当协调者参与者发生崩溃时,会有备库被选为主库时,会重新选择一个来获取事务的状态,来继续集群事务两阶段的提交过程,这就是整个集群事务的跨行实现。

图片38.png

7.MongDB事务使用的注意事项

由于支持了跨行输入,各种数据模型都能适用,比如传统的关系模型是可以适用的。

事务不应该是最常用的操作,它的性能是受影响的,应该更多的使用文档模型或是单行输入做操作,少量用跨行输入,这才是MongoDB比较推荐的使用规则。

事务的操作中,都应该要包含sesion,在操作的过程中指定事务的操作,如果不指定操作就会被认为是一个外部的非事务操作

事务会报错,需要增加重试逻辑。无论是因为写冲突还是网络问题,增加重试逻辑来保证应用不会受到影响。

不必要的事务snapshot要尽快关闭,避免造成对cache的压力

如果像产生写冲突,确保事务做了写操作。测试过程中想产生写冲突,一定要确保事务进行了真正的修改,是两个事务都进行修改,而不是把a事务改为a事务,这个并不是真正的写操作,在MongoDB里,会是一个not的操作,另外的事务是可以进行正常的更新的,就不会产生写冲突,所以要确保有真正的写操作。

注意ddl操作,已有的事务操作会阻塞ddl,ddl会阻塞之后的事务。已有事务会加上一个意向锁阻止ddl操作,ddl操作一旦下发就回去阻止之后的操作就会对应用产生写影响。


四、总结

1.首先介绍了MongoDB为什么需要事务。在大量的多对多关系或是一个事件驱动或是一个操作日志中,这种场景往往需要一个跨表的操作,需要操作来保持原子性,这就要用MongoDB跨行事务

2.MongoDB在副本集和集群上跨行事务的使用方法,这里面要注意增加的有重试操作,可以设置一个重试的次数限制和时间限制

3.MongoDB存储引擎的事务能力

主要讲述了update list的读和写以及更新的时候要进行一个检查来查验冲突是否存在

4.副本集的跨行事务的原理,以及对cache造成的压力和避免方法,需要尽可能快的把事务进行提交或终止

5.集群的跨行事务的原理,提交是进行一致性的提交,每一个commitTS一定是每一个shard  prepareTS的最大值

6.事务使用的注意事项,按照注意事项避免过度使用,过度使用会对数据库造成较大的性能压力。

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
SQL 存储 NoSQL
事务功能使用及原理介绍(一)|学习笔记
快速学习事务功能使用及原理介绍
265 0
事务功能使用及原理介绍(一)|学习笔记
|
存储 缓存 监控
ChangeStreams 使用及原理(二)|学习笔记
快速学习 ChangeStreams 使用及原理
567 0
ChangeStreams 使用及原理(二)|学习笔记
|
SQL 运维 监控
ChangeStreams 使用及原理(一)|学习笔记
快速学习 ChangeStreams 使用及原理
350 0
ChangeStreams 使用及原理(一)|学习笔记
|
运维 NoSQL 数据可视化
复制集使用及原理介绍(一)|学习笔记
快速学习复制集使用及原理介绍
121 0
复制集使用及原理介绍(一)|学习笔记
|
SQL 运维 NoSQL
复制集使用及原理介绍(二)|学习笔记
快速学习复制集使用及原理介绍
219 0
复制集使用及原理介绍(二)|学习笔记
|
存储 弹性计算 算法
分布式事务与数据分区(四)|学习笔记
快速学习分布式事务与数据分区(四)
119 0
|
存储 SQL NoSQL
分布式事务与数据分区(一)|学习笔记
快速学习分布式事务与数据分区(一)
178 0
分布式事务与数据分区(一)|学习笔记
|
存储 SQL 弹性计算
分布式事务与数据分区(二)|学习笔记
快速学习分布式事务与数据分区(二)
130 0
分布式事务与数据分区(二)|学习笔记
|
存储 分布式数据库 数据库
分布式事务与数据分区(三)|学习笔记
快速学习分布式事务与数据分区(三)
55 0
|
安全 Java 关系型数据库
区分多数据源实现(一)|学习笔记
快速学习区分多数据源实现(一)
102 0

热门文章

最新文章