如果你还不了解Replica Set的相关理论,请猛戳传送门阅读笔者的上一篇博文。

因为Replica Set已经属于MongoDb的进阶应用,下文中关于MongoDb的基础知识笔者就不再赘述了,请参考MongoDb Manual

下面分各种场景讲述如何创建一个Replica Set。

Standalone到Replica Set

这是相对简单的一种情况。如果你刚刚在生产环境应用MongoDb,很有可能适用于这种场景。

一台独立的MongoDb实例变为Replica Set的首位成员很容易,需要两个步骤:

1. 运行参数中加入--replicaSet <set name>。如:

mongod --dbpath /var/lib/mongo/ --replicaSet rs0 --fork

如果开启了认证模式则需要额外的一步:为实例配置一个Key,作为今后不同实例间认证的根据。

Key的原理很简单,只要不同实例拥有相同的Key,则认证成功。没有公私钥交换等等麻烦的过程。

生成Key也很简单,任何一个文件存了字符串都可以成为Key。只要保证不同实例使用相同的Key文件即可。

添加Key请使用参数 --keyFile=<file path>。生成Key请参考官方文档

2. 进入MongoDb命令行进行初始化,大致情况如下:

$ mongo localhost:
MongoDB shell version: 2.4.
connecting to: localhost:/test
> rs.initiate()
{
"info2" : "no configuration explicitly specified -- making one",
"me" : "YX-ARCH:27011",
"info" : "Config now saved locally. Should come online in about a minute.",
"ok" :
}

稍等片刻,集群初始化完成,回车后提示符从">"变为

rs0:PRIMARY> 

表示该实例已经成为一个Primary结点。此时集群中只有一个结点,不存在投票问题,所以无论怎么折腾这个结点都会保持Primary状态。但到下一步就要小心了。

rs0:PRIMARY> rs.add("YX-ARCH:27012")
{ "ok" : }

此时第二个结点YX-ARCH:27012已加入集群,可以使用以下命令查看集群状态:

rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2014-01-24T07:21:01Z"),
"myState" : ,
"members" : [
{
"_id" : ,
"name" : "YX-ARCH:27011",
"health" : ,
"state" : ,
"stateStr" : "PRIMARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2014-01-24T07:20:12Z"),
"self" : true
},
{
"_id" : ,
"name" : "YX-ARCH:27012",
"health" : ,
"state" : ,
"stateStr" : "SECONDARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2014-01-24T07:20:12Z"),
"lastHeartbeat" : ISODate("2014-01-24T07:21:00Z"),
"lastHeartbeatRecv" : ISODate("2014-01-24T07:21:00Z"),
"pingMs" : ,
"syncingTo" : "YX-ARCH:27011"
}
],
"ok" :
}

可见27011此时是Primary身份,而27012则是Secondary身份。如果想改变结点的角色,则需要修改集群的配置。首先将集群配置调出:

rs0:PRIMARY> conf = rs.conf()
{
"_id" : "rs0",
"version" : ,
"members" : [
{
"_id" : ,
"host" : "YX-ARCH:27011"
},
{
"_id" : ,
"host" : "YX-ARCH:27012"
}
]
}

上篇讲过每个集群结点都有一个priority属性,默认为1。要改变一个实例的角色,我们只需要给新的实例设置一个高于当前Primary的priority就可以实现了。

注意因为只有Primary结点是可写的,所以重新配置群集的操作只能在Primary结点上进行。

rs0:PRIMARY> conf.members[].priority = 

rs0:PRIMARY> rs.reconfig(conf)
Fri Jan ::36.069 DBClientCursor::init call() failed
Fri Jan ::36.070 trying reconnect to localhost:
Fri Jan ::36.070 reconnect localhost: ok
reconnected to server after rs command (which is normal) rs0:SECONDARY>

可见短暂的罢工后命令行自动重新连接到当前实例,但从提示符可以看出,当前实例已经变为Secondary角色。我们来随便逛逛Secondary里面的内容(Test是笔者自己建立的库)

rs0:SECONDARY> show dbs
Test .203125GB
local .0771484375GB
rs0:SECONDARY> use Test
switched to db Test
rs0:SECONDARY> show collections
Fri Jan ::32.697 error: { "$err" : "not master and slaveOk=false", "code" : } at src/mongo/shell/query.js:

如果用过Master Slave集群的读者应该发现了,Slave结点是可读的,而Secondary结点默认却不可读。要解决这个问题,只需要简单的一步:

rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show collections
system.indexes
test

至此一个拥有2个实例的MongoDb Replica Set就建立完成了。那么我们来试试传说中的故障恢复特性。

假设Primary结点因故停止工作了(我们手动干掉它)

$ ps awx | grep mongo
? Sl : mongod --config mongodb1.conf
? Sl : mongod --config mongodb2.conf <--Primary在这
pts/ Sl+ : mongo localhost:
pts/ S+ : grep --color=auto mongo
$ kill

在美好的幻想中,Secondary应该马上即位成为新的Primary吧?

$ mongo localhost:
MongoDB shell version: 2.4.
connecting to: localhost:/test
rs0:SECONDARY>

神马?还是Secondary?说好的故障恢复呢?

或许是姿势不对?那我们重新启动两个实例,这回先当掉Secondary试试

$ ps awx | grep mongo
? Sl : mongod --config mongodb1.conf <-- Secondary在这
pts/ Sl+ : mongo localhost:
? Sl : mongod --config mongodb2.conf
pts/ S+ : grep --color=auto mongo
$ kill 12546
$ mongo localhost:27012
MongoDB shell version: 2.4.9
connecting to: YX-ARCH:27012/test
rs0:SECONDARY>

神马?Primary降级为Secondary了?如果没看上篇的读者心里应该暗暗骂娘了吧?”禽兽,这算什么错误恢复集群?明明就是两个都死了!“

那么我们再把死掉那个Secondary复活试试?你会发现Primary又回来了。上篇说过,提升到Primary是一个很严格的过程,一旦出现2个Primary的情况,集群就废了。所以宁可错杀1000也不可放过一个。

所以实际发生的事情是这样的:当集群中仅有的2个实例挂掉一个,剩下的一个并不能判断自己的存活状况,因为也可能是自己跟网络断开了连接造成的。另外一个实例说不定还活得好好的呢。因此没有办法,暂时让自己成为Secondary活下去吧。

一旦另外一个实例恢复生存,两个实例就可以互相证明对方存活,因此还是priority较高的那个继续扮演Primary。

为了避免这种悲剧的发生,Arbiter的必要性就体现出来了。

自然你可以在群集中再加一个Secondary,让三个实例互相证明存活性,这样不仅刚才的问题可以解决 ,自动切换也是可以达成的。但是要成为Secondary的服务器可是对性能有一定要求的,现实情况下这样做可能性价比并不高。那么还是添加一个Arbiter吧,它的存在只是为了投票选举,不占用额外资源,放在资源有限的虚拟机中就足够了。如果有多个Arbiter为不同的集群服务,也可以放进同一台虚拟机中以节省资源。

rs0:PRIMARY> rs.addArb("YX-ARCH:27013")
{ "ok" : }

顺带一提,刚才我们的操作中一直使用的是我的机器名"YX-ARCH",而不是localhost。这里localhost是被禁止使用的,原因是这样:

如果在命令行查看集群信息

rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2014-01-24T08:08:32Z"),
"myState" : ,
"members" : [
{
"_id" : ,
"name" : "YX-ARCH:27011",
"health" : ,
"state" : ,
"stateStr" : "SECONDARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2014-01-24T08:07:22Z"),
"lastHeartbeat" : ISODate("2014-01-24T08:08:30Z"),
"lastHeartbeatRecv" : ISODate("2014-01-24T08:08:32Z"),
"pingMs" : ,
"syncingTo" : "YX-ARCH:27012"
},
{
"_id" : ,
"name" : "YX-ARCH:27012",
"health" : ,
"state" : ,
"stateStr" : "PRIMARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2014-01-24T08:07:22Z"),
"self" : true
},
{
"_id" : ,
"name" : "YX-ARCH:27013",
"health" : ,
"state" : ,
"stateStr" : "ARBITER",
"uptime" : ,
"lastHeartbeat" : ISODate("2014-01-24T08:08:31Z"),
"lastHeartbeatRecv" : ISODate("2014-01-24T08:08:32Z"),
"pingMs" :
}
],
"ok" :
}

我们可以看到集群中有三个实例,分别是

YX-ARCH:
YX-ARCH:
YX-ARCH:

当使用某种语言,比如C#来连接MongoDb的时候,哪些服务器可用的信息并不是从连接字符串中来的,而是Driver会得到一份类似以上的信息来判断哪些服务器可用。可见,如果添加到集群中的地址是localhost:27011,那么Driver也会认为localhost:27011是一台可用的实例。但多数情况下应用和数据库都是分开的,这会造成应用徒劳地从自己机器上的27011端口去找MongoDb服务,显然是不可能成功的。关于获取可用实例的问题,笔者之前写过一篇日志,请戳传送门

以上已经建立了一个完整的集群,望大家用得开心。但作为摸爬滚打了多年的IT人,我是万万不敢在没有考虑后路的情况下就往生产环境上使用一个自己不知根知底的系统的。进兵前先考虑退路是常识,所以还是要考虑一下万一的情况下,如何从Replica Set退回Standalone的场景。

Replica Set 到 Standalone

考虑上述3个实例的Replica Set,它虽然可以在任何一个实例当掉的情况下保持正常工作,那如果2个实例同时当掉呢?什么,不可能有这么衰?前人的经验告诉我们:在IT的世界里,只要有可能发生的事情就一定会发生。

所以当灾难来临的时候如果你两手空空,怎么能不被打个鼻青脸肿?那么,操家伙开干。

无论Primary还是Secondary,只要去掉--replicaSet即可马上变回Standalone的状态。如果要彻底回到Standalone状态,还应该删除数据库目录下的local.*文件(注意删除操作必须在MongoDb停止服务的情况下进行)。

如果不删这些文件,MongoDb也会正常工作,但TTL Collection会工作不正常。此外笔者还没发现有什么问题。

现实是,在多数情况下我们是不会希望从Replica Set变回Standalone的。如果你真的这么做了,那大概只有一个原因:踩了上文中的某个雷,导致集群变成完全只读的了。你期望暂时变回Standalone状态,以便不影响生产环境的日常操作,等其他的实例恢复之后再加上--replicaSet参数变回Replica Set状态。很不幸,如果你是这个目的,那么你马上会踩第二个雷。

我们知道Replica Set的原理是传送oplog并重做,而oplog只有在master/slave或replica set模式下才会生成。所以当你回到standalone模式下时所做的任何事情都是没有oplog的,这意味着当你再次回到Replica Set模式中时会丢失部分数据。

更气人的是:MongoDb不会阻止你做这样的事情,它会让你成功回到Replica Set状态。无论是Primary还是Secondary都可以。你甚至可以在Secondary变成Standalone期间修改它的内容后再让它回到ReplicaSet,这样它的内容就跟其他实例不一致!

知道这个坑之后来看看简单粗暴的解决办法:

1. 停止MongoDb实例

$ sudo systemctl stop mongodb

2. 进入数据库文件夹删除local.*

$ cd /var/lib/mongodb/
$ rm local.* -rf

3. 把--replicaSet参数重新添加回配置文件

4. 重启启动MongoDb

sudo systemctl start mongodb

此时MongoDb变回普通的非Replica Set实例

5. 按上文的方法重新初始化Replica Set并建立集群。

虽然复杂了点,但这也是没有办法的事情,千万不可走捷径。

Master/Slave到Replica Set

Master/Slave到Replica Set其实和Standalone到Replica Set并无两样,所有步骤完全相同。要注意的只有一点,一旦Master变成Primary,Slave将不再从它同步数据。也就是说从执行

> rs.initiate()

的一刻开始,Slave就成为了当时数据库的快照,不再更新。所以在生产环境中应用这个操作时,要注意数据不同步可能带来的危害,必要时必须停机维护。遇到这类问题时:

方案一:可以把原来使用Slave的应用切换为使用Master,优点是可以全程不间断服务。缺点是要求你的Master性能足够好。

方案二:如果想使用方案一但Master的性能又不够好,可以使用一台足够强大的新机器先做为Slave,然后所有系统停机维护,这时把Slave变为Master,所有服务使用新的Master继续工作。之后再从这台Master开始向Replica Set的转换工作。优点是停机时间可以比较短。缺点嘛……服务中断了……另外所有系统指向新的Master时视系统规模可能修改比较多,容易有疏漏。

方案三:完全停机进行master/slave到replica set的转换。所有Secondary完成复制后再开始重新服务。这当然是最轻松的方案了,自然停机时间也是最长的。

后记

MongoDb作为新兴的非关系型数据库近年来发展迅速。新特性层出不穷。想了解它的方方面面,最简单的方法是读它的User manual。虽然是件费力的事情,但也是没有办法的事情,就像上面提到的一样,想走捷径的结果往往是掉进坑里。

希望笔者的血泪史为小伙伴们提供前车之鉴,本文如有不正确之处欢迎指正。

关于MongoDb Replica Set的故障转移集群——实战篇的更多相关文章

  1. 关于MongoDb Replica Set的故障转移集群——理论篇

    自从10 gen用Replica Set取代Master/Slave方案后生活其实已经容易多了,但是真正实施起来还是会发现各种各样的小问题,如果不小心一样会栽跟头. 在跟Replica Set血拼几天 ...

  2. 从0开始搭建SQL Server AlwaysOn 第二篇(配置故障转移集群)

    从0开始搭建SQL Server AlwaysOn 第二篇(配置故障转移集群) 第一篇http://www.cnblogs.com/lyhabc/p/4678330.html第二篇http://www ...

  3. (转)从0开始搭建SQL Server AlwaysOn 第二篇(配置故障转移集群)

    原文地址:  http://www.cnblogs.com/lyhabc/p/4682028.html 这一篇是从0开始搭建SQL Server AlwaysOn 的第二篇,主要讲述如何搭建故障转移集 ...

  4. 在Windows Server 2012 R2中搭建SQL Server 2012故障转移集群

    需要说明的是我们搭建的SQL Server故障转移集群(SQL Server Failover Cluster)是可用性集群,而不是负载均衡集群,其目的是为了保证服务的连续性和可用性,而不是为了提高服 ...

  5. 在Windows 2008/2008 R2 上配置IIS 7.0/7.5 故障转移集群

    本文主要是从:http://support.microsoft.com/kb/970759/zh-cn,直接转载,稍作修改裁剪而来,其中红色粗体部分,是我特别要说明的 若要配置 IIS 7.0 和 7 ...

  6. sqlserver2008 复制,镜像,日志传输及故障转移集群区别

    一, 数据库复制 SQL Server 2008数据库复制是通过发布/订阅的机制进行多台服务器之间的数据同步,我们把它用于数据库的同步备份.这里的同步备份指的是备份服务器与主服务器进行 实时数据同步, ...

  7. 部署AlwaysOn第一步:搭建Windows服务器故障转移集群

    在Windows Server 2012 R2 DataCenter 环境中搭建集群之前,首先要对Windows服务器故障转移集群(Windows Server Failover Cluster,简称 ...

  8. SQL AlawaysOn 之四:故障转移集群

    声明,故障转移集群,仅安装在SQL服务器中,域服务器不能和SQL服务器一起加入集群. 1.添加故障转移集群,下一步 2.安装 3.在域控制服务器上的管理工具里打开故不障转移集群管理器,选择创建集群 4 ...

  9. Windows Server2012 故障转移集群之动态仲裁(Dynamic Quorum)

    本篇文章主要介绍Windows2012的故障转移集群一个新功能“动态仲裁”,默认该功能是开启的: 动态仲裁能在当前群集投票出现分歧的情况下取消某些节点的投票权限,比如偶数个节点的群集环境.仲裁见证和动 ...

随机推荐

  1. 反人类的MyEclipse之-MyEclipse代码自动补全

    如果你用过Visual Studio的自动补全功能后,再来用eclipse的自动补全功能,相信大家会有些许失望. 但是eclipse其实是非常强大的,eclipse的自动补全没有VS那么好是因为ecl ...

  2. sikuli

    1.sikuli和selenium集成问题,用java封装一个方法去操作web页面上的一些无法定位的控件  http://bbs.csdn.net/topics/390720479/ 2.关于Siku ...

  3. 查看django里所有的url

    >>> from django.core.urlresolvers import get_resolver >>> get_resolver(None).rever ...

  4. [kuangbin带你飞]专题十四 数论基础

            ID Origin Title   111 / 423 Problem A LightOJ 1370 Bi-shoe and Phi-shoe   21 / 74 Problem B ...

  5. Mingyang.net:No identifier specified for entity

    org.hibernate.AnnotationException: No identifier specified for entity: net.mingyang.modules.system.C ...

  6. (DP)MaxSubArr

    public static int MSA(int[] ar) { int[] arr = new int[ar.length]; int msa = 0; arr[0] = ar[0]; for ( ...

  7. 【原】基于64位Centos6.2的mcrouter使用简介

    此文转载必须注明原文地址,请尊重作者的劳动成果!  http://www.cnblogs.com/lyongerr/p/5040071.html 目录 文档控制... 2 1 mcrouter简介.. ...

  8. HDFS副本机制&负载均衡&机架感知&访问方式&健壮性&删除恢复机制&HDFS缺点

    副本机制 1.副本摆放策略 第一副本:放置在上传文件的DataNode上:如果是集群外提交,则随机挑选一台磁盘不太慢.CPU不太忙的节点上:第二副本:放置在于第一个副本不同的机架的节点上:第三副本:与 ...

  9. 迁移到 Express 4.x

    原文地址: http://expressjs.com/migrating-4.html 概览 从 Express 3 到Express 4 是一个巨大的变化,这意味着现存的 Express 3 应用在 ...

  10. opencv基于HSV的肤色分割

    //函数功能:在HSV颜色空间对图像进行肤色模型分割 //输入:src-待处理的图像,imgout-输出图像 //返回值:返回一个iplimgae指针,指向处理后的结果 IplImage* SkinS ...