副本集是一组服务器,其中有一个是主服务器(primary),用于处理客户端请求;还有多个备份服务器(secondary),用于保存主服务器的数据副本。如果主服务器崩溃了,备份服务器会自动将其中一个成员升级为新的主服务器。
bj1-farm1:PRIMARY> rs.isMaster()
{
"setName": "bj1-farm1",
"setVersion": 4,
"ismaster": true,
"secondary": false,
"hosts": [
"172.16.0.150:27017",
"172.16.0.152:27017",
"172.16.0.151:27017"
],
"primary": "172.16.0.150:27017",
"me": "172.16.0.150:27017",
"maxBsonObjectSize": 16777216,
"maxMessageSizeBytes": 48000000,
"maxWriteBatchSize": 1000,
"localTime": ISODate("2014-12-01T08:20:34.014Z"),
"maxWireVersion": 2,
"minWireVersion": 0,
"ok": 1
}
config ={
"_id":"spock",
"members":[
{"_id":0,"host":"10.0.11.243:27017"},
{"_id":1,"host":"10.0.11.244:27017"}
]
}
db = (newMongo("10.0.11.243:27017")).getDB("test")
rs.initiate(config)
for(i=0;i<1000;i++){db.coll.insert({count:i})}
db.coll.count()
db.setSlaveOk()
db.coll.count()
Rs.stopSet()
Rs是一个全局变量,其中包含与复制相关的辅助函数可用rs.help(),这些函数大多只是数据库命令的包装器,例如
Db.adminCommand({“replSetInitiate”:Config})和之前的是等价的
需要保证副本集每个成员能够互相到达,不应该使用localhost作为主机名
rs.add(“server-3:27017”)
rs.REMOVE(“SERVER-3:27017”) 在删除成员时会报错无法连接到数据库的信息,这是正常的,说明配置修改成功了。重新配置副本集过程中的最后一步,主节点会关闭所有连接,因此shell会短暂断开然后重新自动建立连接
spock:PRIMARY> rs.config()
{
"_id": "spock",
"version": 1,
"members": [
{
"_id": 0,
"host": "10.0.11.243:27017"
},
{
"_id": 1,
"host": "10.0.11.244:27017"
}
]
}
每次修改完,version会自增,初始值为1;
也可以创建新的配置文档,再使用rs.reconfig重新配置副本集
>var config=rs.config()
>config.members[0].host = “server-0:27017”
>rs.reconfig(config)
副本集一个很重要的概念是“大部分”:选择主节点需要大多数决定,即副本中一半以上的成员,所以如果使用只有两个成员的副本集,一个挂了,网络的任何一端都无法达到大多数的要求,因此通常不建议使用这样的配置
1 将“大多数”成员放在同一个数据中心
2 在两个数据中心各自放置数量相等的成员,在第三个地方放置一个用于决定胜负的副本集成员
仲裁者唯一的作用就是选举,不保存数据,也不会为客户端提供服务,它只是为了满足“大部分”的要求
添加仲裁者的两种方式
>rs.addArb(“server-5:27017”)
>rs.add({“_id”:4,”host”:” server-5:27017”,”arbiterOnly”:true})
仲裁的缺点
比如有三个成员的副本集,其中一个是仲裁节点。当一个数据节点挂了,那么另一个数据节点成为主节点,为了保证数据安全,就需要添加一个新的备份节点,但由于仲裁节点无数据,那么新节点的数据传输只能靠当前的主节点完成。那么它不仅要处理应用程序请求,还要数据复制到备份节点,会造成服务器压力巨大
所以尽量配置成奇数个数据成员,而不使用仲裁者
优先级表示一个成员渴望成为主节点的程度,取值范围0-100,默认1,0代表永远不能成为主节点(passive member)
优先级别高的会优先选举为主节点(只要他能得到大部分的支持,并且数据是最新的)
客户端不会向隐藏成员发送请求,隐藏成员也不会成为复制源(除非其他复制源不可用),因此可以将不够强大的服务器或者备份服务器隐藏起来,只有优先级别为0的成员才能被隐藏,通过设置配置中的hidden:true来隐藏。隐藏以后执行isMater()将看不到隐藏成员,使用rs.status和rs.config()能够看到隐藏成员。因为客户端连接到副本集时,调用的是isMaster()来查看可用成员
用于灾难保护,和mysql延迟复制功能类似,使用slaveDelay设置一个延迟的备份节点,要求成员优先级别为0
有时备份节点不需要与主节点一样的索引,甚至不用索引,尤其这个备份节点用途仅仅只是处理数据备份或者是离线的批量任务,那么可以再配置中指定buildIndex:false。要求此节点优先级别为0,并且是个永久性行为,除非删除这个节点并重新添加到这个副本集进行数据同步。
Mongo的复制功能是通过oplog实现的,oplog包含了主节点的每一次写操作,是主节点的local数据库中的一个固定集合,备份节点通过查询这个集合就可以知道需要进行复制的操作。每个备份节点有自己的oplog,这样每个成员就可以当作同步源提供给其他成员使用。备份节点从当前同步源中获取需要执行的操作,然后在自己的数据集上执行这些操作,最后将这些操作写入自己的oplog。
如果备份节点挂了,当它重启后,会自动从oplog中最后一个操作开始同步,由于复制操作的过程是先复制数据再写入oplog,所以备份节点有可能在已经同步过的数据上再次执行复制操作。Mongo是这么处理的:将oplog中的同一操作执行多次与只执行一次的效果是一样。
由于oplog大小是固定的,通常它的使用空间的增长速度与系统处理写请求的速率近乎相同。但是有些例外情况:如果单次处理能够影响到多个文档,那么每个受影响的文档都会对应oplog的一条日志。比如执行db.coll.remove()删除了1000000个文档,那么oplog中就会有1000000条操作日志,这样oplog很快就会被填满。
副本集的成员启动后,就会检查自身状态,确定是否可以从某个成员那里进行同步,如果不行,它会尝试从副本的另外一个成员那里进行完整的数据复制,这就是初始化同步(initial sync),一般有如下几个步骤
1 选择一个成员作为同步源,在local.me中为自己创建一个标示符,删除所有已存在的数据库,已一个全新的状态开始同步
2 克隆,将同步源的所有记录全部复制到本地
3 oplog同步,克隆过程中的操作都会被记录到oplog。如果有文档在克隆过程中被移动了,那么就可能被遗漏,需要重新进行克隆
4 将第一个oplog同步中的操作记录下来,只有在没有东西需要克隆时,才会与第一个不同
5 创建索引
6 将创建索引期间的索引操作同步过来
7 完成初始化,切换到普通同步状态
初始化同步引发的问题及解决方案
如果要跟踪初始化同步过程,最好的方法是查询服务器日志
初始化同步操作简单,但是速度太慢,远不如从备份中恢复
克隆可能损坏同步源的工作集。实际部署后会有一个频繁使用的数据子集在内存中,执行初始化同步会强制将当前成员的所有数据分页加载到内存中,这会导致频繁数据不能常驻内存,进而导致很多请求变慢。不过对于较小的数据集和性能比较好的服务器,初始化同步是个简单易用的选项。
初始化同步如果耗费太长时间,新成员就会与同步源脱节,导致新成员的数据同步速度赶不上同步源的变化速度,同步源可能会将新成员需要复制的某些数据覆盖掉。这个问题没什么好的方法解决,只能在不太忙的时候执行初始化同步。或者是让主节点使用比较大的oplog保存足够多的操作日志是很重要的!
每个成员每隔两秒都会向其他成员发送一个心跳请求,用于检查每个成员的状态,知道自己是否符合大多数的条件。
如果主节点执行了一次写请求后挂了,但是备份节点还没来得及复制这次操作,那么新选举出来的主节点就会漏掉这次写操作。这时就会执行回滚过程。
如果回滚内容太多,mongo会承受不了,如果要回滚的数量大于300m或者大于30分钟,则回滚失败,需要重新同步。
连接到副本集和连接到单台服务器类似,常用的连接字符串如下:
“mongodb://server-1:27017,server-2:27017”
主节点挂了后,驱动程序会尽快自动找到新的主节点。在选举过程中,主节点可能会导致暂时不可用,这段时间内不会处理任何请求(读或写),但是可以选择将读请求路由到备份节点
使用getLastError命令检查写入是否成功,也可以用它确保写入操作被复制到备份节点。参数“w”会强制要求getLastError等待,一直到给定数量的成员都执行完了最后的写入操作。可通过majority关键字传递该值,wtimeout是超时时间
spock:PRIMARY>db.runCommand({"getLastError":1,"w":"majority","wtimeout":1000})
{
"lastOp": Timestamp(0, 0),
"connectionId": 4776,
"n": 0,
"syncMillis": 0,
"writtenTo": null,
"err": null,
"ok": 1
}
通常“w”用来控制写入速度,mongo写入太快,主节点执行完写入操作后,备份节点来不及跟上,通过定期调用getLastError,设置w值大于1可以强制这个连接上的写操作一直等待直到复制成功,但是这会阻塞写操作
1 保证复制到每个数据中心的一台服务器上
2 保证写操作被复制到可见节点中的大多数
3 创建其他规则
将读请求发送到备份节点
1 出于一致性考虑
2 出于负载的考虑
适用场景
1 主节点挂了,要求备份节点能够读取数据(primarypreferred)
2 根据驱动程序到副本集的ping时间获得低延迟的数据,即Nearest参数。如果应用程序需要从多个数据中心读取到最低延迟的同一文档,这是唯一的方法。如果文档和位置的相关性更大,那就使用分片。如果是要求低延迟的读和写,那必须是分片方案。
3 如果能够接受任何陈旧数据,那就使用secondary它会始终将读请求发送到备份节点
4 secondary preferred:优先发送到可用的备份节点,不行再master
5 一般实时性要求高就primary,不是很高就primarypreferred
spock:PRIMARY> db.serverCmdLineOpts()
{
"argv": [
"mongod",
"-f",
"/etc/mongod.conf"
],
"parsed": {
"config": "/etc/mongod.conf",
"net": {
"bindIp": "10.0.11.244"
},
"processManagement": {
"fork": true,
"pidFilePath": "/var/run/mongodb/mongod.pid"
},
"replication": {
"replSet": "spock"
},
"storage": {
"dbPath": "/var/lib/mongo"
},
"systemLog": {
"destination": "file",
"logAppend": true,
"path": "/var/log/mongodb/mongod.log"
}
},
"ok": 1
}
rs.stepDown(60) //60秒内没有其他人升为主节点就要求该节点重新进行选举
rs.freeze(1000)//在每个备份节点上执行,阻止其成为主节点,为了主节点做维护时防止其他节点篡位,rs.freeze(0)表示维护完成后可释放
监控复制最简单的方式就是查看日志
spock:SECONDARY>db.printSlaveReplicationInfo()
source: 10.0.11.243:27017
syncedTo:Mon Dec 01 2014 18:12:32 GMT+0800 (CST)
0secs (0 hrs) behind the primary
1 如果当前服务器是主节点,让他退位,使得其他成员能够尽快更新到与它一致
2 关闭当前服务器
3 将当前服务器已单机模式启动
4 临时将oplog中的最后一条insert操作保存到其他集合中
5 删除当前的oplogdb.oplog.rs.drop()
6 创建一个新的oplog
7 将最后一条操作记录写回oplog
8 将当前服务器作为副本集成员重新启动
方法1:可能导致这个成员过载
1 关闭所有其他成员
2 删除其他成员数据目录中的所有数据
3 重启所有成员
方法2:导致每个服务器有相同大小的oplog
1 关闭所有其他成员
2 删除其他成员数据目录中的所有数据
3 将延迟备份节点的数据文件复制到其他服务器
4 重启所有成员