时间:2021-07-01 10:21:17 帮助过:1人阅读
在上一篇文章《搭建高可用mongodb集群(二)—— 副本集》 介绍了副本集的配置,这篇文章深入研究一下副本集的内部机制。还是带着副本集的问题来看吧!
Bully算法 mongodb副本集故障转移功能得益于它的选举机制。选举机制采用了Bully算法,可以很方便从分布式节点中选出主节点。一个分布式集群架构中一般都有一个所谓的主节点,可以有很多用途,比如缓存机器节点元数据,作为集群的访问入口等等。主节点有就有吧,我们干嘛要什么Bully算法?要明白这个我们先看看这两种架构:
指定主节点的架构,这种架构一般都会申明一个节点为主节点,其他节点都是从节点,如我们常用的mysql就是这样。但是这样架构我们在第一节说了整个集群如果主节点挂掉了就得手工操作,上架一个新的主节点或者从从节点恢复数据,不太灵活。
不指定主节点,集群中的任意节点都可以成为主节点。mongodb也就是采用这种架构,一但主节点挂了其他从节点自动接替变成主节点。如下图:
好了,问题就在这个地方,既然所有节点都是一样,一但主节点挂了,怎么选择出来下一个节点是谁来做为主节点呢?这就是Bully算法解决的问题。
那什么是Bully算法,Bully算法是一种协调者(主节点)竞选算法,主要思想是集群的每个成员都可以声明它是主节点并通知其他节点。别的节点可以选择接受这个声称或是拒绝并进入主节点竞争。被其他所有节点接受的节点才能成为主节点。节点按照一些属性来判断谁应该胜出。这个属性可以是一个静态ID,也可以是更新的度量像最近一次事务ID(最新的节点会胜出)。详情请参考NoSQL数据库分布式算法的协调者竞选还有维基百科的解释 。
选举 那mongodb是怎进行选举的呢?官方这么描述:
We use a consensus protocol to pick a primary. Exact details will be spared here but that basic process is:
- get maxLocalOpOrdinal from each server.
- if a majority of servers are not up (from this server’s POV), remain in Secondary mode and stop.
- if the last op time seems very old, stop and await human intervention.
- else, using a consensus protocol, pick the server with the highest maxLocalOpOrdinal as the Primary.
大致翻译过来为使用一致协议选择主节点。基本步骤为:
这里提到了一个一致协议(其实就是bully算法),这个和数据库的一致性协议还是有些区别,一致协议主要强调的是通过一些机制保证大家达成共识;而一致性协议强调的是操作的顺序一致性,比如同时读写一个数据会不会出现脏数据。一致协议在分布式里有一个经典的算法叫“Paxos算法”,后续再介绍。
上面有个问题,就是所有从节点的最后操作时间都是一样怎么办?就是谁先成为主节点的时间最快就选谁。
选举触发条件 选举不是什么时刻都会被触发的,有以下情况可以触发。
选举还有个前提条件,参与选举的节点数量必须大于副本集总节点数量的一半,如果已经小于一半了所有节点保持只读状态。
日志将会出现:
主节点挂掉能否人为干预?答案是肯定的。
如果杀不掉可以使用强制开关
db.adminCommand({replSetStepDown : 1, force : true})或者使用 rs.stepDown(120)也可以达到同样的效果,中间的数字指不能在停止服务这段时间成为主节点,单位为秒。
rs.conf(); {
我们来设置,让id为1的主机可以优先成为主节点。
cfg = rs.conf()然后再执行rs.conf()命令查看优先级已经设置成功,主节点选举也会触发。
{如果不想让一个从节点成为主节点可以怎么操作?
a、使用rs.freeze(120)冻结指定的秒数不能选举成为主节点。
b、按照上一篇设置节点为Non-Voting类型。
当主节点不能和大部分从节点通讯。把主机节点网线拔掉,嘿嘿:)
优先级还可以这么用,如果我们不想设置什么hidden节点,就用secondary类型作为备份节点也不想让他成为主节点怎么办?看下图,共三个节点分布在两个数据中心,数据中心2的节点设置优先级为0不能成为主节点,但是可以参与选举、数据复制。架构还是很灵活吧!
奇数 官方推荐副本集的成员数量为奇数,最多12个副本集节点,最多7个节点参与选举。最多12个副本集节点是因为没必要一份数据复制那么多份,备份太多反而增加了网络负载和拖慢了集群性能;而最多7个节点参与选举是因为内部选举机制节点数量太多就会导致1分钟内还选不出主节点,凡事只要适当就好。这个“12”、“7”数字还好,通过他们官方经过性能测试定义出来可以理解。具体还有哪些限制参考官方文档《 MongoDB Limits and Thresholds 》。 但是这里一直没搞懂整个集群为什么要奇数,通过测试集群的数量为偶数也是可以运行的,参考这个文章http://www.itpub.net/thread-1740982-1-1.html。后来突然看了一篇stackoverflow的文章终于顿悟了,mongodb本身设计的就是一个可以跨IDC的分布式数据库,所以我们应该把它放到大的环境来看。
假设四个节点被分成两个IDC,每个IDC各两台机器,如下图。但这样就出现了个问题,如果两个IDC网络断掉,这在广域网上很容易出现的问题,在上面选举中提到只要主节点和集群中大部分节点断开链接就会开始一轮新的选举操作,不过mongodb副本集两边都只有两个节点,但是选举要求参与的节点数量必须大于一半,这样所有集群节点都没办法参与选举,只会处于只读状态。但是如果是奇数节点就不会出现这个问题,假设3个节点,只要有2个节点活着就可以选举,5个中的3个,7个中的4个。。。
心跳 综上所述,整个集群需要保持一定的通信才能知道哪些节点活着哪些节点挂掉。mongodb节点会向副本集中的其他节点每两秒就会发送一次pings包,如果其他节点在10秒钟之内没有返回就标示为不能访问。每个节点内部都会维护一个状态映射表,表明当前每个节点是什么角色、日志时间戳等关键信息。如果是主节点,除了维护映射表外还需要检查自己能否和集群中内大部分节点通讯,如果不能则把自己降级为secondary只读节点。
同步,副本集同步分为初始化同步和keep复制。初始化同步指全量从主节点同步数据,如果主节点数据量比较大同步时间会比较长。而keep复制指初始化同步过后,节点之间的实时同步一般是增量同步。初始化同步不只是在第一次才会被处罚,有以下两种情况会触发:
那什么是oplog的大小?前面说过oplog保存了数据的操作记录,secondary复制oplog并把里面的操作在secondary执行一遍。但是oplog也是mongodb的一个集合,保存在local.oplog.rs里,但是这个oplog是一个capped collection也就是固定大小的集合,新数据加入超过集合的大小会覆盖。所以这里需要注意,跨IDC的复制要设置合适的oplogSize,避免在生产环境经常产生全量复制。oplogSize 可以通过–oplogSize设置大小,对于linux 和windows 64位,oplog size默认为剩余磁盘空间的5%。
同步也并非只能从主节点同步,假设集群中3个节点,节点1是主节点在IDC1,节点2、节点3在IDC2,初始化节点2、节点3会从节点1同步数据。后面节点2、节点3会使用就近原则从当前IDC的副本集中进行复制,只要有一个节点从IDC1的节点1复制数据。
设置同步还要注意以下几点:
到此,本章前面提到的问题全部解决了,不得不说mongodb的设计还真是强大!
后续继续解决上一节这几个问题:
还有这两个问题后续解决:
原创文章,转载请注明: 转载自LANCEYAN.COM
本文链接地址: 搭建高可用mongodb集群(三)—— 深入副本集内部机制
Posted in mongodb, 技术.
在上一篇文章《搭建高可用MongoDB集群(一)——配置MongoDB》 提到了几个问题还没有解决。
这篇文章看完这些问题就可以搞定了。NoSQL的产生就是为了解决大数据量、高扩展性、高性能、灵活数据模型、高可用性。但是光通过主从模式的架构远远达不到上面几点,由此MongoDB设计了副本集和分片的功能。这篇文章主要介绍副本集:
mongoDB官方已经不建议使用主从模式了,替代方案是采用副本集的模式,点击查看 ,如图:
那什么是副本集呢?打魔兽世界总说打副本,其实这两个概念差不多一个意思。游戏里的副本是指玩家集中在高峰时间去一个场景打怪,会出现玩家暴多怪物少的情况,游戏开发商为了保证玩家的体验度,就为每一批玩家单独开放一个同样的空间同样的数量的怪物,这一个复制的场景就是一个副本,不管有多少个玩家各自在各自的副本里玩不会互相影响。 mongoDB的副本也是这个,主从模式其实就是一个单副本的应用,没有很好的扩展性和容错性。而副本集具有多个副本保证了容错性,就算一个副本挂掉了还有很多副本存在,并且解决了上面第一个问题“主节点挂掉了,整个集群内会自动切换”。难怪mongoDB官方推荐使用这种模式。我们来看看mongoDB副本集的架构图:
由图可以看到客户端连接到整个副本集,不关心具体哪一台机器是否挂掉。主服务器负责整个副本集的读写,副本集定期同步数据备份,一但主节点挂掉,副本节点就会选举一个新的主服务器,这一切对于应用服务器不需要关心。我们看一下主服务器挂掉后的架构:
副本集中的副本节点在主节点挂掉后通过心跳机制检测到后,就会在集群内发起主节点的选举机制,自动选举一位新的主服务器。看起来很牛X的样子,我们赶紧操作部署一下!
官方推荐的副本集机器数量为至少3个,那我们也按照这个数量配置测试。
1、准备两台机器 192.168.1.136、192.168.1.137、192.168.1.138。 192.168.1.136 当作副本集主节点,192.168.1.137、192.168.1.138作为副本集副本节点。
2、分别在每台机器上建立mongodb副本集测试文件夹
1 2 3 4 5 6 7 8 |
#存放整个mongodb文件
mkdir -p /data/mongodbtest/replset
#存放mongodb数据文件
mkdir -p /data/mongodbtest/replset/data
#进入mongodb文件夹
cd /data/mongodbtest
|
3、下载mongodb的安装程序包
1 |
wget http: //fastdl .mongodb.org /linux/mongodb-linux-x86_64-2 .4.8.tgz
|
注意linux生产环境不能安装32位的mongodb,因为32位受限于操作系统最大2G的文件限制。
1 2 |
#解压下载的压缩包
tar xvzf mongodb-linux-x86_64-2.4.8.tgz
|
4、分别在每台机器上启动mongodb
1 |
/data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongod --dbpath /data/mongodbtest/replset/data --replSet repset
|
可以看到控制台上显示副本集还没有配置初始化信息。
1 2 |
Sun Dec 29 20:12:02.953 [rsStart] replSet can‘t get local.system.replset config from self or any seed (EMPTYCONFIG)
Sun Dec 29 20:12:02.953 [rsStart] replSet info you may need to run replSetInitiate -- rs.initiate() in the shell -- if that is not already done
|
5、初始化副本集
在三台机器上任意一台机器登陆mongodb
1 2 3 4 |
/data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongo
#使用admin数据库
use admin
|
#定义副本集配置变量,这里的 _id:”repset” 和上面命令参数“ –replSet repset” 要保持一样。
1 2 3 4 5 |
config = { _id: "repset" , members:[
... {_id:0,host: "192.168.1.136:27017" },
... {_id:1,host: "192.168.1.137:27017" },
... {_id:2,host: "192.168.1.138:27017" }]
... }
|
#输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{
"_id" : "repset",
"members" : [
{
"_id" : 0,
"host" : "192.168.1.136:27017"
},
{
"_id" : 1,
"host" : "192.168.1.137:27017"
},
{
"_id" : 2,
"host" : "192.168.1.138:27017"
}
]
}
|
1 2 |
#初始化副本集配置
rs.initiate(config);
|
#输出成功
1 2 3 4 |
{
"info" : "Config now saved locally. Should come online in about a minute.",
"ok" : 1
}
|
#查看日志,副本集启动成功后,138为主节点PRIMARY,136、137为副本节点SECONDARY。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
Sun Dec 29 20:26:13.842 [conn3] replSet replSetInitiate admin command received from client
Sun Dec 29 20:26:13.842 [conn3] replSet replSetInitiate config object parses ok, 3 members specified
Sun Dec 29 20:26:13.847 [conn3] replSet replSetInitiate all members seem up
Sun Dec 29 20:26:13.848 [conn3] ******
Sun Dec 29 20:26:13.848 [conn3] creating replication oplog of size: 990MB...
Sun Dec 29 20:26:13.849 [FileAllocator] allocating new datafile /data/mongodbtest/replset/data/local.1, filling with zeroes...
Sun Dec 29 20:26:13.862 [FileAllocator] done allocating datafile /data/mongodbtest/replset/data/local.1, size: 1024MB, took 0.012 secs
Sun Dec 29 20:26:13.863 [conn3] ******
Sun Dec 29 20:26:13.863 [conn3] replSet info saving a newer config version to local.system.replset
Sun Dec 29 20:26:13.864 [conn3] replSet saveConfigLocally done
Sun Dec 29 20:26:13.864 [conn3] replSet replSetInitiate config now saved locally. Should come online in about a minute.
Sun Dec 29 20:26:23.047 [rsStart] replSet I am 192.168.1.138:27017
Sun Dec 29 20:26:23.048 [rsStart] replSet STARTUP2
Sun Dec 29 20:26:23.049 [rsHealthPoll] replSet member 192.168.1.137:27017 is up
Sun Dec 29 20:26:23.049 [rsHealthPoll] replSet member 192.168.1.136:27017 is up
Sun Dec 29 20:26:24.051 [rsSync] replSet SECONDARY
Sun Dec 29 20:26:25.053 [rsHealthPoll] replset info 192.168.1.136:27017 thinks that we are down
Sun Dec 29 20:26:25.053 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state STARTUP2
Sun Dec 29 20:26:25.056 [rsMgr] not electing self, 192.168.1.136:27017 would veto with ‘I don‘t think 192.168.1.138:27017 is electable‘
Sun Dec 29 20:26:31.059 [rsHealthPoll] replset info 192.168.1.137:27017 thinks that we are down
Sun Dec 29 20:26:31.059 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state STARTUP2
Sun Dec 29 20:26:31.062 [rsMgr] not electing self, 192.168.1.137:27017 would veto with ‘I don‘t think 192.168.1.138:27017 is electable‘
Sun Dec 29 20:26:37.074 [rsMgr] replSet info electSelf 2
Sun Dec 29 20:26:38.062 [rsMgr] replSet PRIMARY
Sun Dec 29 20:26:39.071 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state RECOVERING
Sun Dec 29 20:26:39.075 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state RECOVERING
Sun Dec 29 20:26:42.201 [slaveTracking] build index local.slaves { _id: 1 }
Sun Dec 29 20:26:42.207 [slaveTracking] build index done. scanned 0 total records. 0.005 secs
Sun Dec 29 20:26:43.079 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state SECONDARY
Sun Dec 29 20:26:49.080 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state SECONDARY
|
1 2 |
#查看集群节点的状态
rs.status();
|
#输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
{
"set" : "repset",
"date" : ISODate("2013-12-29T12:54:25Z"),
"myState" : 1,
"members" : [
{
"_id" : 0,
"name" : "192.168.1.136:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 1682,
"optime" : Timestamp(1388319973, 1),
"optimeDate" : ISODate("2013-12-29T12:26:13Z"),
"lastHeartbeat" : ISODate("2013-12-29T12:54:25Z"),
"lastHeartbeatRecv" : ISODate("2013-12-29T12:54:24Z"),
"pingMs" : 1,
"syncingTo" : "192.168.1.138:27017"
},
{
"_id" : 1,
"name" : "192.168.1.137:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 1682,
"optime" : Timestamp(1388319973, 1),
"optimeDate" : ISODate("2013-12-29T12:26:13Z"),
"lastHeartbeat" : ISODate("2013-12-29T12:54:25Z"),
"lastHeartbeatRecv" : ISODate("2013-12-29T12:54:24Z"),
"pingMs" : 1,
"syncingTo" : "192.168.1.138:27017"
},
{
"_id" : 2,
"name" : "192.168.1.138:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2543,
"optime" : Timestamp(1388319973, 1),
"optimeDate" : ISODate("2013-12-29T12:26:13Z"),
"self" : true
}
],
"ok" : 1
}
|
整个副本集已经搭建成功了。
6、测试副本集数据复制功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#在主节点192.168.1.138 上连接到终端:
mongo 127.0.0.1
#建立test 数据库。
use test ;
往testdb表插入数据。
> db.testdb.insert({ "test1" : "testval1" })
#在副本节点 192.168.1.136、192.168.1.137 上连接到mongodb查看数据是否复制过来。
/data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongo 192.168.1.136:27017
#使用test 数据库。
repset:SECONDARY> use test ;
repset:SECONDARY> show tables;
|
#输出
1 |
Sun Dec 29 21:50:48.590 error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:128
|
1 2 3 4 5 |
#mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读。
repset:SECONDARY> db.getMongo().setSlaveOk();
#可以看到数据已经复制到了副本集。
repset:SECONDARY> db.testdb. find ();
|
1 2 |
#输出
{ "_id" : ObjectId("52c028460c7505626a93944f"), "test1" : "testval1" }
|
7、测试副本集故障转移功能
先停掉主节点mongodb 138,查看136、137的日志可以看到经过一系列的投票选择操作,137 当选主节点,136从137同步数据过来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Sun Dec 29 22:03:05.351 [rsBackgroundSync] replSet sync source problem: 10278 dbclient error communicating with server: 192.168.1.138:27017
Sun Dec 29 22:03:05.354 [rsBackgroundSync] replSet syncing to: 192.168.1.138:27017
Sun Dec 29 22:03:05.356 [rsBackgroundSync] repl: couldn‘t connect to server 192.168.1.138:27017
Sun Dec 29 22:03:05.356 [rsBackgroundSync] replSet not trying to sync from 192.168.1.138:27017, it is vetoed for 10 more seconds
Sun Dec 29 22:03:05.499 [rsHealthPoll] DBClientCursor::init call() failed
Sun Dec 29 22:03:05.499 [rsHealthPoll] replset info 192.168.1.138:27017 heartbeat failed, retrying
Sun Dec 29 22:03:05.501 [rsHealthPoll] replSet info 192.168.1.138:27017 is down (or slow to respond):
Sun Dec 29 22:03:05.501 [rsHealthPoll] replSet member 192.168.1.138:27017 is now in state DOWN
Sun Dec 29 22:03:05.511 [rsMgr] not electing self, 192.168.1.137:27017 would veto with ‘192.168.1.136:27017 is trying to elect itself but 192.168.1.138:27017 is already primary and more up-to-date‘
Sun Dec 29 22:03:07.330 [conn393] replSet info voting yea for 192.168.1.137:27017 (1)
Sun Dec 29 22:03:07.503 [rsHealthPoll] replset info 192.168.1.138:27017 heartbeat failed, retrying
Sun Dec 29 22:03:08.462 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state PRIMARY
Sun Dec 29 22:03:09.359 [rsBackgroundSync] replSet syncing to: 192.168.1.137:27017
Sun Dec 29 22:03:09.507 [rsHealthPoll] replset info 192.168.1.138:27017 heartbeat failed, retrying
|
查看整个集群的状态,可以看到138为状态不可达。
1 2 3 |
/data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongo 192.168.1.136:27017
repset:SECONDARY> rs.status();
|
#输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
{
"set" : "repset",
"date" : ISODate("2013-12-29T14:28:35Z"),
"myState" : 2,
"syncingTo" : "192.168.1.137:27017",
"members" : [
{
"_id" : 0,
"name" : "192.168.1.136:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 9072,
"optime" : Timestamp(1388324934, 1),
"optimeDate" : ISODate("2013-12-29T13:48:54Z"),
"self" : true
},
{
"_id" : 1,
"name" : "192.168.1.137:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 7329,
"optime" : Timestamp(1388324934, 1),
"optimeDate" : ISODate("2013-12-29T13:48:54Z"),
"lastHeartbeat" : ISODate("2013-12-29T14:28:34Z"),
"lastHeartbeatRecv" : ISODate("2013-12-29T14:28:34Z"),
"pingMs" : 1,
"syncingTo" : "192.168.1.138:27017"
},
{
"_id" : 2,
"name" : "192.168.1.138:27017",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : Timestamp(1388324934, 1),
"optimeDate" : ISODate("2013-12-29T13:48:54Z"),
"lastHeartbeat" : ISODate("2013-12-29T14:28:35Z"),
"lastHeartbeatRecv" : ISODate("2013-12-29T14:28:23Z"),
"pingMs" : 0,
"syncingTo" : "192.168.1.137:27017"
}
],
"ok" : 1
}
|
再启动原来的主节点 138,发现138 变为 SECONDARY,还是137 为主节点 PRIMARY。
1 2 3 4 5 6 7 8 |
Sun Dec 29 22:21:06.619 [rsStart] replSet I am 192.168.1.138:27017
Sun Dec 29 22:21:06.619 [rsStart] replSet STARTUP2
Sun Dec 29 22:21:06.627 [rsHealthPoll] replset info 192.168.1.136:27017 thinks that we are down
Sun Dec 29 22:21:06.627 [rsHealthPoll] replSet member 192.168.1.136:27017 is up
Sun Dec 29 22:21:06.627 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state SECONDARY
Sun Dec 29 22:21:07.628 [rsSync] replSet SECONDARY
Sun Dec 29 22:21:08.623 [rsHealthPoll] replSet member 192.168.1.137:27017 is up
Sun Dec 29 22:21:08.624 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state PRIMARY
|
8、java程序连接副本集测试。三个节点有一个节点挂掉也不会影响应用程序客户端对整个副本集的读写!