1、主从复制
主从复制是MongoDB最常用的复制方式,这种方式很灵活.可用于备份,故障恢复,读扩展等。最基本的设置方式就是建立一个主节点和一个或多个从节点,每个从节点要知道主节点的地址。
运行mongod --master就启动了主服务器;
运行mongod --slave --source master_address就启动了从服务器。
其中master_address是主节点的地址。
1.1、建立主节点
首先给主节点建立数据目录:
[[email protected] ~]# mkdir -p /opt/mongo/master
[[email protected] ~]# mongod --dbpath /opt/mongo/master/ --port 10000 --master
1.2、建立从节点
选择不同的目录和端口,并且用--source为从节点指明主节点的地址:
建立从节点slave1:
[[email protected] data]# mkdir -p /opt/mongo/slave1
[[email protected] data]# mongod --dbpath /opt/mongo/slave1/ --port 10001 --slave --source localhost:10000
建立从节点slave2
[[email protected] ~]# mkdir -p /opt/mongo/slave2
[[email protected] ~]# mongod --dbpath /opt/mongo/slave2 --port 10002 --slave --source localhost:10000
注意:所有从节点都从主节点复制内容,还没有从从节点复制的机制,原因是从节点并不保存自己的oplog。
一个集群中有多少个从节点并没有明确的限制,但是上千个从节点对单个主机节点发起查询也会让其负担过重,所以实际上,不超过12个从节点的集群就可以运转良好。
1.3、数据测试
主节点:
[[email protected] ~]# mongo localhost:10000
MongoDB shell version: 2.6.6
connecting to: localhost:10000/test
......
> db.rgf.insert("name":"rgf","age":10,"sex":"male")
2015-02-11T11:20:36.909+0800 SyntaxError: Unexpected token :
> db.rgf.insert({"name":"rgf","age":10,"sex":"male"})
WriteResult({ "nInserted" : 1 })
> db.rgf.find()
{ "_id" : ObjectId("54daca943149616d6b6d9714"), "name" : "rgf", "age" : 10, "sex" : "male" }
slave1:
[[email protected] ~]# mongo localhost:10001
MongoDB shell version: 2.6.6
connecting to: localhost:10001/test
Server has startup warnings:
......
> use test
switched to db test
> db.rgf.find()
{ "_id" : ObjectId("54daca943149616d6b6d9714"), "name" : "rgf", "age" : 10, "sex" : "male" }
slave2:
[[email protected] ~]# mongo localhost:10002
MongoDB shell version: 2.6.6
connecting to: localhost:10002/test
......
> use test
switched to db test
> db.rgf.find()
{ "_id" : ObjectId("54daca943149616d6b6d9714"), "name" : "rgf", "age" : 10, "sex" : "male" }
>
主从数据同步。
1.4、主从复制选项
1 | --only | 在从节点上指定只复制特定某个数据库(默认复制所有数据库) |
2 | --slavedelay | 用在从节点上,当应用主节点的操作时增加延时(s)。这样就能轻松设置延时节点,这种节点对用户无意中删除重要文档或者插入垃圾数据等有防护作用,这些不良操作都会被复制到所有的从节点上,通过延时执行操作,可以有个恢复的时间差。 |
3 | --fastsync | 以主节点的数据快照为基础启动从节点。如果数据目录一开始是主节点的数据快照,从节点用这个选项启动要比做完整同步快很多。 |
4 | --autoresync | 如果从节点与主节点不同步了,则自动重新同步。 |
5 | --oplogSize | 主节点oplog的大小 |
1.5、添加及删除源
1.5.1添加节点
启动从节点时,可以用--source指定主节点,也可以在shell中配置这个这个源。
从节点启动示例:
[[email protected] ~]# mongod --slave --dbpath /opt/mongo/slave --port 10001
这个功能也可以在shell中运行,并且更加灵活:
> use local
switched to db local
> db.sources.insert({"host":"localhost:10000"})
插入源后,可以看到插入的文档:
> use local
switched to db local
> db.sources.find()
{ "_id" : ObjectId("54dac7ca275148d655ca1792"), "host" : "localhost:10000"}
当完成同步,该文档即被更新。
> db.sources.find()
{ "_id" : ObjectId("54dac7ca275148d655ca1792"), "host" : "localhost:10000", "source" : "main", "syncedTo" : Timestamp(1423632078, 1) }
1.5.2删除节点
如果想更改从节点的配置,改为比如10.100.11.102:10000为源,则可以用insert和remove来完成。
> db.sources.insert({"host":"10.100.11.102:10000"})
> db.sources.remove({"host":"localhost:10000"})
可以看到,sources集合可以被当做普通集合进行操作,而且为管理节点提供了很大的灵活性。
2、副本集
副本集(Replica Set)就是有自动故障恢复功能的主从集群。主从集群和副本集最明显的区别是副本集没有固定的"主节点",整个集群会选举出一个"主节点",当其不能工作时则变更到其他节点。副本集总会有一个活跃节点(primary)和一个或多个备份节点(secondary)。副本集可以在活跃节点有问题时自动切换。当活跃节点不工作了,备份节点就会成为活跃节点;如果原来的活跃节点恢复了,它会成为新的活跃节点的备份节点。
2.1、初始化副本集
副本集不能用localhost地址作为成员,所以的找到机器的主机名:
[[email protected] ~]# echo ${HOSTNAME}
gflinux102
2.1.1、创建数据目录
要为每一个服务器创建数据目录,选择端口:
[[email protected] mongo]# mkdir -p /opt/mongo/node{1,2}
2.1.2、命名副本集
名字是为了易于与别的副本集区分,也是为了方便将整个集合视为一个整体,这里命名为"gf"。--replSet是让服务器知晓这个"gf"副本集中还有别的同伴。
[[email protected] mongo]# mkdir -p /opt/mongo/node{1,2}
2.1.2、启动副本集
启动第一台:
[[email protected] mongo]# mongod --dbpath /opt/mongo/node1 --port 10001 --replSet gf/gflinux102:10002
启动第二台:
[[email protected] data]# mongod --dbpath /opt/mongo/node2/ --port 10002 --replSet gf/gflinux102:10001
如果想要启动第三台:
[[email protected] data]# mongod --dbpath /opt/mongo/node3/ --port 10003 --replSet gf/gflinux102:10001
[[email protected] data]# mongod --dbpath /opt/mongo/node2/ --port 10002 --replSet gf/gflinux102:10001,gflinux102:10002
副本集的亮点就是有自检测功能:在其中指定单台服务器后,Mongodb就会自动搜索并连接其他的节点。
启动了几台服务器之后,日志就会告诉你副本集没有进行初始化,因为还差最后一步:
2.1.2、shell中初始化副本集
在shell中,连接其中一个服务器,初始化命令只能执行一次:
[[email protected] data]#mongo gflinux:10001/admin
>db.runCommand({"replSetInitiate":{
..."_id":"gf",
..."members":[
... {
... "_id":1,
... "host":"gflinux102:10001"
... },
... {
... "_id":2,
... "host":"gflinux102:10002"
... }
...]}})
{
"info":"Config now saved locally.Shoule come online in about a
minute.",
"ok": true
}
"_id":"gf" :副本集的名字
"members":[...] :副本集中的服务器列表,每个服务器文档至少有两个键。
"_id":N:每个服务器的唯一ID
2.1.3、副本集中的节点
任何时间,集群中只有一个活跃节点,其他的都是备份节点.活跃节点实际上是活跃服务器,指定的活跃节点可以随时间而改变.
有几种不同类型的节点可以存在与副本集中
standard 标准节点
这是常规节点,它存储一份完整的数据副本,参与选举投票有可能成为活跃节点
passive 被动结点
存储了完整的数据副本,参与投票,不能成为活跃节点
arbiter 仲裁者
仲裁者只能参与投票,不接收复制的数据,也不能成为活跃节点.
标准节点和被动节点之间的区别仅仅是数量的差别,每个参与节点(非仲裁)有优先权.
优先权按照优先值从大到小.
在节点配置中修改priority键,来配置标准节点或者被动节点.
>members.push({"_id":3,"host":"127.0.0.1:10002","priority":40})
默认优先级为1,可以是0-1000(含)
"arbiterOnly"键可以指定仲裁节点
>members.push({"_id":4,"host":"127.0.0.1:10003","arbiterOnly":true})
备份节点会从活跃节点抽取oplog,并执行操作,就像活跃备份系统中的备份服务器一样.活跃节点也会写操作
到自己的本地oplog.oplog中的操作包含严格递增的序号,这个序号来判定数据的时效性.
2.1.4、故障切换和活跃节点选举
如果活跃节点出现故障,其余节点会选一个新的活跃节点.选举过程可以由任何非活跃节点发起,新的活跃节点由副本集中的大多数选举产生.仲裁节点也参与选举,避免出现僵局.新的活跃节点将是优先级最高的节点,优先级相同这数据较新的节点获胜.
不论活跃节点何时变化,新的活跃节点的数据就被假定为系统的最新数据.对其他几点(原来的活跃节点)的操作都会回滚,即便是之前的活跃节点已经恢复工作了.为了完成回滚,所有节点连接新的活跃节点后重新同步。这些节点会查看自己的oplog,找出自重活跃节点没有的操作,然后向活跃节点请求这些操作影响的文档最新副本。正在执行重新同步的节点被视为恢复中,在完成这个过程之前不能成为活跃节点的候选者。
3、从服务器上的操作
从节点的主要作用是作为故障恢复机制,以防主节点数据丢失或者停止服务,除此之外,从节点可用做备份的数据源、扩展读取性能或进行数据处理。
3.1、读扩展
用MongoDB扩展读取的一种方式就是将查询放在从节点上。这样,主节点的负载就减轻了。一般说来,当负载是读取密集型时这是非常不错的方案。要是写密集型,则要考虑用自动分片来进行扩展。
使用从节点来扩展MongoDB的读取有个要点,就是数据复制并不同步,也就是说在主节点插入和更新数据后,有片刻从节点的数据并不是最新的,在考虑用查询从节点完成请求时这点非常重要。
扩展读取本身很简单,像往常一样设置主从复制,连接从服务器处理请求.唯一的技巧是有个特殊的查询选项,告诉从服务器是否可以处理请求(默认是不可以的)。这个选项是slaveOkay,所有的MongoDB驱动程序都提供了一种机制来设置它。有些驱动程序还提供工具使得请求分布到从节点的过程自动化,但这个过程随驱动程序的不同而不同.
3.2、用从节点做数据处理
从节点可以作为一种机制来减轻密集型处理的负载,或作为聚合,避免影响主节点的性能。用--master参数启动一个普通的从节点.同时使用--slave和--master有点矛盾,这意味着如果能对从节点进行写入,像往常一样查询,就把它作为一个普通的MongoDB主节点就行了,向从节点插入数据不会同步到主节点中。从节点还是会不断的从真正的主节点复制数据。这样,就可以对从节点执行阻塞操作也不影响主节点的性能。用这种技术的时候,一定要确保不能对正在复制主节点数据的从节点上的数据库进行写入。从节点不能恢复这些操作,就不能映射主节点.
诸如:
启动主节点:
[[email protected] ~]# mongod --dbpath /opt/mongo/master/ --port 10000 --master
启动从节点:
[[email protected] ~]# mongod --dbpath /opt/mongo/slave1/ --port 10001 --master --slave --source localhost:10000
在主节点插入数据:
> db.rgf.insert({"name":"xuan","age":10,"sex":"male"})
WriteResult({ "nInserted" : 1 })
> db.rgf.find()
{ "_id" : ObjectId("54daca943149616d6b6d9714"), "name" : "rgf", "age" : 10, "sex" : "male" }
{ "_id" : ObjectId("54db1db98b5be4a508f8006c"), "name" : "xuan", "age" : 10, "sex" : "male" }
在从节点查看数据同步状况:
> use test;
switched to db test
> db.rgf.find()
{ "_id" : ObjectId("54daca943149616d6b6d9714"), "name" : "rgf", "age" : 10, "sex" : "male" }
{ "_id" : ObjectId("54db1db98b5be4a508f8006c"), "name" : "xuan", "age" : 10, "sex" : "male" }
同步成功。现在在从节点插入数据:
> db.rgf.insert({"hello":"zhao","meinv":1,"ok":"yes"})
WriteResult({ "nInserted" : 1 })
> db.rgf.find()
{ "_id" : ObjectId("54daca943149616d6b6d9714"), "name" : "rgf", "age" : 10, "sex" : "male" }
{ "_id" : ObjectId("54db1db98b5be4a508f8006c"), "name" : "xuan", "age" : 10, "sex" : "male" }
{ "_id" : ObjectId("54db1e898d04c632fbf8982d"), "hello" : "zhao", "meinv" : 1, "ok" : "yes" }
>
再在主节点查看数据:
> db.rgf.find()
{ "_id" : ObjectId("54daca943149616d6b6d9714"), "name" : "rgf", "age" : 10, "sex" : "male" }
{ "_id" : ObjectId("54db1db98b5be4a508f8006c"), "name" : "xuan", "age" : 10, "sex" : "male" }
>
说明主节点并没有同步从节点的数据。
4、复制工作原理
4.1、操作日志
复制至少需要两个服务器或节点。其中一个是主节点,负责处理客户端请求,其他都是从节点负责映射主节点的数据。主节点记录在其上执行的所有操作。从节点定期轮训主节点获得这些操作,然后对自己的数据副本执行这些操作。由于和主节点执行了相同的操作,从节点就能保持与主节点的数据同步。