Nosql mongodb集群搭建(2)

这篇文章看完这些问题就可以搞定了。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副本集测试文件夹

#存放整个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的文件限制。

#解压下载的压缩包  
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. SunDec2920:12:02.953[RSStart]replSetcan'tgetlocal.system.replsetconfigfromselforanyseed(EMPTYCONFIG)
  2. SunDec2920:12:02.953[RSStart]replSetinfoyoumayneedtorunreplSetInitiate--rs.initiate()intheshell--ifthatisnotalreadydone

5、初始化副本集

在三台机器上任意一台机器登陆mongodb

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

#使用admin数据库
use admin

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

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

#输出

copy
    {
  1. "_id":"repset",
  2. "members":[
  3. {
  4. "_id":0,
  5. "host":"192.168.1.136:27017"
  6. },0); background-color:inherit">"_id":1,0); background-color:inherit">"host":"192.168.1.137:27017"
  7. "_id":2,0); background-color:inherit">"host":"192.168.1.138:27017"
  8. }
  9. ]
  10. }
#初始化副本集配置
rs.initiate(config);

#输出成功

copy
    "info":"Confignowsavedlocally.Shouldcomeonlineinaboutaminute.",0); background-color:inherit">"ok":1
  1. }

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

copy
    SunDec2920:26:13.842[conn3]replSetreplSetInitiateadmincommand
  1. receivedfromclientSunDec2920:26:13.842[conn3]replSetreplSetInitiate
  2. configobjectparsesok,3membeRSSpecifiedSunDec2920:26:13.847[conn3]
  3. replSetreplSetInitiateallmembeRSSeemupSunDec2920:26:13.848[conn3]
  4. ******SunDec2920:26:13.848[conn3]creatingreplicationoplogofsize:
  5. 990MB...SunDec2920:26:13.849[FileAllocator]allocatingnewdatafile
  6. /data/mongodbtest/replset/data/local.1,fillingwithzeroes...SunDec
  7. 2920:26:13.862[FileAllocator]doneallocatingdatafile/data/mongodbtest/replset/data/local.1,0); background-color:inherit">size:1024MB,took0.012secsSunDec2920:26:13.863[conn3]******Sun
  8. Dec2920:26:13.863[conn3]replSetinfosavinganewerconfigversion
  9. tolocal.system.replsetSunDec2920:26:13.864[conn3]replSetsaveConfigLocally
  10. doneSunDec2920:26:13.864[conn3]replSetreplSetInitiateconfignow
  11. savedlocally.Shouldcomeonlineinaboutaminute.SunDec2920:26:23.047
  12. [RSStart]replSetIam192.168.1.138:27017SunDec2920:26:23.048[RSStart]
  13. replSetSTARTUP2SunDec2920:26:23.049[rsHealthPoll]replSetmember
  14. 192.168.1.137:27017isupSunDec2920:26:23.049[rsHealthPoll]replSet
  15. member192.168.1.136:27017isupSunDec2920:26:24.051[RSSync]replSet
  16. SECONDARYSunDec2920:26:25.053[rsHealthPoll]replsetinfo192.168.1.136:27017
  17. thinksthatwearedownSunDec2920:26:25.053[rsHealthPoll]replSet
  18. member192.168.1.136:27017isnowinstateSTARTUP2SunDec2920:26:25.056
  19. [rsMgr]notelectingself,192.168.1.136:27017wouldvetowith'Idon't
  20. think192.168.1.138:27017iselectable'SunDec2920:26:31.059[rsHealthPoll]
  21. replsetinfo192.168.1.137:27017thinksthatwearedownSunDec2920:26:31.059
  22. [rsHealthPoll]replSetmember192.168.1.137:27017isnowinstateSTARTUP2
  23. SunDec2920:26:31.062[rsMgr]notelectingself,192.168.1.137:27017
  24. wouldvetowith'Idon'tthink192.168.1.138:27017iselectable'SunDec
  25. 2920:26:37.074[rsMgr]replSetinfoelectSelf2SunDec2920:26:38.062
  26. [rsMgr]replSetPRIMARYSunDec2920:26:39.071[rsHealthPoll]replSet
  27. member192.168.1.137:27017isnowinstateRECOVERINGSunDec2920:26:39.075
  28. [rsHealthPoll]replSetmember192.168.1.136:27017isnowinstateRECOVERING
  29. SunDec2920:26:42.201[slaveTracking]buildindexlocal.slaves{_id:
  30. 1}SunDec2920:26:42.207[slaveTracking]buildindexdone.scanned0
  31. totalrecords.0.005secsSunDec2920:26:43.079[rsHealthPoll]replSet
  32. member192.168.1.136:27017isnowinstateSECONDARYSunDec2920:26:49.080
  33. [rsHealthPoll]replSetmember192.168.1.137:27017isnowinstateSECONDARY
#查看集群节点的状态 rs.status();

copy

    "set":"repset",0); background-color:inherit">"date":ISODate("2013-12-29T12:54:25Z"),0); background-color:inherit">"myState":1,0); background-color:inherit">"name":"192.168.1.136:27017",0); background-color:inherit">"health":1,0); background-color:inherit">"state":2,0); background-color:inherit">"stateStr":"SECONDARY",0); background-color:inherit">"uptime":1682,0); background-color:inherit">"optime":Timestamp(1388319973,1),0); background-color:inherit">"optimeDate":ISODate("2013-12-29T12:26:13Z"),0); background-color:inherit">"lastHeartbeat":ISODate("2013-12-29T12:54:25Z"),0); background-color:inherit">"lastHeartbeatRecv":ISODate("2013-12-29T12:54:24Z"),0); background-color:inherit">"pingMs":1,0); background-color:inherit">"syncingTo":"192.168.1.138:27017"
  1. "name":"192.168.1.137:27017",0); background-color:inherit">"name":"192.168.1.138:27017",0); background-color:inherit">"state":1,0); background-color:inherit">"stateStr":"PRIMARY",0); background-color:inherit">"uptime":2543,0); background-color:inherit">"self":true
  2. }
  3. ],0); background-color:inherit">"ok":1
  4. 整个副本集已经搭建成功了。

    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;
    

    copy

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

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

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

    copy
      SunDec2922:03:05.351[rsBackgroundSync]replSetsyncsourceproblem:
    1. 10278dbclienterrorcommunicatingwithserver:192.168.1.138:27017Sun
    2. Dec2922:03:05.354[rsBackgroundSync]replSetsyncingto:192.168.1.138:27017
    3. SunDec2922:03:05.356[rsBackgroundSync]repl:couldn'tconnecttoserver
    4. 192.168.1.138:27017SunDec2922:03:05.356[rsBackgroundSync]replSet
    5. nottryingtosyncfrom192.168.1.138:27017,itisvetoedfor10moreseconds
    6. SunDec2922:03:05.499[rsHealthPoll]DBClientCursor::initcall()Failed
    7. SunDec2922:03:05.499[rsHealthPoll]replsetinfo192.168.1.138:27017
    8. heartbeatFailed,retryingSunDec2922:03:05.501[rsHealthPoll]replSet
    9. info192.168.1.138:27017isdown(orslowtorespond):SunDec2922:03:05.501
    10. [rsHealthPoll]replSetmember192.168.1.138:27017isnowinstateDOWN
    11. SunDec2922:03:05.511[rsMgr]notelectingself,192.168.1.137:27017
    12. wouldvetowith'192.168.1.136:27017istryingtoelectitselfbut192.168.1.138:27017
    13. isalreadyprimaryandmoreup-to-date'SunDec2922:03:07.330[conn393]
    14. replSetinfovotingyeafor192.168.1.137:27017(1)SunDec2922:03:07.503
    15. [rsHealthPoll]replsetinfo192.168.1.138:27017heartbeatFailed,retrying
    16. SunDec2922:03:08.462[rsHealthPoll]replSetmember192.168.1.137:27017
    17. isnowinstatePRIMARYSunDec2922:03:09.359[rsBackgroundSync]replSet
    18. syncingto:192.168.1.137:27017SunDec2922:03:09.507[rsHealthPoll]
    19. replsetinfo192.168.1.138:27017heartbeatFailed,retrying

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

    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 192.168.1.136:27017
    
    repset:SECONDARY> rs.status();
    

    copy

      "date":ISODate("2013-12-29T14:28:35Z"),0); background-color:inherit">"myState":2,0); background-color:inherit">"syncingTo":"192.168.1.137:27017",0); background-color:inherit">"members":[
    1. {
    2. "uptime":9072,0); background-color:inherit">"optime":Timestamp(1388324934,0); background-color:inherit">"optimeDate":ISODate("2013-12-29T13:48:54Z"),0); background-color:inherit">"self":true
    3. "uptime":7329,0); background-color:inherit">"lastHeartbeat":ISODate("2013-12-29T14:28:34Z"),0); background-color:inherit">"lastHeartbeatRecv":ISODate("2013-12-29T14:28:34Z"),0); background-color:inherit">"health":0,0); background-color:inherit">"state":8,0); background-color:inherit">"stateStr":"(notreachable/healthy)",0); background-color:inherit">"uptime":0,0); background-color:inherit">"lastHeartbeat":ISODate("2013-12-29T14:28:35Z"),0); background-color:inherit">"lastHeartbeatRecv":ISODate("2013-12-29T14:28:23Z"),0); background-color:inherit">"pingMs":0,0); background-color:inherit">"syncingTo":"192.168.1.137:27017"
    4. 再启动原来的主节点 138,发现138 变为 SECONDARY,还是137 为主节点 PRIMARY。

      copy
        SunDec2922:21:06.619[RSStart]replSetIam192.168.1.138:27017
      1. SunDec2922:21:06.619[RSStart]replSetSTARTUP2
      2. SunDec2922:21:06.627[rsHealthPoll]replsetinfo192.168.1.136:27017thinksthatwearedown
      3. SunDec2922:21:06.627[rsHealthPoll]replSetmember192.168.1.136:27017isup
      4. SunDec2922:21:06.627[rsHealthPoll]replSetmember192.168.1.136:27017isnowinstateSECONDARY
      5. SunDec2922:21:07.628[RSSync]replSetSECONDARY
      6. SunDec2922:21:08.623[rsHealthPoll]replSetmember192.168.1.137:27017isup
      7. SunDec2922:21:08.624[rsHealthPoll]replSetmember192.168.1.137:27017isnowinstatePRIMARY

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

      [java] copy
        publicclassTestMongoDBReplSet{staticvoidmain(String[]args)
      1. {try{List<ServerAddress>addresses=newArrayList<ServerAddress>();
      2. ServerAddressaddress1=newServerAddress("192.168.1.136",27017);ServerAddress
      3. address2="192.168.1.137",0); background-color:inherit">27017);ServerAddressaddress3
      4. ="192.168.1.138",0); background-color:inherit">27017);addresses.add(address1);
      5. addresses.add(address2);addresses.add(address3);MongoClientclient=
      6. newMongoClient(addresses);DBdb=client.getDB("test");DBCollection
      7. coll=db.getCollection("testdb");//插入BasicDBObjectobject=newBasicDBObject();
      8. object.append("test2","testval2");coll.insert(object);DBCursordbCursor
      9. =coll.find();while(dbCursor.hasNext()){DBObjectdbObject=dbCursor.next();
      10. System.out.println(dbObject.toString());}}catch(Exceptione){e.printStackTrace();
      11. }}}

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

      看图说话:

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

      1、设置读写分离需要先在副本节点SECONDARY 设置 setSlaveOk。
      2、在程序中设置副本节点负责读操作,如下代码:

      copy @H_301_571@
        classTestMongoDBReplSetReadSplit{
      1. voidmain(String[]args){
      2. try{
      3. List<ServerAddress>addresses=27017);
      4. ServerAddressaddress2=27017);
      5. ServerAddressaddress3=addresses.add(address1);
      6. addresses.add(address2);
      7. addresses.add(address3);
      8. MongoClientclient=newMongoClient(addresses);
      9. DBdb=client.getDB("test");
      10. DBCollectioncoll=db.getCollection("testdb");
      11. BasicDBObjectobject=newBasicDBObject();
      12. "testval2");
      13. //读操作从副本节点读取
      14. ReadPreferencepreference=ReadPreference.secondary();
      15. DBObjectdbObject=coll.findOne(object,null,preference);
      16. System.out.println(dbObject);
      17. }catch(Exceptione){
      18. e.printStackTrace();
      19. 读参数除了secondary一共还有五个参数:primary、primaryPreferred、secondary、secondaryPreferred、nearest。

        primary:默认参数,只从主节点上进行读取操作;
        primaryPreferred:大部分从主节点上读取数据,只有主节点不可用时从secondary节点读取数据。
        secondary:只从secondary节点上进行读取操作,存在的问题是secondary节点的数据会比primary节点数据“旧”。
        secondaryPreferred:优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据;
        nearest:不管是主节点、secondary节点,从网络延迟最低的节点上读取数据。

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

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

        Secondary-Only:不能成为primary节点,只能作为secondary副本节点,防止一些性能不高的节点成为主节点。
        Hidden:这类节点是不能够被客户端制定IP引用,也不能被设置为主节点,但是可以投票,一般用于备份数据。
        Delayed:可以指定一个时间延迟从primary节点同步数据。主要用于备份数据,如果实时同步,误删除数据马上同步到从节点,恢复又恢复不了。
        Non-Voting:没有选举权的secondary节点,纯粹的备份数据节点。

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

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

        还有这两个问题后续解决

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

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

        • 副本集故障转移,主节点是如何选举的?能否手动干涉下架某一台主节点。
        • 官方说副本集数量最好是奇数,为什么?
        • mongodb副本集是如何同步的?如果同步不及时会出现什么情况?会不会出现不一致性?
        • mongodb的故障转移会不会无故自动发生?什么条件会触发?频繁触发可能会带来系统负载加重

        相关文章

        一、引言 学习redis 也有一段时间了,该接触的也差不多了。后来有一天,以前的同事问我,如何向redis中...
        一、引言 上一篇文章,我介绍了如何在Linux系统上安装和配置MongoDB,其实都不是很难,不需要安装和编译...
        一、介绍 Redis客户端使用RESP(Redis的序列化协议)协议与Redis的服务器端进行通信。 虽然该协议是专门...
        一、引言 redis学了一段时间了,基本的东西都没问题了。从今天开始讲写一些redis和lua脚本的相关的东西...
        一、介绍 今天继续redis-cli使用的介绍,上一篇文章写了一部分,写到第9个小节,今天就来完成第二部分。...
        一、引言 上一篇文章我们已经介绍了MongoDB数据库的查询操作,但是并没有介绍全,随着自己的学习的深入...