搭建高可用的MongoDB集群

http://www.csdn.net/article/2014-04-09/2819221-build-high-avialable-mongodb-cluster-part-1/1

在大数据的时代,传统的关系型数据库要能更高的服务必须要解决高并发读写、海量数据高效存储、高可扩展性和高可用性这些难题。不过就是因为这些问题Nosql诞生了。

NOSQL有这些优势:

  • 大数据量,可以通过廉价服务器存储大量的数据,轻松摆脱传统mysql单表存储量级限制。
  • 高扩展性,Nosql去掉了关系数据库的关系型特性,很容易横向扩展,摆脱了以往老是纵向扩展的诟病。
  • 高性能,Nosql通过简单的key-value方式获取数据,非常快速。还有NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL在这个层面上来说就要性能高很多。
  • 灵活的数据模型,NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。
  • 高可用,NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如mongodb通过mongos、mongo分片就可以快速配置出高可用配置。

在nosql数据库里,大部分的查询都是键值对(key、value)的方式。MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中最像关系数据库的。支持类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。所以这个非常方便,我们可以用sql操作MongoDB,从关系型数据库迁移过来,开发人员学习成本会大大减少。如果再对底层的sql API做一层封装,开发基本可以感觉不到mongodb和关系型数据库的区别。同样MongoDB也是号称自己能够快速搭建一个高可用可扩展的的分布式集群,网上有很多搭建的文章,在我们搭建的时候还需要查找修改很多东西,所以把自己实战的步骤记录下来以备忘。我们看看如何一步一步搭建这个东东。

一、mongodb单实例。这种配置只适合简易开发时使用,生产使用不行,因为单节点挂掉整个数据业务全挂,如下图。

搭建高可用的MongoDB集群

虽然不能生产使用,但这个模式可以快速搭建启动,并且能够用mongodb的命令操作数据库。下面列出在linux下安装单节点mongodb的步骤

1. 建立mongodb测试文件夹

  1. #存放整个mongodb文件
  2. mkdir -p /data/mongodbtest/single
  3. #存放mongodb数据文件
  4. mkdir -p /data/mongodbtest/single/data
  5. #进入mongodb文件夹
  6. cd  /data/mongodbtest/single

2. 下载mongodb的安装程序包

  1. wget <a href="http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.6.tgz">http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.6.tgz</a>
  2. #解压下载的压缩包
  3. tar xvzf mongodb-linux-x86_64-2.4.6.tgz
  4. #进入mongodb程序执行文件夹
  5. cd mongodb-linux-x86_64-2.4.6/bin/

3. 启动单实例mongodb

  1. mongod  --dbpath /data/mongodbtest/single/data

输出日志如下,成功!

[initandlisten] db version v2.4.6 

…….. 

[initandlisten] waiting for connections on port 27017 

[websvr] admin web console waiting for connections on port 28017

mongodb默认自带提供了web访问接口,通过 IP + 端口的形式可以访问。

http://192.168.0.1:28017/

搭建高可用的MongoDB集群

二、主从模式。使用mysql数据库时大家广泛用到,采用双机备份后主节点挂掉了后从节点可以接替主机继续服务。所以这种模式比单节点的高可用性要好很多。

搭建高可用的MongoDB集群

下面看一下怎么一步步搭建一个mongodb的主从复制节点:

1. 准备两台机器 192.168.0.1 和 192.168.0.2。 192.168.0.1 当作主节点, 192.168.0.2作为从节点。

2. 分别下载mongodb安装程序包。在192.168.0.1上建立文件夹 /data/mongodbtest/master,192.168.0.2建立文件夹/data/mongodbtest/slave。

3. 在192.168.0.1启动mongodb主节点程序。注意后面的这个 “ –master ”参数,标示主节点。

mongod –dbpath /data/mongodbtest/master –master

输出日志如下,成功!

[initandlisten] MongoDB starting : pid=18285 port=27017 dbpath=/data/mongodbtest/master master=1

#日志显示主节点参数

[initandlisten] options: { dbpath: “/data/mongodbtest/master”, master: true } 

…….. 

[initandlisten] waiting for connections on port 27017

4. 在192.168.0.2启动mongodb从节点程序。关键配置,指定主节点ip地址和端口 –source 192.168.0.1:27017 和 标示从节点 –source 参数。

mongod –dbpath /data/mongodbtest/slave –slave –source 192.168.0.1:27017

输出日志如下,成功!

[initandlisten] MongoDB starting : pid=17888 port=27017 dbpath=/data/mongodbtest/slave slave=1 

…….. 

#日志显示从节点参数 

[initandlisten] options: { dbpath: “/data/mongodbtest/slave”, slave: true, source: “192.168.0.1:27017″ } 

…….. 

[initandlisten] waiting for connections on port 27017 

#日志显示从节点 从主节点同步复制数据 

[replslave] repl: from host:192.168.0.1:27017

5. 测试主从复制。

在主节点上连接到终端:

  1. mongo 127.0.0.1
  2. #建立test 数据库。
  3. use test;
  4. 往testdb表插入数据。
  5. > db.testdb.insert({"test1":"testval1"})
  6. 查询testdb数据看看是否成功。
  7. > db.testdb.find();
  8. { "_id" : ObjectId("5284e5cb1f4eb215b2ecc463"), "test1" : "testval1" }

可以看到主机的同步日志

[initandlisten] connection accepted from 192.168.0.2:37285 #3 (2 connections now open) 

[slaveTracking] update local.slaves query: { _id: ObjectId(’5284e6268ed115d6238bdb39′), config: { host: “192.168.0.2:35271″, upgradeNeeded: true }, ns: “local.oplog.$main” } update: { $set: { syncedTo: Timestamp 1384441570000|1 } } nscanned:1 nupdated:1 fastmod:1
keyUpdates:0 locks(micros) w:132015 132ms

检查从主机的数据。

mongo 127.0.0.1

查看当前数据库。

  1. > show dbs;
  2. local   0.203125GB
  3. test    0.203125GB
  4. use test;
  5. db.testdb.find();
  6. { "_id" : ObjectId("5284e5cb1f4eb215b2ecc463"), "test1" : "testval1" }

查询后数据已经同步过来了。再看看日志,发现从主机确实从主机同步了数据。

  1. Thu Nov 14 23:05:13 [replslave] repl:   checkpoint applied 15 operations
  2. Thu Nov 14 23:05:13 [replslave] repl:   syncedTo: Nov 14 23:08:10 5284e75a:1

查看服务状态

  1. > db.printReplicationInfo();
  2. this is a slave, printing slave replication info.
  3. source:   192.168.0.1:27017
  4. syncedTo: Sun Nov 17 2013 16:04:02 GMT+0800 (CST)
  5. = -54 secs ago (-0.01hrs)

到此主从结构的mongodb搭建好了。 



故障转移测试,现在两台服务器如果主服务器挂掉了,从服务器可以正常运转吗?

a. 先测试下从服务器可以当成主服务器吗,也就是往从服务器里写能够同步主服务器吗?在192.168.0.2上连接mongodb。

  1. mongo 127.0.0.1:27017
  2. > db.testdb.insert({"test3":"testval3"});
  3. not master

可以看到 mongodb的从节点是不能提供写操作的,只能提供读操作。

b. 如果从服务器挂掉,主服务器还可以提供服务。如果主服务器挂掉了从服务器能否自动变为可写。 

测试一下!

先杀掉原来的mongodb主服务器。

  1. kill -3 `ps -ef|grep mongod|grep -v grep|awk '{print $2}'`

测试从服务器能否可写。在192.168.0.2上连接mongodb测试。

  1. > db.testdb.insert({"test3":"testval3"});
  2. not master

看起来从服务器没有自动接替主服务器的功能,只有手工处理了!

停止从服务器,在原数据文件启动并添加主服务器标示。

  1. mongod  --dbpath /data/mongodbtest/slave --master

等到启动成功(时间有点长)。在192.168.0.2 上 连接

  1. mongo 192.168.0.2:27017
  1. > db.testdb.find();
  2. { "_id" : ObjectId("5288629e9b0318be4b20bd4c"), "test1" : "testval1" }
  3. { "_id" : ObjectId("528862d69b0318be4b20bd4d"), "test2" : "testval2" }

成功!

多个从节点。现在只是一个数据库服务器又提供写又提供读,机器承载会出现瓶颈。大家还记得mysql里的读写分离吗?把20%的写放到主节点,80%的读放到从节点分摊了减少了服务器的负载。但是大部分应用都是读操作带来的压力,一个从节点压力负载不了,可以把一个从节点变成多个节点。那mongodb的一主多从可以支持吗?答案是肯定的。

搭建高可用的MongoDB集群

为了方便测试,在192.168.0.2上再建立一个文件夹 /data/mongodbtest/slave1 作为另一个slave服务器。启动slave2服务,

  1. mongod  --dbpath /data/mongodbtest/slave1 --slave  --port 27017 --source 192.168.0.1:27017。

成功启动后通过mongodb连接测试:

  1. > db.testdb.find();
  2. { "_id" : ObjectId("5288629e9b0318be4b20bd4c"), "test1" : "testval1" }
  3. { "_id" : ObjectId("528862d69b0318be4b20bd4d"), "test2" : "testval2" }

搭建了这套主从复制系统是不是就很稳健了,其实不然。。。看看这几个问题?

  • 主节点挂了能否自动切换连接?目前需要手工切换。
  • 主节点的写压力过大如何解决?
  • 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
  • 就算对从节点路由实施路由访问策略能否做到自动扩展?

NoSQL的产生就是为了解决大数据量、高扩展性、高性能、灵活数据模型、高可用性。但是光通过主从模式的架构远远达不到上面几点,由此MongoDB设计了副本集和分片的功能。这篇文章主要介绍副本集

mongoDB官方已经不建议使用主从模式了,替代方案是采用副本集的模式, 点击查看,如图: 

搭建高可用的MongoDB集群

那什么是副本集呢?打魔兽世界总说打副本,其实这两个概念差不多一个意思。游戏里的副本是指玩家集中在高峰时间去一个场景打怪,会出现玩家暴多怪物少的情况,游戏开发商为了保证玩家的体验度,就为每一批玩家单独开放一个同样的空间同样的数量的怪物,这一个复制的场景就是一个副本,不管有多少个玩家各自在各自的副本里玩不会互相影响。 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副本集测试文件夹

#存放整个mongodb文件
mkdir -p /data/mongodbtest/replset #存放mongodb数据文件
mkdir -p /data/mongodbtest/replset/data #进入mongodb文件夹
cd /data/mongodbtest

3、下载mongodb的安装程序包

wget <a href="http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz">http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz</a>

注意linux生产环境不能安装32位的mongodb,因为32位受限于操作系统最大2G的文件限制。

搭建高可用的MongoDB集群

#解压下载的压缩包
tar xvzf mongodb-linux-x86_64-2.4.8.tgz

4、分别在每台机器上启动mongodb

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod  --dbpath /data/mongodbtest/replset/data   --replSet repset

可以看到控制台上显示副本集还没有配置初始化信息。

[plain] view
plain
copy
  1. Sun Dec 29 20:12:02.953 [rsStart] replSet can't get local.system.replset config from self or any seed (EMPTYCONFIG)
  2. 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

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo

#使用admin数据库
use admin

#定义副本集配置变量,这里的 _id:”repset” 和上面命令参数“ –replSet repset” 要保持一样。

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"}]
... }

#输出

[plain] view
plain
copy
  1. {
  2. "_id" : "repset",
  3. "members" : [
  4. {
  5. "_id" : 0,
  6. "host" : "192.168.1.136:27017"
  7. },
  8. {
  9. "_id" : 1,
  10. "host" : "192.168.1.137:27017"
  11. },
  12. {
  13. "_id" : 2,
  14. "host" : "192.168.1.138:27017"
  15. }
  16. ]
  17. }
#初始化副本集配置
rs.initiate(config);

#输出成功

[plain] view
plain
copy
  1. {
  2. "info" : "Config now saved locally.  Should come online in about a minute.",
  3. "ok" : 1
  4. }

#查看日志,副本集启动成功后,138为主节点PRIMARY,136、137为副本节点SECONDARY

[plain] view
plain
copy
  1. Sun Dec 29 20:26:13.842 [conn3] replSet replSetInitiate admin command
  2. received from client Sun Dec 29 20:26:13.842 [conn3] replSet replSetInitiate
  3. config object parses ok, 3 members specified Sun Dec 29 20:26:13.847 [conn3]
  4. replSet replSetInitiate all members seem up Sun Dec 29 20:26:13.848 [conn3]
  5. ****** Sun Dec 29 20:26:13.848 [conn3] creating replication oplog of size:
  6. 990MB... Sun Dec 29 20:26:13.849 [FileAllocator] allocating new datafile
  7. /data/mongodbtest/replset/data/local.1, filling with zeroes... Sun Dec
  8. 29 20:26:13.862 [FileAllocator] done allocating datafile /data/mongodbtest/replset/data/local.1,
  9. size: 1024MB, took 0.012 secs Sun Dec 29 20:26:13.863 [conn3] ****** Sun
  10. Dec 29 20:26:13.863 [conn3] replSet info saving a newer config version
  11. to local.system.replset Sun Dec 29 20:26:13.864 [conn3] replSet saveConfigLocally
  12. done Sun Dec 29 20:26:13.864 [conn3] replSet replSetInitiate config now
  13. saved locally. Should come online in about a minute. Sun Dec 29 20:26:23.047
  14. [rsStart] replSet I am 192.168.1.138:27017 Sun Dec 29 20:26:23.048 [rsStart]
  15. replSet STARTUP2 Sun Dec 29 20:26:23.049 [rsHealthPoll] replSet member
  16. 192.168.1.137:27017 is up Sun Dec 29 20:26:23.049 [rsHealthPoll] replSet
  17. member 192.168.1.136:27017 is up Sun Dec 29 20:26:24.051 [rsSync] replSet
  18. SECONDARY Sun Dec 29 20:26:25.053 [rsHealthPoll] replset info 192.168.1.136:27017
  19. thinks that we are down Sun Dec 29 20:26:25.053 [rsHealthPoll] replSet
  20. member 192.168.1.136:27017 is now in state STARTUP2 Sun Dec 29 20:26:25.056
  21. [rsMgr] not electing self, 192.168.1.136:27017 would veto with 'I don't
  22. think 192.168.1.138:27017 is electable' Sun Dec 29 20:26:31.059 [rsHealthPoll]
  23. replset info 192.168.1.137:27017 thinks that we are down Sun Dec 29 20:26:31.059
  24. [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state STARTUP2
  25. Sun Dec 29 20:26:31.062 [rsMgr] not electing self, 192.168.1.137:27017
  26. would veto with 'I don't think 192.168.1.138:27017 is electable' Sun Dec
  27. 29 20:26:37.074 [rsMgr] replSet info electSelf 2 Sun Dec 29 20:26:38.062
  28. [rsMgr] replSet PRIMARY Sun Dec 29 20:26:39.071 [rsHealthPoll] replSet
  29. member 192.168.1.137:27017 is now in state RECOVERING Sun Dec 29 20:26:39.075
  30. [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state RECOVERING
  31. Sun Dec 29 20:26:42.201 [slaveTracking] build index local.slaves { _id:
  32. 1 } Sun Dec 29 20:26:42.207 [slaveTracking] build index done. scanned 0
  33. total records. 0.005 secs Sun Dec 29 20:26:43.079 [rsHealthPoll] replSet
  34. member 192.168.1.136:27017 is now in state SECONDARY Sun Dec 29 20:26:49.080
  35. [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state SECONDARY
#查看集群节点的状态 rs.status();

#输出

[plain] view
plain
copy
  1. {
  2. "set" : "repset",
  3. "date" : ISODate("2013-12-29T12:54:25Z"),
  4. "myState" : 1,
  5. "members" : [
  6. {
  7. "_id" : 0,
  8. "name" : "192.168.1.136:27017",
  9. "health" : 1,
  10. "state" : 2,
  11. "stateStr" : "SECONDARY",
  12. "uptime" : 1682,
  13. "optime" : Timestamp(1388319973, 1),
  14. "optimeDate" : ISODate("2013-12-29T12:26:13Z"),
  15. "lastHeartbeat" : ISODate("2013-12-29T12:54:25Z"),
  16. "lastHeartbeatRecv" : ISODate("2013-12-29T12:54:24Z"),
  17. "pingMs" : 1,
  18. "syncingTo" : "192.168.1.138:27017"
  19. },
  20. {
  21. "_id" : 1,
  22. "name" : "192.168.1.137:27017",
  23. "health" : 1,
  24. "state" : 2,
  25. "stateStr" : "SECONDARY",
  26. "uptime" : 1682,
  27. "optime" : Timestamp(1388319973, 1),
  28. "optimeDate" : ISODate("2013-12-29T12:26:13Z"),
  29. "lastHeartbeat" : ISODate("2013-12-29T12:54:25Z"),
  30. "lastHeartbeatRecv" : ISODate("2013-12-29T12:54:24Z"),
  31. "pingMs" : 1,
  32. "syncingTo" : "192.168.1.138:27017"
  33. },
  34. {
  35. "_id" : 2,
  36. "name" : "192.168.1.138:27017",
  37. "health" : 1,
  38. "state" : 1,
  39. "stateStr" : "PRIMARY",
  40. "uptime" : 2543,
  41. "optime" : Timestamp(1388319973, 1),
  42. "optimeDate" : ISODate("2013-12-29T12:26:13Z"),
  43. "self" : true
  44. }
  45. ],
  46. "ok" : 1
  47. }

整个副本集已经搭建成功了。

6、测试副本集数据复制功能

#在主节点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;

#输出

[plain] view
plain
copy
  1. Sun Dec 29 21:50:48.590 error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:128
#mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读。
repset:SECONDARY> db.getMongo().setSlaveOk(); #可以看到数据已经复制到了副本集。
repset:SECONDARY> db.testdb.find();
[plain] view
plain
copy
  1. #输出
  2. { "_id" : ObjectId("52c028460c7505626a93944f"), "test1" : "testval1" }

7、测试副本集故障转移功能

先停掉主节点mongodb 138,查看136、137的日志可以看到经过一系列的投票选择操作,137 当选主节点,136从137同步数据过来。

[plain] view
plain
copy
  1. Sun Dec 29 22:03:05.351 [rsBackgroundSync] replSet sync source problem:
  2. 10278 dbclient error communicating with server: 192.168.1.138:27017 Sun
  3. Dec 29 22:03:05.354 [rsBackgroundSync] replSet syncing to: 192.168.1.138:27017
  4. Sun Dec 29 22:03:05.356 [rsBackgroundSync] repl: couldn't connect to server
  5. 192.168.1.138:27017 Sun Dec 29 22:03:05.356 [rsBackgroundSync] replSet
  6. not trying to sync from 192.168.1.138:27017, it is vetoed for 10 more seconds
  7. Sun Dec 29 22:03:05.499 [rsHealthPoll] DBClientCursor::init call() failed
  8. Sun Dec 29 22:03:05.499 [rsHealthPoll] replset info 192.168.1.138:27017
  9. heartbeat failed, retrying Sun Dec 29 22:03:05.501 [rsHealthPoll] replSet
  10. info 192.168.1.138:27017 is down (or slow to respond): Sun Dec 29 22:03:05.501
  11. [rsHealthPoll] replSet member 192.168.1.138:27017 is now in state DOWN
  12. Sun Dec 29 22:03:05.511 [rsMgr] not electing self, 192.168.1.137:27017
  13. would veto with '192.168.1.136:27017 is trying to elect itself but 192.168.1.138:27017
  14. is already primary and more up-to-date' Sun Dec 29 22:03:07.330 [conn393]
  15. replSet info voting yea for 192.168.1.137:27017 (1) Sun Dec 29 22:03:07.503
  16. [rsHealthPoll] replset info 192.168.1.138:27017 heartbeat failed, retrying
  17. Sun Dec 29 22:03:08.462 [rsHealthPoll] replSet member 192.168.1.137:27017
  18. is now in state PRIMARY Sun Dec 29 22:03:09.359 [rsBackgroundSync] replSet
  19. syncing to: 192.168.1.137:27017 Sun Dec 29 22:03:09.507 [rsHealthPoll]
  20. replset info 192.168.1.138:27017 heartbeat failed, retrying

查看整个集群的状态,可以看到138为状态不可达。

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 192.168.1.136:27017

repset:SECONDARY> rs.status();

#输出

[plain] view
plain
copy
  1. {
  2. "set" : "repset",
  3. "date" : ISODate("2013-12-29T14:28:35Z"),
  4. "myState" : 2,
  5. "syncingTo" : "192.168.1.137:27017",
  6. "members" : [
  7. {
  8. "_id" : 0,
  9. "name" : "192.168.1.136:27017",
  10. "health" : 1,
  11. "state" : 2,
  12. "stateStr" : "SECONDARY",
  13. "uptime" : 9072,
  14. "optime" : Timestamp(1388324934, 1),
  15. "optimeDate" : ISODate("2013-12-29T13:48:54Z"),
  16. "self" : true
  17. },
  18. {
  19. "_id" : 1,
  20. "name" : "192.168.1.137:27017",
  21. "health" : 1,
  22. "state" : 1,
  23. "stateStr" : "PRIMARY",
  24. "uptime" : 7329,
  25. "optime" : Timestamp(1388324934, 1),
  26. "optimeDate" : ISODate("2013-12-29T13:48:54Z"),
  27. "lastHeartbeat" : ISODate("2013-12-29T14:28:34Z"),
  28. "lastHeartbeatRecv" : ISODate("2013-12-29T14:28:34Z"),
  29. "pingMs" : 1,
  30. "syncingTo" : "192.168.1.138:27017"
  31. },
  32. {
  33. "_id" : 2,
  34. "name" : "192.168.1.138:27017",
  35. "health" : 0,
  36. "state" : 8,
  37. "stateStr" : "(not reachable/healthy)",
  38. "uptime" : 0,
  39. "optime" : Timestamp(1388324934, 1),
  40. "optimeDate" : ISODate("2013-12-29T13:48:54Z"),
  41. "lastHeartbeat" : ISODate("2013-12-29T14:28:35Z"),
  42. "lastHeartbeatRecv" : ISODate("2013-12-29T14:28:23Z"),
  43. "pingMs" : 0,
  44. "syncingTo" : "192.168.1.137:27017"
  45. }
  46. ],
  47. "ok" : 1
  48. }

再启动原来的主节点 138,发现138 变为 SECONDARY,还是137 为主节点 PRIMARY。

[plain] view
plain
copy
  1. Sun Dec 29 22:21:06.619 [rsStart] replSet I am 192.168.1.138:27017
  2. Sun Dec 29 22:21:06.619 [rsStart] replSet STARTUP2
  3. Sun Dec 29 22:21:06.627 [rsHealthPoll] replset info 192.168.1.136:27017 thinks that we are down
  4. Sun Dec 29 22:21:06.627 [rsHealthPoll] replSet member 192.168.1.136:27017 is up
  5. Sun Dec 29 22:21:06.627 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state SECONDARY
  6. Sun Dec 29 22:21:07.628 [rsSync] replSet SECONDARY
  7. Sun Dec 29 22:21:08.623 [rsHealthPoll] replSet member 192.168.1.137:27017 is up
  8. Sun Dec 29 22:21:08.624 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state PRIMARY

8、java程序连接副本集测试。三个节点有一个节点挂掉也不会影响应用程序客户端对整个副本集的读写!

[java] view
plain
copy
  1. public class TestMongoDBReplSet { public static void main(String[] args)
  2. { try { List<ServerAddress> addresses = new ArrayList<ServerAddress>();
  3. ServerAddress address1 = new ServerAddress("192.168.1.136" , 27017); ServerAddress
  4. address2 = new ServerAddress("192.168.1.137" , 27017); ServerAddress address3
  5. = new ServerAddress("192.168.1.138" , 27017); addresses.add(address1);
  6. addresses.add(address2); addresses.add(address3); MongoClient client =
  7. new MongoClient(addresses); DB db = client.getDB( "test"); DBCollection
  8. coll = db.getCollection( "testdb"); // 插入 BasicDBObject object = new BasicDBObject();
  9. object.append( "test2", "testval2" ); coll.insert(object); DBCursor dbCursor
  10. = coll.find(); while (dbCursor.hasNext()) { DBObject dbObject = dbCursor.next();
  11. System. out.println(dbObject.toString()); } } catch (Exception e) { e.printStackTrace();
  12. } } }

目前看起来支持完美的故障转移了,这个架构是不是比较完美了?其实还有很多地方可以优化,比如开头的第二个问题:主节点的读写压力过大如何解决?常见的解决方案是读写分离,mongodb副本集的读写分离如何做呢?

看图说话:

搭建高可用的MongoDB集群

常规写操作来说并没有读操作多,所以一台主节点负责写,两台副本节点负责读。

1、设置读写分离需要先在副本节点SECONDARY 设置 setSlaveOk。 

2、在程序中设置副本节点负责读操作,如下代码:

[java] view
plain
copy
  1. public class TestMongoDBReplSetReadSplit {
  2. public static void main(String[] args) {
  3. try {
  4. List<ServerAddress> addresses = new ArrayList<ServerAddress>();
  5. ServerAddress address1 = new ServerAddress("192.168.1.136" , 27017);
  6. ServerAddress address2 = new ServerAddress("192.168.1.137" , 27017);
  7. ServerAddress address3 = new ServerAddress("192.168.1.138" , 27017);
  8. addresses.add(address1);
  9. addresses.add(address2);
  10. addresses.add(address3);
  11. MongoClient client = new MongoClient(addresses);
  12. DB db = client.getDB( "test" );
  13. DBCollection coll = db.getCollection( "testdb" );
  14. BasicDBObject object = new BasicDBObject();
  15. object.append( "test2" , "testval2" );
  16. //读操作从副本节点读取
  17. ReadPreference preference = ReadPreference. secondary();
  18. DBObject dbObject = coll.findOne(object, null , preference);
  19. System. out .println(dbObject);
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }

读参数除了secondary一共还有五个参数:primary、primaryPreferred、secondary、secondaryPreferred、nearest。

搭建高可用的MongoDB集群

primary:默认参数,只从主节点上进行读取操作; 

primaryPreferred:大部分从主节点上读取数据,只有主节点不可用时从secondary节点读取数据。 

secondary:只从secondary节点上进行读取操作,存在的问题是secondary节点的数据会比primary节点数据“旧”。 

secondaryPreferred:优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据; 

nearest:不管是主节点、secondary节点,从网络延迟最低的节点上读取数据。

好,读写分离做好我们可以数据分流,减轻压力解决了“主节点的读写压力过大如何解决?”这个问题。不过当我们的副本节点增多时,主节点的复制压力会加大有什么办法解决吗?mongodb早就有了相应的解决方案。

看图: 

搭建高可用的MongoDB集群

其中的仲裁节点不存储数据,只是负责故障转移的群体投票,这样就少了数据复制的压力。是不是想得很周到啊,一看mongodb的开发兄弟熟知大数据架构体系,其实不只是主节点、副本节点、仲裁节点,还有Secondary-Only、Hidden、Delayed、Non-Voting。

Secondary-Only:不能成为primary节点,只能作为secondary副本节点,防止一些性能不高的节点成为主节点。 

Hidden:这类节点是不能够被客户端制定IP引用,也不能被设置为主节点,但是可以投票,一般用于备份数据。 

Delayed:可以指定一个时间延迟从primary节点同步数据。主要用于备份数据,如果实时同步,误删除数据马上同步到从节点,恢复又恢复不了。 

Non-Voting:没有选举权的secondary节点,纯粹的备份数据节点。

到此整个mongodb副本集搞定了两个问题:

  • 主节点挂了能否自动切换连接?目前需要手工切换。
  • 主节点的读写压力过大如何解决?

还有这两个问题后续解决:

  • 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
  • 数据压力大到机器支撑不了的时候能否做到自动扩展?

做了副本集发现又一些问题:

  • 副本集故障转移,主节点是如何选举的?能否手动干涉下架某一台主节点。
  • 官方说副本集数量最好是奇数,为什么?
  • mongodb副本集是如何同步的?如果同步不及时会出现什么情况?会不会出现不一致性?
  • mongodb的故障转移会不会无故自动发生?什么条件会触发?频繁触发可能会带来系统负载加重
上一篇:keepalived工作原理和配置说明 腾讯云VPC内通过keepalived搭建高可用主备集群


下一篇:Redis总结(八)如何搭建高可用的Redis集群