MongoDB可以把数据从一个节点复制到其它节点,并在修改时同步数据,这就是复制集群。复制集群中每个mongdb的节点存储的数据是一样的。
注意:虽然可复制集群中数据是冗余的,但是它无法取代数据备份机制。备份是过去某个时间点的数据库快照,可复制集群通常是最新的。有时候数据量太大,可能备份很困难。但通常备份都是必须的,即使运行了可复制集群也需要启动备份。可以使用备份节点实现。
复制的应用场景
- 可以使数据冗余,可以确保从节点与主节点的数据同步。主从节点可以位于不同的数据中心。
- 复制是异步的,网络延迟不会影响主节点的性能。
- 复制节点可以设置延迟同步数据,防止用户无意的删除数据
- 用来在从节点上进行数据备份,而不是在主节点备份数据,可以缓解主节点的压力。
- 可以在从节点上构建索引,之后切换主从节点,减小构建索引的压力
- 复制模式可以缓解系统的读压力。
复制不能解决的问题
- 现有的硬件无法处理工作负荷,如数据集超过内存大小,复制集群不能解决这个问题,因为复制后单节点的数据量并没有减少。
- 写入读取的比例超过50%,不适合部署复制集群。因为每个主节点的写入都会同步到从节点,如果从节点的写入数据较多,会影响从节点的读取和写入性能。可能会造成从节点的读写延迟
- 应用程序要求数据的读取一致性。因为同步是有延迟的,主从节点的数据并不能保持强一致性。
安装
可复制集群的最小化安装需要3个mongod节点,如果只有两个节点,一但主节点宕机,就无法进行投票表决,选举新的主节点。
部署方式有两种
1. 三个实例都为存储数据的服务器
2. 两个数据存储服务器+一个裁判服务器,裁判服务器不存储数据,只参与主节点的选举。
安装详细步骤
环境准备:
centos7.2;
mongo3.6;
安装方式为:2个数据节点+1裁判节点
- 创建数据文件
mkdir -p /data/node1
mkdir -p /data/node2
mkdir -p /data/arbiter
- 创建节点配置文件
vim ~/repl_node1.cnf
dbpath=/data/node1/
logpath=/data/node1/mongo.log
logappend=true
fork=true
port=40000
vim ~/repl_node2.cnf
dbpath=/data/node2/
logpath=/data/node2/mongo.log
logappend=true
fork=true
port=40001
vim repl_arbiter.cnf
dbpath=/data/arbiter/
logpath=/data/control/mongo.log
logappend=true
fork=true
port=40002
- 启动mongod实例
mongod --replSet myapp -f ~/repl_node1.cnf
mongod --replSet myapp -f ~/repl_node2.cnf
mongod --replSet myapp -f ~/repl_arbiter.cnf
- 集群配置
连接非裁判节点
mongo --port 40000
执行 rs.initiate() 初始化集群
输出
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "localhost:40000",
"ok" : 1,
"operationTime" : Timestamp(1562984114, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1562984114, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
此时执行rs.status() 可以看到当前集群中已经有一个节点了
rs.status()
{
"set" : "myapp",
"date" : ISODate("2019-07-13T02:17:58.831Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1562984276, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1562984276, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1562984276, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1562984276, 1),
"t" : NumberLong(1)
}
},
"members" : [
{
"_id" : 0,
"name" : "localhost:40000",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 614,
"optime" : {
"ts" : Timestamp(1562984276, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2019-07-13T02:17:56Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1562984114, 2),
"electionDate" : ISODate("2019-07-13T02:15:14Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
}
],
"ok" : 1,
"operationTime" : Timestamp(1562984276, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1562984276, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
在添加其他两个节点
rs.add("localhost:40001")
rs.addArb("localhost:40002")
集群信息查看
rs.isMaster() 查看集群的摘要信息
rs.status() 查看集群的详细信息
开启从节点读取
集群启动后,从节点默认是不允许读取的,因为数据的同步可能比较慢。需要在从节点上执行
rs.slaveOk() 开启从节点的读取。
复制集的工作原理
可复制集依赖两个机制,oplog和heatbeat
oplog实现数据的复制功能
hearbeat 监控集群状态并触发灾备
oplog
- oplog是一个盖子集合,存储在每个复制节点的local数据库里,记录了所有数据的变化
- 当有数据写入到主节点的数据,都会写入到主节点的oplog中
- 数据复制到某个从节点后,也会写入到从节点的oplog中。
查看oplog数据
在主节点插入数据
use school
switched to db school
myapp:PRIMARY> db.students.insert({name:'zhangsan',age:16,sex:'man'})
查看oplog
db.oplog.rs.find({op:'i'}).pretty()
{
"ts" : Timestamp(1562985810, 3), 文档时间戳 :1970开始的秒数 + 计数器3
"t" : NumberLong(1),
"h" : NumberLong("-7265383297214975045"),
"v" : 2,
"op" : "i", 表示日志的操作类型 i 表示insert
"ns" : "school.students", 命名空间:当前操作的 数据库.集合
"ui" : UUID("5e9fc652-9276-4a1a-b21b-b7b7f2a0f122"),
"wall" : ISODate("2019-07-13T02:43:30.381Z"),
"o" : { //文档信息
"_id" : ObjectId("5d294552d0a0923c139d2149"),
"name" : "zhangsan",
"age" : 16,
"sex" : "man"
}
}
当一条命令修改了多个文档的时候,会产生多个oplog日志。这是为了保证服务器间数据的幂等性。
查看oplog状态信息
db.getReplicationInfo()
{
"logSizeMB" : 1342.1103515625,
"usedMB" : 0.04,
"timeDiff" : 2662,
"timeDiffHours" : 0.74,
"tFirst" : "Sat Jul 13 2019 10:15:14 GMT+0800 (CST)",
"tLast" : "Sat Jul 13 2019 10:59:36 GMT+0800 (CST)",
"now" : "Sat Jul 13 2019 10:59:45 GMT+0800 (CST)"
}
从节点复制数据的步骤
- 查看自己oplog里最新记录的时间戳
- 查询主节点oplog中所有大于自己时间戳的记录。
- 写入数据,并添加每个操作日志到自己的oplog中。
这意味着,到主节点故障,提升任意节点为主节点时,都会有一个oplog,供其他节点同步数据。实现了可复制集群的故障恢复功能
停止复制
如果从节点无法在主节点oplog找到同步的日志记录点,就会永久停止复制。
由于oplog是一个盖子集合,如果从节点离线一段时间,主节点的oplog可能无法存储这段时间所有的日志。如果启动复制会导致部分数据的丢失。
唯一的解决方案就是重新同步主节点的所有数据。
可以通过监控主从延迟和设置oplog的大小来避免这种问题的发生。
oplog大小设置
在mongo3.0中,操作步骤为,停止mongod实例,然后作为独立节点启动,修改oplog大小,重新启动成员。
心跳和故障转移
集群的心跳便于实现选举和灾备。默认每个节点会每隔2s ping一次其他节点。可以通过rs.status 查看每个节点最新的心跳状态 1表示正常,0表示无应答。
- 当某个从节点失去响应,集群中大部分节点正常,集群不会做出改变,只是简单的等待从节点恢复
- 如果主节点故障,大多数节点正常,此时会选举一个从节点作为主节点
- 如果从节点和裁判节点故障,只有一个主节点存在,此时主节点会降级为从节点。
场景:如果出现网络分区,导致主节点与其他节点心跳失败。但其他节点仍然在线,并且互相心跳正常,其他节点就会选取一个新的主节点对外提供服务。如果原来的主节点不降级,就会有两个主节点对外提供服务。
提交和回滚
当向主节点写入数据时,这些写入操作不会被提交,知道它们被复制给大多数节点。
回滚场景:
假设我们在主节点插入了一些数据,但是由于某种原因,这些数据没有同步到从节点。这时主节点宕机,从节点提升为主节点,这时我们向新的主节点写入数据。这时原来的主节点重新上线,成为集群中的从节点,从现有的主节点同步数据。当旧的主节点有一些写入数据在新的主节点的oplog中不存在时,就会触发回滚。
在回滚时,所有没有复制给大多数节点的写入操作都会被取消。
回滚日志:
这些还原的写操作存储在相关节点数据目录的子目录里,对于每个回滚的写入集合,都会创建一个单独的BSON文件,文件名包含回滚的时间。如果需要恢复回滚数据可以使用bsondump 和mongorestore来操作。
通过设置写关注点,可以减少复制集群的回滚的发生。
集群配置
集群的json文档初始化方式
声明config json对象
config={_id:'myapp',members:[]}
config.members.push({_id:0,host:'localhost:40000'})
config.members.push({_id:1,host:'localhost:40001'})
config.members.push({_id:2,host:'localhost:40002',arbiterOnly:true})
结果:
config
{
"_id" : "myapp",
"members" : [
{
"_id" : 0,
"host" : "localhost:40000"
},
{
"_id" : 1,
"host" : "localhost:40001"
},
{
"_id" : 2,
"host" : "localhost:40002",
"arbiterOnly" : true
}
]
}
rs.initiate(config) 将配置的config对象传递给初始化函数,
成员参数说明
参数 | 说明 |
---|---|
_id | 唯一的自增整数,用来表示成员id,这个id从0开始,而且必须是递增的 |
host | 存储主机的名字和端口,冒号分割 |
arbiterOnly | boolean值,表示这个成员是否是裁判 |
priority | 指定此节点被选为主节点的优先级,优先级越高,被选为主节点的概率越大,如果为0,表示此节点永远不会被选为主节点 |
votes | 默认情况下,所有的节点都有一次投票机会,votes可以允许某个节点投多票 |
hidden | boolean值,当为true时,用isMaster查询时不会显示此节点,可以和buildIndexes一起使用,必须和slaveDelay一起使用 |
buildIndexes | boolean值,默认为true,用来决定此成员是否构建索引,只有当前节点不会为主节点时才会设置为false,备份节点不需要构建索引 |
slaveDelay | 从节点与主节点同步的延迟时间,只有不是主节点的实例上使用,防止用户操作错误。丢失数据 |
tags | 标签,包含键值对的集合文档,通常用来区分数据中心,及节点机房位置 |
集群的全集参数
config
{
"_id" : "myapp",
"members" : [...],
settings:{
getLastErrorDefaults:{
w:2,
wtimeout:500
},
getLastErrorModes:{
}
}
}
参数 | 说明 |
---|---|
getLastErrorDefaults | 当客户端无参数调用getLastError时,返回的默认值 |
getLastErrorDefaults | 为getLastError命令定义了额外模式的文档 |
部署策略
在生产环境中建议部署方式:
- 两个复制节点+1个裁判节点,由于裁判节点不占用资源,可以和应用部署在同一个服务器上,这种部署方式可以满足大部分服务
- 3个复制节点,此种方式提高了应用的 稳定性,当一个复制节点宕机,还有两个节点可以对外提供服务。可以将节点部署在两个数据中心,达到异地灾备的目的。
写关注点
mongo默认的写关注点为1,及只要数据写到主节点就会提交,为了减少集群的回滚,在写入数据时,可以动态的指定操作的写关注点。也可以在集群中配置默认的写关注点,较大的写关注点,会影响集群的写入性能。
总结
- 在部署生产环境时,应该部署复制集群,否则就需要频繁的备份数据
- 复制集群至少需要3个节点,其中一个可以是裁判节点
- 数据在复制到大多数节点的时候,才会提交。在故障的场景中,如果大多数节点正常,集群可以正常对外服务。没有达到大多数节点的写请求会被放入数据的回滚目录下,必须手动处理
- 如果从节点宕机一段时间,并且这段时间的写入数据没有写入主节点的oplog,这是就需要手动恢复从节点的数据。
- 驱动的写关注点可以控制写入请求写入多少个节点后才返回,可以增加应用的持久性,但是降低了写入性能
- 可以设置集群的读偏好,分散集群主节点的读取压力。