课程内容:
1.MongoDB分片集群架构及其原理分析
2.MongoDB分片集群环境搭建
3.MongoDB分片策略和数据均衡详解
4.写事务之writeConcern实战
5.读事务之readPreference&readConcern实战
6.MongoDB多文档事务详解
分片集群架构
分片(shard)是指在将数据进行水平切分之后,将其存储到多个不同的服务器节点上的一种扩展方式。
分片在概念上非常类似于应用开发中的“水平分表"。不同的点在于,MongoDB本身就自带了分片管理的能力,对于开发者来说可以做到开箱即用。
为什么要使用分片?
MongoDB复制集实现了数据的多副本复制及高可用,但是一个复制集能承载的容量和负载是有限的。 在你遇到下面的场景时,就需要考虑使用分片了∶
垂直扩容(Scale Up)VS水平扩容(Scale Out) :
MongoDB分片集群(Sharded Cluster)是对数据进行水平扩展的一种方式。MongoDB使用分片集群来支持大数据集和高吞吐量的业务场景。在分片模式下,存储不同的切片数据的节点被称为分片节点,一个分片集群内包含了多个分片节点。当然,除了分片节点,集群中还需要一些配置节点、路由节点,以保证分片机制的正常运作。
核心概念
数据分片:分片用于存储真正的数据,并提供最终的数据读写访问。分片仅仅是一个逻辑的概念,它可以是一个单独的mongod实例,也可以是一个复制集。图中的Shard1、Shard2都是一个复制集分片。在生产环境中也一般会使用复制集的方式,这是为了防止数据节点出现单点故障。
配置服务器(Config Server):配置服务器包含多个节点,并组成一个复制集结构,对应于图中的ConfigRepISet。配置复制集中保存了整个分片集群中的元数据,其中包含各个集合的分片策略,以及分片的路由表等。如果我们查询一个数据,到底在哪个Shard里面,就是通过这个配置知道的。
查询路由(mongos) : mongos是分片集群的访问入口,其本身并不持久化数据。mongos启动后,会从配置服务器中加载元数据。之后mongos开始提供访问服务,并将用户的请求正确路由到对应的分片。在分片集群中可以部署多个mongos以分担客户端请求的压力。
环境准备
3+3+3+2=11,也就是MongoDB生产环境至少要11台机器。
配置域名解析
在3台虚拟机上执行以下命令,注意替换实际IP地址
echo "192.168.65.97 mongo1 mongo01.com mongo02.com" >> /etc/hosts
echo "192.168.65.190 mongo2 mongo03.com mongo04.com" >> /etc/ hosts
echo "192.168.65,200 mongo3 mongo05.com mongo06.com" >> /etc/ hosts
准备分片目录
在各服务器上创建数据目录,我们使用`/data’,请按自己需要修改为其他目录:在mongo01.com / mongo03.com / mongo05.com 上执行以下命令:
mkdir -p /data/shard1/db /data/shard1/log /data/config/db /data/config/log
在mongo02.com / mongo04.com / mongo06.com 上执行以下命令:
mkdir -p /data/shard2/db /data/shard2/log /data/mongos/
创建第一个分片用的复制集
在mongo01.com / mongo03.com / mongo05.com 上执行以下命令:
mongod --bind_ip 0.0.0.0 --replSet shard1 --dbpath /data/shard1/db \
--logpath /data/shard1/log/mongod.log --port 27010 --fork \
--shardsvr --wiredTigerCacheSizeGB 1
–shardsvr 声明这是集群的一个分片
–wiredTigerCacheSizeGB 设置内存大小
初始化第一个分片复制集
# 进入mongo shell
mongo mongo01.com:27010
#shard1复制集节点初始化
rs.initiate({
_id: "shard1",
"members" : [
{
"_id": 0,
"host" : "mongo01.com:27010"
},
{
"_id": 1,
"host" : "mongo03.com:27010"
},
{
"_id": 2,
"host" : "mongo05.com:27010"
}
]
})
#查看复制集状态
rs.status()
创建 config server 复制集
在mongo01.com / mongo03.com / mongo05.com上执行以下命令:
mongod --bind_ip 0.0.0.0 --replSet config --dbpath /data/config/db \
--logpath /data/config/log/mongod.log --port 27019 --fork \
--configsvr --wiredTigerCacheSizeGB 1
初始化 config server 复制集
# 进入mongo shell
mongo mongo01.com:27019
#config复制集节点初始化
rs.initiate({
_id: "config",
"members" : [
{
"_id": 0,
"host" : "mongo01.com:27019"
},
{
"_id": 1,
"host" : "mongo03.com:27019"
},
{
"_id": 2,
"host" : "mongo05.com:27019"
}
]
})
搭建 mongos
在mongo01.com / mongo03.com / mongo05.com上执行以下命令:
#启动mongos,指定config复制集
mongos --bind_ip 0.0.0.0 --logpath /data/mongos/mongos.log --port 27017 --fork \
--configdb config/mongo01.com:27019,mongo03.com:27019,mongo05.com:27019
mongos加入第1个分片
# 连接到mongos
mongo mongo01.com:27017
#添加分片
mongos>sh.addShard("shard1/mongo01.com:27010,mongo03.com:27010,mongo05.com:27010")
#查看mongos状态
mongos>sh.status()
创建分片集合
连接到mongos, 创建分片集合
mongo mongo01.com:27017
mongos>sh.status()
#为了使集合支持分片,需要先开启database的分片功能
mongos>sh.enableSharding("company")
# 执行shardCollection命令,对集合执行分片初始化
mongos>sh.shardCollection("company.emp", {_id: 'hashed'})
mongos>sh.status()
#插入测试数据
use company
for (var i = 0; i < 10000; i++) {
db.emp.insert({i: i});
}
#查询数据分布
db.emp.getShardDistribution()
创建第2个分片的复制集
在mongo02.com / mongo04.com / mongo06.com上执行以下命令:
mongod --bind_ip 0.0.0.0 --replSet shard2 --dbpath /data/shard2/db \
--logpath /data/shard2/log/mongod.log --port 27011 --fork \
--shardsvr --wiredTigerCacheSizeGB 1
初始化第二个分片的复制集
# 进入mongo shell
mongo mongo06.com:27011
#shard2复制集节点初始化
rs.initiate({
_id: "shard2",
"members" : [
{
"_id": 0,
"host" : "mongo06.com:27011"
},
{
"_id": 1,
"host" : "mongo02.com:27011"
},
{
"_id": 2,
"host" : "mongo04.com:27011"
}
]
})
#查看复制集状态
rs.status()
mongos加入第2个分片
# 连接到mongos
mongo mongo01.com:27017
#添加分片
mongos>sh.addShard("shard2/mongo02.com:27011,mongo04.com:27011,mongo06.com:27011")
#查看mongos状态
mongos>sh.status()
mtools介绍
mtools是一套基于Python实现的MongoDB工具集,其包括MongoDB日志分析、报表生成及简易的数据库安装等功能。它由MongoDB原生的工程师单独发起并做开源维护,目前已经有大量的使用者。
mtools所包含的一些常用组件如下:
Tools | Description |
---|---|
mlogfilter | 合并、分割日志文件,过滤慢查询,集合扫描,格式转换等 |
mloginfo | 统计日志内的数据库信息(启停、连接、集群状态等) |
mplotqueries | 日志转化为图表形式 |
mlogvis | 日志转化为HTML页面,与mplotqueries类似 |
mlaunch | 快速搭建本地测试环境(单机、集群、分片) |
安装mtools
环境准备
pip安装
安装依赖
pip3 install python-dateutil
pip3 install psutil pymongo
安装mtools
pip3 install mtools
通过源码安装
wget https://github.com/rueckstiess/mtools/archive/refs/tags/v1.6.4.tar.gz
#解压后进入mtools
python setup.py install
使用mtools创建复制集
#准备复制集使用的工作目录
mkdir -p /data/mongo
cd /data/mongo
#初始化3节点复制集
mlaunch init --replicaset --nodes 3
端口默认从27017开始,依次为2017,27018,27019
查看复制集状态
#准备分片集群使用的工作目录
mkdir /data/mongo-cluster
cd /data/mongo-cluster/
# 执行mlaunch init初始化集群
mlaunch init --sharded 2 --replicaset --node 3 --config 3 --csrs --mongos 3 --port 27050
选项说明
检查分片实例
mlaunch list命令可以对当前集群的实例状态进行检查
# 显示标签
mlaunch list --tags
# 显示启动命令
mlaunch list --startup
连接mongos,查看分片实例的情况
mongo --port 27050
mongos> db.adminCommand({listShards:1})
停止、启动
如果希望停止集群,则可以使用mlaunch stop命令
再次启动集群,可以使用mlaunch start命令
使用mtools搭建测试集群是相当方便的,相比手工搭建的方式可缩减大量的时间。
为了使集合支持分片,需要先开启database的分片功能
sh . enablesharding("shop")
执行shardCollection命令,对集合执行分片初始化
sh.shardCollection("shop.product",{productId:"hashed",false,{numInitialChunks:4})
shop.product集合将productld作为分片键,并采用了哈希分片策略,除此以外,“numInitialChunks:4"表示将初始化4个chunk。numInitialChunks必须和哈希分片策略配合使用。而且,这个选项只能用于空的集合,如果已经存在数据则会返回错误。
向分片集合写入数据
向shop.product集合写入一批数据
db = db.getSiblingDB("shop");
var count = 0;
for (var i = 0; i < 1000; i++) {
var p = [];
for (var j = 0; j < 100; j++) {
p.push({
"productId": "P-" + i + "-" + j,
name: "羊毛衫",
tags: [
{tagKey: "size", tagValue: ["L", "XL", "XXL"] },
{tagKey: "color", tagValue: ["蓝色", "杏色"] },
{tagKey: "style", tagValue: "韩风" }
]
});
}
count+=p.length;
db.product.insertMany(p);
print("insert",count)
}
查询数据的分布
db.product.getShardDistribution()
通过分片功能,可以将一个非常大的集合分散存储到不同的分片上,如图:
假设这个集合大小是1TB,那么拆分到4个分片上之后,每个分片存储256GB的数据。这个当然是最理想化的场景,实质上很难做到如此绝对的平衡。一个集合在拆分后如何存储、读写,与该集合的分片策略设定是息息相关的。在了解分片策略之前,我们先来介绍一下chunk。
chunk的意思是数据块,一个chunk代表了集合中的“一段数据",例如,用户集合(db.users)在切分成多个chunk之后如图所示:
chunk所描述的是范围区间,例如,db.users使用了userld作为分片键,那么chunk就是userld的各个值(或哈希值)的连续区间。集群在操作分片集合时,会根据分片键找到对应的chunk,并向该chunk所在的分片发起操作请求,而chunk的分布在一定程度上会影响数据的读写路径,这由以下两点决定:
chunk的切分方式,决定如何找到数据所在的chunk
chunk的分布状态,决定如何找到chunk所在的分片
chunk切分是根据分片策略进行实施的,分片策略的内容包括分片键和分片算法
。当前,MongoDB支持两种分片算法:
范围分片(range sharding)
假设集合根据x字段来分片,x的完整取值范围为minKey, maxKey,其将整个取值范围划分为多个chunk,例如:
范围分片能很好地满足范围查询的需求,比如想查询x的值在[-30,10]之间的所有文档,这时mongos直接将请求定位到chunk2所在的分片服务器,就能查询出所有符合条件的文档。范围分片的缺点在于,如果Shard Key有明显递增〈或者递减)趋势,则新插入的文档会分布到同一个chunk,此时写压力会集中到一个节点,从而导致单点的性能瓶颈。一些常见的导致递增的Key如下:
哈希分片会先事先根据分片键计算出一个新的哈希值(64位整数),再根据哈希值按照范围分片的策略进行chunk的切分。适用于日志,物联网等高并发场景。
哈希分片与范围分片是互补的,由于哈希算法保证了随机性,所以文档可以更加离散地分布到多个chunk上,这避免了集中写问题。然而,在执行一些范围查询时,哈希分片并不是高效的。因为所有的范围查询都必然导致对所有chunk进行检索,如果集群有10个分片,那么mongos将需要对10个分片分发查询请求。哈希分片与范围分片的另一个区别是,哈希分片只能选择单个字段,而范围分片允许采用组合式的多字段作为分片键。
哈希分片仅支持单个字段的哈希分片:
{ x : "hashed" }
{x : 1 , y : "hashed" } // 4.4 new
4.4以后的版本,可以将单个字段的哈希分片和一个到多个的范围分片键字段来进行组合,比如指定x:1,y是哈希的方式。
MongoDB允许通过为分片添加标签(tag)的方式来控制数据分发。一个标签可以关联到多个分片区间(TagRange)。均衡器会优先考虑chunk是否正处于某个分片区间上(被完全包含),如果是则会将chunk迁移到分片区间所关联的分片,否则按一般情况处理。
分片标签适用于一些特定的场景。例如,集群中可能同时存在OLTP和OLAP处理,一些系统日志的重要性相对较低,而且主要以少量的统计分析为主。为了便于单独扩展,我们可能希望将日志与实时类的业务数据分开,此时就可以使用标签。
为了让分片拥有指定的标签,需执行addShardTag命令
sh.addShardTag("shard01","oltp")
sh.addShardTag("shard02","oltp")
sh.addShardTag("shard03","olap")
实时计算的集合应该属于oltp标签,声明TagRange
sh.addTagRange("main.devices",{shardKey:MinKey},{shardKey:MaxKey},"oltp")
而离线计算的集合,则属于olap标签
sh.addTagRange("other.systemLogs",{shardKey:MinKey},{shardKey:MaxKey},"olap")
main.devices集合将被均衡地分发到shard01、shard02分片上,而other.systemLogs集合将被单独分发到shard03分片上。
在选择分片键时,需要根据业务的需求及范围分片、哈希分片的不同特点进行权衡。一般来说,在设计分片键时需要考虑的因素包括:
分片键的基数(cardinality),取值基数越大越有利于扩展。
以性别作为分片键︰数据最多被拆分为2份
以月份作为分片键︰数据最多被拆分为12份
分片键的取值分布应该尽可能均匀。
业务读写模式,尽可能分散写压力,而读操作尽可能来自一个或少量的分片。
分片键应该能适应大部分的业务操作。
ShardKey必须是一个索引。非空集合须在ShardCollection前创建索引;空集合ShardCollection自动创建索引
4.4版本之前:
4.4版本之后:
而在4.2版本之前,ShardKey对应的值不可以修改;4.2版本之后,如果ShardKey为非_ID字段,那么可以修改ShardKey对应的值。
一种理想的情况是,所有加入的分片都发挥了相当的作用,包括提供更大的存储容量,以及读写访问性能。因此,为了保证分片集群的水平扩展能力,业务数据应当尽可能地保持均匀分布。这里的均匀性包含以下两个方面:
其中,第1点由业务场景和分片策略来决定,而关于第2点,
我们有以下两种选择:
一种做法是,可以在初始化集合时预分配一定数量的chunk(仅适用于哈希分片),比如给10个分片分配1000个chunk,那么每个分片拥有100个chunk。另一种做法则是,可以通过splitAt、moveChunk命令进行手动切分、迁移。
开启MongoDB集群的自动均衡功能。均衡器会在后台对各分片的chunk进行监控,一旦发现了不均衡状态就会自动进行chunk的搬迁以达到均衡。其中,chunk不均衡通常来自于两方面的因素:
在默认情况下,一个chunk的大小为64MB,该参数由配置的chunksize参数指定。如果持续地向该chunk写入数据,并导致数据量超过了chunk大小,则MongoDB会自动进行分裂,将该chunk切分为两个相同大小的chunk。
chunk分裂是基于分片键进行的,如果分片键的基数太小,则可能因为无法分裂而会出现jumbo chunk(超大块)的问题
。例如,对db.users使用gender(性别)作为分片键,由于同一种性别的用户数可能达到数千万,分裂程序并不知道如何对分片键(gender)的一个单值进行切分,因此最终导致在一个chunk上集中存储了大量的user记录(总大小超过64MB)。
jumbo chunk对水平扩展有负面作用,该情况不利于数据的均衡,
业务上应尽可能避免。一些写入压力过大的情况可能会导致chunk多次失败(split),最终当chunk中的文档数大于1.3×avgObjectSize时会导致无法迁移。此外在一些老版本中,如果chunk中的文档数超过250000个,也会导致无法迁移。
MongoDB的数据均衡器运行于Primary Config Server(配置服务器的主节点)上,而该节点也同时会控制chunk数据的搬迁流程。
流程说明:
分片shardO在持续的业务写入压力下,产生了chunk分裂。
分片服务器通知Config Server进行元数据更新。
Config Server的自动均衡器对chunk分布进行检查,发现shard0和shard1的chunk数差异达到了阈值,向shard0下发moveChunk命令以执行chunk迁移。
shard0执行指令,将指定数据块复制到shard1。该阶段会完成索引、 chunk数据的复制,而且在整个过程中业务侧对数据的操作仍然会指向shard0;所以,在第一轮复制完毕之后,目标shard1会向shard0确认是否还存在增量更新的数据,如果存在则继续复制。
shard0完成迁移后发送通知,此时Config Server开始更新元数据库,将chunk的位置更新为目标shard1。在更新完元数据库后并确保没有关联cursor的情况下,shard0会删除被迁移的chunk副本。
Config Server通知mongos服务器更新路由表。此时,新的业务请求将被路由到shard1。
均衡器对于数据的“不均衡状态"判定是根据两个分片上的chunk个数差异来进行的
chunk个数 | 迁移阈值 |
---|---|
少于20 | 2 |
20~79 | 4 |
80及以上 | 8 |
数据均衡的整个过程并不是很快,影响MongoDB均衡速度的几个选项如下:
随着版本的迭代,MongoDB迁移的能力也在逐步提升。从MongoDB 4.0版本开始,支持在迁移数据的过程中并发地读取源端和写入目标端,迁移的整体性能提升了约40%。这样也使得新加入的分片能更快地分担集群的访问读写压力。
数据均衡会影响性能,在分片间进行数据块的迁移是一个“"繁重"的工作,很容易带来磁盘/O使用率飙升,或业务时延陡增等一些问题。因此,建议尽可能提升磁盘能力,如使用SSD。除此之外,我们还可以将数据均衡的窗口对齐到业务的低峰期以降低影响。
登录mongos,在config数据库上更新配置,代码如下:
use config
sh.setBalancerState(true)
db.settings.update(
{_id:"balancer"},
{$set:{activewindow:{start:"02:00",stop:"04:00"}}},
{upsert:true}
)
在上述操作中启用了自动均衡器,同时在每天的凌晨2点到4点运行数据均衡操作
对分片集合中执行count命令可能会产生不准确的结果,
mongos在处理count命令时会分别向各个分片发送请求,并累加最终的结果。如果分片上正在执行数据迁移,则可能导致重复的计算。替代办法是使用db.collection.countDocuments({})方法,该方法会执行聚合操作进行实时扫描,可以避免元数据读取的问题,但需要更长时间。
在执行数据库备份的期间,不能进行数据均衡操作,
否则会产生不一致的备份数据。在备份操作之前,可以通过如下命令确认均衡器的状态:
事务(transaction)是传统数据库所具备的一项基本能力,其根本目的是为数据的可靠性与一致性提供保障。而在通常的实现中,事务包含了一个系列的数据库读写操作,这些操作要么全部完成,要么全部撤销。例如,在电子商城场景中,当顾客下单购买某件商品时,除了生成订单,还应该同时扣减商品的库存,这些操作应该被作为一个整体的执行单元进行处理,否则就会产生不一致的情况。
数据库事务需要包含4个基本特性,即常说的ACID,具体如下:
在MongoDB中,对单个文档的操作是原子的
。由于可以在单个文档结构中使用内嵌文档和数组来获得数据之间的关系,而不必跨多个文档和集合进行范式化,所以这种单文档原子性避免了许多实际场景中对多文档事务的需求。
对于那些需要对多个文档(在单个或多个集合中)进行原子性读写的场景,MongoDB支持多文档事务。而使用分布式事务,事务可以跨多个操作、集合、数据库、文档和分片使用。
MongoDB虽然已经在4.2开始全面支持了多文档事务,但并不代表大家应该毫无节制地使用它。相反,对事务的使用原则应该是:能不用尽量不用
。通过合理地设计文档模型,可以规避绝大部分使用事务的必要性。
使用事务的原则
MongoDB对事务支持
事务属性 | 支持程度 |
---|---|
Atomocity 原子性 | 单表单文档:1.x就支持,复制集多表多行:4.0分片集群多表多行:4.2 |
Consistency 一致性 | writeConcern, readConcern (3.2) |
lsolation隔离性readConcern (3.2) | |
Durability 持久性 | Journal and Replication |
使用方法
MongoDB多文档事务的使用方式与关系数据库非常相似:
try(ClientSession clientSession = client.startSession()){
clientSession.startTransaction();
collection.insertOne(clientSession,docOne);
collection.insertOne(clientSession,docTwo);
clientSession.commitTransaction() ;
}
https://docs.mongodb.com/manual/reference/write-concern/
writeConcern决定一个写操作落到多少个节点上才算成功。MongoDB支持客户端灵活配置写入策略( writeConcern),以满足不同场景的需求。
语法格式:
{w:<value>,j:<boolean>,wtimeout:<number>}
j:写入操作的journal持久化后才向客户端确认
wtimeout: 写入超时时间,仅w的值大于1时有效。
思考:对于5个节点的复制集来说,写操作落到多少个节点上才算是安全的?
1 2 3 4 5 majority
测试
包含延迟节点的3节点pss复制集
db.user.insertone({name: "李四"},{writeConcern:{w:"majority"}})
#等待延迟节点写入数据后才会响应
db.user.insertOne({name:"王五"},{writeconcern:{w:3}})
#超时写入失败
db.user.insertOne({name:"小明"}, {writeConcern:{w:3,wtimeout:3000}})
注意事项
在读取数据的过程中我们需要关注以下两个问题:
第一个问题是是由readPreference来解决,第二个问题则是由readConcern来解决
readPreference决定使用哪一个节点来满足正在发起的读请求。可选值包括:
合理的ReadPreference可以极大地扩展复制集的读性能,降低访问延迟。
通过MongoDB的连接串参数:
mongodb://host1:27107, host2:27107, host3:27017/?replicaSet=rs0&readPreference=secondary
通过MongoDB驱动程序API:
MongoCollection.withReadPreference(ReadPreference readPref)
Mongo Shell:
db.collection.find().readPref("secondary")
# mongo --host rs0/localhost:28017
rs0:PRIMARY> db.user.insert({count:1})
在primary节点中调用readPref(“secondary”)查询从节点用直连方式(mongo
localhost:28017)会查到数据,需要通过mongo --host rs0/localhost:28017方式连接复制集,参考: https://jira.mongodb.org/browse/SERVER-22289
#mongo localhost:28018
rs0:SECONDARY> rs.secondaryok()
rs0:SECONDARY> db.fsyncLock()
rs0:PRIMARY> db.user.insert({count : 2})
rs0:PRIMARY> db.user.find()
rs0:PRIMARY> db.user.find().readPref("secondary")
rs0:SECONDARY> db.user.find()
rs0:SECONDARY>db.fsyncUnlock()
rs0:PRIMARY> db.user.find().readPref("secondary")
扩展: Tag
readPreference只能控制使用一类节点。Tag则可以将节点选择控制到一个或几个节点。考虑以下场景:
可以使用Tag来达到这样的控制目的:
#为复制集节点添加标签
conf = rs.conf()
conf.members[1].tags = { purpose: "online"}
conf.members[4].tags = { purpose: "analyse" }
rs.reconfig(conf)
#查询
db.collection.find({}).readPref("secondary" ,[ {purpose: "analyse"}])
注意事项
指定readPreference时也应注意高可用问题。例如将readPreference指定 primary,则发生故障转移不存在primary期间将没有节点可读。如果业务允许,则应选择primaryPreferred;
使用Tag时也会遇到同样的问题,如果只有一个节点拥有一个特定Tag,则在这个节点失效时将无节点可读。这在有时候是期望的结果,有时候不是。例如:
Tag有时需要与优先级、选举权综合考虑。例如做报表的节点通常不会希望它成为主节点,则优先级应为0。
在readPreference选择了指定的节点后,readConcern 决定这个节点上的数据哪些是可读的,类似于关系数据库的隔离级别。可选值包括:
readConcern: local和available
在复制集中local和available是没有区别的,两者的区别主要体现在分片集上。
考虑以下场景:
一个chunk x正在从shard1向shard2迁移;
整个迁移过程中chunk x中的部分数据会在shard1和shard2中同时存在,但源分片shard1仍然是chunk x的负责方:
此时如果读shard2,则会体现出local和available 的区别:
注意事项
readConcern:majority
只读取大多数据节点上都提交了的数据。考虑如下场景:
如果在各节点上应用{readConcern: “majority”}来读取数据:
如何实现?
节点上维护多个x版本(MVCC机制),MongoDB通过维护多个快照来链接不同的版本:
测试readConcern: majority vs local
replication:
replSetName: rs0
enableMajorityReadConcern: true
rs0:PRIMARY> db.user.insert({count: 10})
rs0: PRIMARY> db.user.find ().readConcern("local")
rs0: PRIMARY> db.user.find ().readConcern("majority")
结论:
readConcern: majority与脏读MongoDB中的回滚:
所以从分布式系统的角度来看,事务的提交被提升到了分布式集群的多个节点级别的“提交",而不再是单个节点上的“提交”。
在可能发生回滚的前提下考虑脏读问题:
使用{readConcern: "majority"}可以有效避免脏读
如何安全的读写分离考虑如下场景:
下述方式有可能读不到刚写入的订单
db.orders.insert({oid:101,sku:"kite",q:1})
db.orders.find({oid: 101}).readPref("secondary")
使用writeConcern+readConcern majority来解决
db.orders.insert({oid:101,sku:"kite",q:1}, {writeConcern:{w:"majority")))
db.orders.find({oid:101}).readPref("secondary").readConcern("majority")
readConcern: linearizable
只读取大多数节点确认过的数据。和majority最大差别是保证绝对的操作线性顺序
{readConcern: “snapshot"》只在多文档事务中生效。将一个事务的readConcern 设置为snapshot,将保证在事务中的读:
因为所有的读都将使用同一个快照,直到事务提交为止该快照才被释放。
小结
MongoDB默认的事务隔离级别是:读已提交级别
snapshot不是串行化,应该是读已提交。