文章目录
- 一、介绍
- 1、存在的意义和作用?
- 2、需要实现啥功能?
- 3、典型案例
- 4、注意事项
- 二、搭建
- 1、安装MongoDB,配置环境变量
- 2、创建数据目录
- 3、配置文件
- 4、启动 MongoDB 进程
- 5、配置复制集
- 6、验证
- 三、写策略writeConcern
- 1、w参数
- 2、j参数
- 2.1 介绍
- 2.2 可选的参数值
- 2.3 实战
- 3、注意事项
- 四、读策略
- 1、readPreference
- 1.1 可选的值
- 1.2 适用场景
- 1.3 Teg
- 1.4 配置
- 1.5实战
- 2、readConcern
- 2.1 可选的值
- 2.2 majority
- 2.3 snapshot
一、介绍
1、存在的意义和作用?
主要意义在于实现服务高可用
作用
- 高可用:主要作用
- 数据分发:将数据从一个区域复制到另一个区域,减少另一个区域的读延迟
- 读写分离:不同类型的压力分别在不同的节点上执行
- 异地容灾:在数据中心故障时候快速切换到异地
2、需要实现啥功能?
- 数据写入时将数据迅速复制到另一个独立节点上
- 在接受写入的节点发生故障时自动选举出一个新的替代节点
数据是如何复制的?
- 当一个修改操作,无论是插入、更新或删除,到达主节点时,对数据的操作将被记录下来(经过一些必要的转换),这些记录称为oplog
- 从节点通过在主节点上打开一个tailable游标不断获取新进入主节点的oplog,并在自己的数据上回放,以此保持跟主节点的数据一致
如何通过选举完成故障恢复?
复制集中最多可以有50个节点,但具有投票权的节点最多7个
- 具有投票权的节点之间两两互相发送心跳(2s)
- 当5次心跳未收到时判断为节点失联(如果失联的是主节点,从节点会发起选举,选出新的主节点;如果失联的是从节点则不会产生新的选举)
- 选举基于 RAFT一致性算法实现,选举成功的必要条件是大多数投票节点存活
影响选举的因素?
- 集群存活节点大于一半
- 被选举为主节点的节点需要具备的条件:能够与多数节点建立连接、具有较新的 oplog、具有较高的优先级
常见选项配置?
- 是否具有投票权(v 参数):有则参与投票
- 优先级(priority 参数):优先级越高的节点越优先成为主节点
优先级为0的节点无法成为主节点 - 隐藏(hidden 参数):复制数据,但对应用不可见。隐藏节点可以具有投票仅,但优先级必须为0
- 延迟(slaveDelay 参数):复制 n 秒之前的数据,保持与主节点的时间差
3、典型案例
1主2从,最简单的复制集
备注:复制集需由3个及以上的具有投票权的节点组成
不同的节点功能?
主节点:接收写入操作,选举时投票
从节点:复制主节点上的新数据,选举时投票
投票节点:只负责投票,不存储数据,一般不推荐使用
4、注意事项
硬件的话,节点的配置必须一致,保证地位一样,另外一个就是要独立,保证不会同时宕机
软件的话,节点的版本必须一致,避免出现不兼容问题
增加节点不会增加系统写性能,可能降低写性能,这是因为需要同步,但会提高读性能
二、搭建
1、安装MongoDB,配置环境变量
这个不展开,之前介绍过,可以看入坑篇
2、创建数据目录
为3个复制集节点创建各自的数据目录,分别为data1、data2、data3
- Linux
mkdir -p /data/db{1,2,3} - Windows
md D:\mongodb\data\db1
md D:\mongodb\data\db2
md D:\mongodb\data\db3
3、配置文件
复制集的每个mongod进程应该位于不同的服务器,这里我们用一台机器,要弄3个进程,配置不同的端口号即可
systemLog:
destination: file
path: D:\mongodb\data1\mongod.log # 日志文件路径
logAppend: true
storage:
dbPath: D:\mongodb\data1 # 数据目录
net:
bindIp: 0.0.0.0
port: 28017 # 端口
replication:
replSetName: forlan0
说明:
bindIp为什么是0.0.0.0?适用所有网卡,都可以访问该服务
fork表示以后台进程运行,linux可以配置
这里只展示了1台,其它两个主要改端口号和数据、日志路径
4、启动 MongoDB 进程
mongod -f D:\mongodb\data\db1\mongod.conf
mongod -f D:\mongodb\data\db2\mongod.conf
mongod -f D:\mongodb\data\db3\mongod.conf
因为 Windows 不支持 fork,以上命令需要在3个不同的窗口执行,执行后不可关闭窗口否则进程将直接结束
5、配置复制集
- 方法1
rs.initiate()
rs.add("centosvm:28018")
rs.add("centosvm:28019")
centosvm是hostname
- 方法2
rs.initiate({_id: "forlan0",members: [{_id: 0,host: "localhost:28017"},{_id: 1,host: "localhost:28018"},{_id: 2,host: "localhost:28019"}]})
6、验证
主节点写入
db.test.insert({ a:1 })
从节点读取
rs.slaveOk()
db.test.find()
三、写策略writeConcern
决定一个写操作落到多少个节点上才算成功
1、w参数
- 0:发起写操作,不关心是否成功
- 1~n:写操作需要被复制到指定节点数才算成功
n为集群最大数据节点数;1的话,表示数据写入到Primary就向客户端发送确认,从节点采取异步线程去写 - majority:写操作需要被复制到大多数节点上才算成功
发起写操作的程序将阻塞,直到写操作到达指定的节点数为止
大多数节点确认模式
起码写到一个从节点才返回 - all:全部节点确认模式,可能长时间阻塞
2、j参数
2.1 介绍
Journaling,类似于关系数据库中的redolog,能够使MongoDB数据库由于意外故障后快速恢复,由于提交journal日志会产生写入阻塞,所以它对写入的操作有性能影响,但对于读没有影响
MongoDB2.4版本后默认开启了Journaling日志功能,mongod实例每次启动时都会检查journal日志文件看是否需要恢复
2.2 可选的参数值
- true:写操作落到 journal 文件中才算成功
- false:写操作到达内存即算作成功
对于5个节点的复制集来说,写操作落到多少个节点上才算是安全的?
至少3个节点或者配置为majority
2.3 实战
1、测试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 }})
2、配置延迟节点,模拟网络延迟(复制延迟)
conf=rs.conf()
conf.members[2].secondaryDelaySecs = 5 //老版本可能是conf.members[2].slaveDelay = 5
conf.members[2].priority = 0 // 不具备选举权限
rs.reconfig(conf)
观察复制延迟下的写入:执行插入操作,设置timeout参数,超时报错
db.test.insert( {count: 1}, {writeConcern: {w: 3, wtimeout:3000 }})
3、注意事项
- 虽然多于半数的 writeConcern 都是安全的,但通常只会设置 majority,因为这是等待写入延迟时间最短的选择
- 不要设置 writeConcern 等于总节点数,因为一旦有一个节点故障,所有写操作都将失败
- writeConcern 虽然会增加写操作延迟时间,但并不会显著增加集群压力,因此无论是否等待,写操作最终都会复制到所有节点上
设置 writeConcern 只是让写操作需要等待复制完成后,再返回而已 - 重要数据应用设置{w: “majority”},以确保数据不丢失
- 普通数据应用 设置{w: 1} ,以确保最佳性能
四、读策略
1、readPreference
解决的是,从哪里读,决定使用哪一个节点来满足正在发起的读请求
1.1 可选的值
- primary: 只选择主节点(默认值);
- primaryPreferred:优先选择主节点,如果不可用则选择从节点;
- secondary:只选择从节点;
- secondaryPreferred:优先选择从节点,如果从节点不可用则选择主节点;
- nearest:选择最近的节点;
指定 readPreference 时也应注意高可用问题,例如将 readPreference 指定 primary,则发生故障转移不存在没有节点可读。如果业务允许,则应选择 primaryPreferred
1.2 适用场景
- primary/primaryPreferred:用户下订单后马上将用户转到订单详情页(查询时效性要求)
- secondary/secondaryPreferred:用户查询自己下过的订单(查询历史订单对时效性通常没有太高要求)
- secondary:生成报表,对时效性要求不高,但资源需求大,可以在从节点单独处理,避免对线上用户造成影响
- nearest:将用户上传的图片分发到全世界,让各地用户能够就近读取(每个地区的应用选择最近的节点读取数据)
1.3 Teg
作用?
因为readPreference 只能控制使用一类节点,而它可以选择控制到一个或几个节点
场景?
一个 5 个节点的复制集:
- 3 个节点硬件较好,专用于服务线上客户
- 2 个节点硬件较差,专用于生成报表
所以可以使用 Tag 来达到这样的控制目的,在线应用读取时指定{purpose: “online”},报表读取时指定 {purpose: “analyse”}
1.4 配置
- 通过 MongoDB 的连接串参数
xxx?replicaSet=rs&readPreference=secondary - 通过 MongoDB 驱动程序 API
MongoCollection.withReadPreference(ReadPreference readPref) - Mongo Shell
db.collection.find({}).readPref(“secondary”)
1.5实战
主节点写入 {x:1}, 观察该条数据在各个节点均可见
db.test.drop()
db.test.insert({x:1})
在两个从节点分别执行 db.fsyncLock() 来锁定写入(同步)
db.fsyncLock()
主节点写入 {x:2}
db.test.insert({x:2})
主节点上看到有2条数据
从节点上看到只有1条数据
解除从节点锁定 db.fsyncUnlock()
在从节点上看,可以看到同步的数据了
db.test.find().readPref("secondary")
2、readConcern
解决的是,什么数据可以读
决定这个节点上的数据哪些是可读的,类似于关系数据库的隔离级别
2.1 可选的值
有事务要求,使用snapshot,否则使用majority
- available
读取所有可用的数据 - local
读取所有可用且属于当前分片的数据 - majority
读取在大多数节点上提交完成的数据 - linearizable
可线性化读取文档 - snapshot
读取最近快照中的数据
2.2 majority
只读取大多数据节点上都提交了的数据
如何实现?
节点上维护多个 x 版本,MVCC 机制,通过维护多个快照来链接不同的版本
- 每个被大多数节点确认过的版本都将是一个快照
- 快照持续到没有人使用为止才被删除
实战:
1、安装 3 节点复制集(一主两从)
2、配置文件内 server 参数 enableMajorityReadConcern
3、将复制集中的两个从节点使用 db.fsyncLock() 锁住写入(模拟同步延迟)
db.test.save({"x":1})
db.test.find().readConcern("local")
db.test.find().readConcern("majority")
解锁后就正常了
结论:
写操作在没到达大多数节点前,主节点挂了,写操作就丢失了,操作回滚,可以理解为事务,只不过是大多数节点成功才提交,所以可以避免脏读
2.3 snapshot
只在多文档事务中生效,类似隔离级别,不可重复读
测试:
1、准备数据
db.tx.insertMany([{ x: 1 }, { x: 2 }]);
2、事务操作
var session = db.getMongo().startSession();
session.startTransaction();
var coll = session.getDatabase('test').getCollection("tx");
coll.updateOne({x: 1}, {$set: {y: 1}});
coll.findOne({x: 1});
db.tx.findOne({x: 1});
session.abortTransaction();
事务内操作db.tx.findOne({x: 1}),得到值 {x:1, y:1}
事务外操作db.tx.findOne({x: 1}),得到值 {x:1}
3、可重复读Repeatable Read,事务操作
var session = db.getMongo().startSession();
session.startTransaction({readConcern: {level: "snapshot"},writeConcern: {w: "majority"}});
var coll = session.getDatabase('test').getCollection("tx");
coll.findOne({x: 1});
db.tx.updateOne({x: 1}, {$set: {y: 1}});
db.tx.findOne({x: 1});
coll.findOne({x: 1});
session.abortTransaction();
两次读取结果一样,如下:
注意事项
- 事务默认必须在 60 秒(可调)内完成,否则将被取消
- 涉及事务的分片不能使用仲裁节点
- 事务会影响 chunk 迁移效率。正在迁移的 chunk 也可能造成事务提交失败(重试即可)
- 多文档事务中的读操作必须使用主节点读
- readConcern 只应该在事务级别设置,不能设置在每次读写操作上