MongoDB学习6:MongoDB的事务处理
1.MongoDB的写操作事务
写⼊策略 writeConcern
语法:db.collection.insert({x: 1}, {writeConcern: {w: 1}})
什么是writeConcern?
writeConcern决定⼀个写操作落到多少个节点上才算成功,这决定了MongoDB是否成功写⼊数据。writeConcern的取值有以下:0:发起写⼊操作,不关⼼是否成功(适⽤于性能要求⾼,但不关注正确性的场景)
1-集最⼤节点数:写操作需要被复制到指定节点数才算成功
majority:写操作需要被复制到⼤多数节点上才算成功(适⽤于对数据安全性要求⽐较⾼的场景,该选项会降低写⼊性能
)
all:复制到全部节点上才算成功
发起写操作的程序将阻塞到写操作到达指定的节点数为⽌
writeConcern的⾏为
以3节点复制集为例:不做任何特定设置
上图表⽰⼀个写操作进⼊后,直接写⼊主节点成功就返回了,后台会异步复制到从节点secondary1和secondary2。但假如数据刚写⼊主节点后,从节点还没有复制数据,主节点就宕机了,此时MongoDB就可能出现丢失数据的问题,那么如何解决呢?
参数 w:"majority"
majority表⽰数据写⼊⼤多数(超半数)节点后才算成功。此时如果主节点再发⽣宕机情况,那么从节点secondary1就会被选举为新的主节点,数据也没有丢失
参数 w:"all"
all表⽰确认全部节点写⼊成功后才返回。这是⼀种最安全的写法,数据绝对不会丢失,但是如果有⼀个节点故障,那么就会发⽣阻塞⼀直等
待
参数 j:true
j表⽰写⼊操作的journal持久化后才向客户端确认,取值有:
true:写操作落到 journal ⽂件中才算成功
false:写操作到达内存即算成功
参数 wtimeout: 写⼊超时时间,仅w的值⼤于1时有效
当指定{w: }时,数据需要成功写⼊n个节点才算成功,如果写⼊过程中有节点故障,可能导致这个条件⼀直不能满⾜,从⽽⼀直不能向客户端发送确认结果,针对这种情况,客户端可设置wtimeout选项来指定超时时间,当写⼊过程持续超过该时间仍未结束,则认为写⼊失败
writeConcern测试
以下测试在3个节点环境中
slaveDelay:设置节点延迟时间(单位:s),延迟多久才会同步数据
var conf = rs.conf
注意事项
虽然多半数的 writeConcern都是安全的,但通常只会设置 majority,因为这是等待写⼊延迟时间最短的选择
不要设置 writeConcern 等于总节点数,因为⼀旦有⼀个节点故障,所有写操作都会失败
writeConcern 虽然会增加写操作的延迟时间,但并不会显著增加集的压⼒,因此⽆论是否等待,写操作最终都会复制到所有节点上。但设置 writeConcern只是让写操作等待复制后在返回⽽已
应对重要数据(订单、⾦融有关的)应⽤ {w:"majority"},普通数据(⽇志)可以应⽤{w:1}以确保最佳性能
2.MongoDB的读操作事务
在读取数据的过程中我们需要关注以下问题:
从哪⾥读?关注数据节点的位置(由readPerference解决)
什么样的数据可以读?关注数据的隔离性(由readConcern解决)
readPerference:
使⽤:db.collection.find({}).readPerf("secondary")
readPerference决定使⽤哪⼀个节点来满⾜正在发起的读请求,可选值包括:
primary(默认):只选择主节点,保证每次读到的数据都是最新的
primaryPerferred:优先选择主节点,如果不可⽤则选择从节点
secondary:只选择从节点
secondaryPerferred:优先选择从节点,如果从节点不可⽤则选择主节点
session如何设置和读取nearest:选择最近的节点,针对多区域部署的情况
readPerference使⽤场景举例:
⽤户下单后马上跳转到订单详情页--primary/primaryPerferred,因为此时从节点可能还没复制到新的订单数据
⽤户查询⾃⼰下过的订单--secondary/secondaryPerferred,查询历史订单对时效性通常没有太⾼要求
⽣成报表--secondary,报表对时效性要求不⾼,但资源需求⼤,可以在从节点单独处理,避免影响主节点操作
将⽤户上传的图⽚分发到全世界,让各地⽤户就近读取--nearest,每个地区的应⽤选择最近的节点读取数据
readPerference与Tag
readPreference只能控制使⽤⼀类节点。Tag则可以将节点选择控制到⼀个或⼏个具体的节点,有以下场景:
⼀个5个节点的复制集
3个节点硬件较好,专⽤于服务线上⽤户
2个节点硬件较差,专⽤于⽣成包报表
可以使⽤Tag来达到这样的控制⽬的:
为3个较好的节点打上 {purpose:"online"}
为2个较差的节点打上 {purpose:"analyse"}
在线应⽤读取时指定online,报表读取时指定analyse
readPerference配置:
通过MongoDB的连接字符串:mongodb://host:27017,host2:27017,host3:27017/?replicaSet=rs&readPerferende=secondary
通过MongoDB驱动程序API:MongoCollection.withReadPerference(ReadPerference readPerf)
Mongo Shell:db.collection.find({}).readPerf("secondary")
readPerference注意事项:
指定readPerference时也应该注意⾼可⽤问题。例如将 readPerference 指定primary,则发⽣故障转移不存在primary期间将没有节点可读。如果业务允许,则应选择primaryPerference
使⽤Tag时也会遇到同样的问题,如果只有⼀个节点拥有⼀个特定的Tag,则这个节点宕机时将⽆节点可读。这在有时候是期望的结果,有时候不是:
如果报表使⽤的节点失效,即使不⽣成报表,通常也不希望将报表负载转移到其他节点上,此时只有⼀个节点有报表Tag是合理的选择
如果线上节点失效,通常希望有替代节点,则应该保持多个节点有同样的Tag
Tag有时需要与优先级、选举权综合考虑。例如做报表的节点通常不会希望它成为主节点,则优先级应为0
readConcern:
使⽤:db.test.find().readConcern("majority")
在readPerference选择了指定节点后,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)
readConcern:majority
只读取⼤多数数据节点上都提交了的数据,考虑如下场景:
集合中原有⽂档 {x:0}
将x值更新为1
如果在各节点上应⽤ {readConcern:"majority"}来读取数据:
readConcern:majority与脏读
MongoDB中的回滚:
写操作到达⼤多数节点之前都是不安全的,⼀旦主节点崩溃,⽽从节点还没复制到该次操作,刚才的写操作就丢失了把⼀次写操作视为⼀个事务,从事务的⾓度来看,可以认为事务被回滚了。
所以从分布式系统的⾓度来看,事务的提交被提升到了分布式集的多个节点级别的“提交”,⽽不是单个节点的“提交”在可能发⽣回滚的前提下考虑脏读问题:
如果在⼀次写操作到达⼤多数节点前读取了这个写操作,然后因为系统故障该操作回滚了,则发⽣脏读问题,使⽤{readConcern:"majority"}可以有效避免脏读
readConcern:实现安全的读写分离
有以下场景
向主节点写⼊⼀条数据
⽴即从从节点读取这条数据
如何保证⾃⼰能够读取到刚刚写⼊的数据?
下述⽅式有可能读不到刚写⼊的订单:
使⽤writeConcern + readConcern majority来解决:
readConcern:linearizable
只读取⼤多数节点确认过的数据。和majority最⼤差别是保证绝对的操作线性顺序-在写操作⾃然时间后⾯发⽣的读,⼀定可以读到之前的写
只对读取单个⽂档时有效
可能导致⾮常慢的读,因此总是建议配合使⽤ maxTimeMS
readConcern:snapshot
只在多⽂档事务中⽣效。将⼀个事务的 readConcern设置为snapshot将保证在事务中的读:
不出现脏读
不出现不可重复读
不出现幻读
因为所有的读都将使⽤同⼀个快照,直到事务提交为⽌该快照才被释放
3.MongoDB的多⽂档事务
对事务的使⽤原则应该是:能不⽤尽量不⽤
通过合理的设计⽂档模型,可以避免⼤部分使⽤事务的必要性,因为事务=锁,节点协调需要额外开销,影响性能
MongoDB ACID多⽂档事务⽀持
事务属性⽀持程度
Atomocity 原⼦性单表单⽂档:1.x就⽀持
复制集多表多⾏:4.0复制集分⽚集多表多⾏:4.2
Consistency ⼀致性writeCOncern、readConcern(3.2)
Isolation 隔离性readConcern(3.2)
Durability 持久性Journal and Replication
使⽤⽅法
MongoDB多⽂档事务使⽤⽅式和关系型数据库⾮常相似,以java为例
try(ClientSession clientSession = client.startSession()){
clientSession.startTransaction();
collection.insertOne(clientSession,docOne);
collection.insertOne(clientSession,docTwo);
clientSessionmitTransaction();
}
事务的隔离级别
事务完成前,事务外的操作对该事务所做的修改不可访问
如果事务内使⽤{readConcern:"snapshot"},则可以达到可重复度 Repeatable Read
事务写机制
MongoDB的事务错误处理机制不同于关系型数据库
当⼀个事务开始后,如果事务要修改的⽂档在事务外部被修改过,则事务修改这个⽂档时会出发Abort错误,因为此时修改冲突了,这种情况下,只需要简单的重做事务就可以了
如果⼀个事务已经开始修改⼀个⽂档,在事务以外尝试修改同⼀个⽂档,则事务以外的修改会等待事务完成才能继续进⾏
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论