设为首页收藏本站

追梦Linux

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 385|回复: 0

Mongodb高可用集群(二)——副本集

[复制链接]

482

主题

485

帖子

16万

积分

CEO

Rank: 9Rank: 9Rank: 9

积分
168233

最佳新人活跃会员热心会员推广达人宣传达人灌水之王突出贡献优秀版主荣誉管理论坛元老

QQ
发表于 2017-3-3 11:02:03 | 显示全部楼层 |阅读模式
在上一篇文章《Mongodb高可用集群(一)——主从同步》提到了几个问题还没有解决。
  • 主节点挂了能否自动切换连接?目前需要手工切换。
  • 主节点的读写压力过大如何解决?
  • 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
  • 数据压力大到机器支撑不了的时候能否做到自动扩展?
这篇文章看完这些问题就可以搞定了。NoSQL的产生就是为了解决大数据量、高扩展性、高性能、灵活数据模型、高可用性。但是光通过主从模式的架构远远达不到上面几点,由此MongoDB设计了副本集和分片的功能。这篇文章主要介绍副本集
mongoDB官方已经不建议使用主从模式了,替代方案是采用副本集的模式,点击查看 ,如图:


那什么是副本集呢?打魔兽世界总说打副本,其实这两个概念差不多一个意思。游戏里的副本是指玩家集中在高峰时间去一个场景打怪,会出现玩家暴多怪物少的情况,游戏开发商为了保证玩家的体验度,就为每一批玩家单独开放一个同样的空间同样的数量的怪物,这一个复制的场景就是一个副本,不管有多少个玩家各自在各自的副本里玩不会互相影响。 mongoDB的副本也是这个,主从模式其实就是一个单副本的应用,没有很好的扩展性和容错性。而副本集具有多个副本保证了容错性,就算一个副本挂掉了还有很多副本存在,并且解决了上面第一个问题“主节点挂掉了,整个集群内会自动切换”。难怪mongoDB官方推荐使用这种模式。我们来看看mongoDB副本集的架构图:



由图可以看到客户端连接到整个副本集,不关心具体哪一台机器是否挂掉。主服务器负责整个副本集的读写,副本集定期同步数据备份,一但主节点挂掉,副本节点就会选举一个新的主服务器,这一切对于应用服务器不需要关心。我们看一下主服务器挂掉后的架构:



副本集中的副本节点在主节点挂掉后通过心跳机制检测到后,就会在集群内发起主节点的选举机制,自动选举一位新的主服务器。看起来很牛X的样子,我们赶紧操作部署一下!
官方推荐的副本集机器数量为至少3个,那我们也按照这个数量配置测试。

1、准备三台机器

172.17.0.2
    副本集主节点
172.17.0.3   
副本集副本节点
172.17.0.4   
副本集副本节点


2、安装mongodb

注意linux生产环境不能安装32位的mongodb,因为32位受限于操作系统最大2G的文件限制。
[Bash shell] 纯文本查看 复制代码
wget [url=http://downloads.mongodb.org/lin]http://downloads.mongodb.org/lin[/url] ... 64-rhel62-3.4.2.tgz
tar xf mongodb-linux-x86_64-rhel62-3.4.2.tgz
mv mongodb-linux-x86_64-rhel62-3.4.2   /usr/local/mongodb
cd /usr/local/mongodb
#存放配置文件
mkdir /usr/local/mongodb/etc
#存放日志
mkdir /usr/local/mongodb/log
#存放整个mongodb文件
mkdir -p /usr/local/mongodb/data
#存放mongodb数据文件
mkdir -p /usr/local/mongodb/data/replset
#进入mongodb文件夹
cd  /usr/local/mongodb 

3、分别在每台server启动mongodb
[Bash shell] 纯文本查看 复制代码
/usr/local/mongodb/bin/mongod --dbpath /usr/local/mongodb/data/replset --replSet repset --logpath /usr/local/mongodb/log/mongod.log  --fork
4、初始化副本集
在三台机器中任意一台登录mongo(此处演示172.17.0.2)
[Bash shell] 纯文本查看 复制代码
> use admin
switched to db admin
#定义副本集配置变量,这里的 _id:”repset” 和上面命令参数“ –replSet repset” 要保持一样。
> config = { _id:"repset", members:[ {_id:0,host:"172.17.0.2:27017"}, {_id:1,host:"172.17.0.3:27017"}, {_id:2,host:"172.17.0.4:27017"}] }
{
    "_id" : "repset",
    "members" : [
        {
            "_id" : 0,
            "host" : "172.17.0.2:27017"
        },
        {
            "_id" : 1,
            "host" : "172.17.0.3:27017"
        },
        {
            "_id" : 2,
            "host" : "172.17.0.4:27017"
        }
    ]
}

#初始化副本集配置
> rs.initiate(config);
{ "ok" : 1 }
#查看日志,副本集启动成功后,172.17.0.2为主节点PRIMARY,172.17.0.3、172.17.0.4为副本节点SECONDARY。
#查看集群节点的状态
repset:SECONDARY> rs.status();
{
    "set" : "repset",
    "date" : ISODate("2017-03-03T06:00:16.887Z"),
    "myState" : 1,
    "term" : NumberLong(1),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1488520811, 1),
            "t" : NumberLong(1)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1488520811, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1488520811, 1),
            "t" : NumberLong(1)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "172.17.0.2:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 2598,
            "optime" : {
                "ts" : Timestamp(1488520811, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-03-03T06:00:11Z"),
            "electionTime" : Timestamp(1488520541, 1),
            "electionDate" : ISODate("2017-03-03T05:55:41Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 1,
            "name" : "172.17.0.3:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 285,
            "optime" : {
                "ts" : Timestamp(1488520811, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1488520811, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-03-03T06:00:11Z"),
            "optimeDurableDate" : ISODate("2017-03-03T06:00:11Z"),
            "lastHeartbeat" : ISODate("2017-03-03T06:00:15.497Z"),
            "lastHeartbeatRecv" : ISODate("2017-03-03T06:00:14.924Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.17.0.2:27017",
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "172.17.0.4:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 285,
            "optime" : {
                "ts" : Timestamp(1488520811, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1488520811, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-03-03T06:00:11Z"),
            "optimeDurableDate" : ISODate("2017-03-03T06:00:11Z"),
            "lastHeartbeat" : ISODate("2017-03-03T06:00:15.501Z"),
            "lastHeartbeatRecv" : ISODate("2017-03-03T06:00:15.004Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.17.0.3:27017",
            "configVersion" : 1
        }
    ],
    "ok" : 1
}

整个副本集已经搭建成功了。
5、测试副本集数据复制功能
主节点插入数据,每个节点都可以获取数据
[Bash shell] 纯文本查看 复制代码
#在主节点
mongo --host 172.17.0.2 --port 27017
MongoDB shell version v3.4.2
connecting to: mongodb://172.17.0.2:27017/
MongoDB server version: 3.4.2
#建立test 数据库
repset:PRIMARY> use test
switched to db test
#往testdb表插入数据
repset:PRIMARY> db.testdb.insert({"test1":"testval1"})
WriteResult({ "nInserted" : 1 })
repset:PRIMARY> db.testdb.find();
{ "_id" : ObjectId("58b907045462b6144bbc37bc"), "test1" : "testval1" }

#分别在2个从节点读取数据
#mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读。

mongo --host 172.17.0.3 --port 27017
MongoDB shell version v3.4.2
connecting to: mongodb://172.17.0.3:27017/
MongoDB server version: 3.4.2
repset:SECONDARY> db.getMongo().setSlaveOk();
repset:SECONDARY> use test
switched to db test
repset:SECONDARY> db.testdb.find();
{ "_id" : ObjectId("58b907045462b6144bbc37bc"), "test1" : "testval1" }
#######################################################################
mongo --host 172.17.0.4 --port 27017
MongoDB shell version v3.4.2
connecting to: mongodb://172.17.0.4:27017/
MongoDB server version: 3.4.2
repset:SECONDARY> db.getMongo().setSlaveOk();
repset:SECONDARY> use test
switched to db test
repset:SECONDARY> db.testdb.find();
{ "_id" : ObjectId("58b907045462b6144bbc37bc"), "test1" : "testval1" }


6、测试副本集故障转移功能
先停掉主节点mongodb 172.17.0.2,查看172.17.0.3172.17.0.4的日志可以看到经过一系列的投票选择操作,172.17.0.3当选主节点,172.17.0.2172.17.0.3同步数据过来,通过状态也可以看到。
[Bash shell] 纯文本查看 复制代码
repset:SECONDARY> rs.status();
{
    "set" : "repset",
    "date" : ISODate("2017-03-03T06:13:43.703Z"),
    "myState" : 2,
    "term" : NumberLong(2),
    "syncingTo" : "172.17.0.3:27017",
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1488521621, 1),
            "t" : NumberLong(2)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1488521621, 1),
            "t" : NumberLong(2)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1488521621, 1),
            "t" : NumberLong(2)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "172.17.0.2:27017",
            "health" : 0,
            "state" : 8,
            "stateStr" : "(not reachable/healthy)",
            "uptime" : 0,
            "optime" : {
                "ts" : Timestamp(0, 0),
                "t" : NumberLong(-1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(0, 0),
                "t" : NumberLong(-1)
            },
            "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
            "optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
            "lastHeartbeat" : ISODate("2017-03-03T06:13:42.463Z"),
            "lastHeartbeatRecv" : ISODate("2017-03-03T06:11:00.251Z"),
            "pingMs" : NumberLong(0),
            "lastHeartbeatMessage" : "Connection refused",
            "configVersion" : -1
        },
        {
            "_id" : 1,
            "name" : "172.17.0.3:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 1090,
            "optime" : {
                "ts" : Timestamp(1488521621, 1),
                "t" : NumberLong(2)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1488521621, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-03-03T06:13:41Z"),
            "optimeDurableDate" : ISODate("2017-03-03T06:13:41Z"),
            "lastHeartbeat" : ISODate("2017-03-03T06:13:43.154Z"),
            "lastHeartbeatRecv" : ISODate("2017-03-03T06:13:42.753Z"),
            "pingMs" : NumberLong(0),
            "electionTime" : Timestamp(1488521470, 1),
            "electionDate" : ISODate("2017-03-03T06:11:10Z"),
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "172.17.0.4:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 3383,
            "optime" : {
                "ts" : Timestamp(1488521621, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-03-03T06:13:41Z"),
            "syncingTo" : "172.17.0.3:27017",
            "configVersion" : 1,
            "self" : true
        }
    ],
    "ok" : 1
}


再启动之前的主节点,此时变成
SECONDARY
[Bash shell] 纯文本查看 复制代码
repset:SECONDARY> rs.status();
{
    "set" : "repset",
    "date" : ISODate("2017-03-03T06:19:25.415Z"),
    "myState" : 2,
    "term" : NumberLong(2),
    "syncingTo" : "172.17.0.3:27017",
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1488521961, 1),
            "t" : NumberLong(2)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1488521961, 1),
            "t" : NumberLong(2)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1488521961, 1),
            "t" : NumberLong(2)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "172.17.0.2:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 6,
            "optime" : {
                "ts" : Timestamp(1488521961, 1),
                "t" : NumberLong(2)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1488521961, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-03-03T06:19:21Z"),
            "optimeDurableDate" : ISODate("2017-03-03T06:19:21Z"),
            "lastHeartbeat" : ISODate("2017-03-03T06:19:25.161Z"),
            "lastHeartbeatRecv" : ISODate("2017-03-03T06:19:23.041Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.17.0.4:27017",
            "configVersion" : 1
        },
        {
            "_id" : 1,
            "name" : "172.17.0.3:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 1432,
            "optime" : {
                "ts" : Timestamp(1488521961, 1),
                "t" : NumberLong(2)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1488521961, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-03-03T06:19:21Z"),
            "optimeDurableDate" : ISODate("2017-03-03T06:19:21Z"),
            "lastHeartbeat" : ISODate("2017-03-03T06:19:23.514Z"),
            "lastHeartbeatRecv" : ISODate("2017-03-03T06:19:25.152Z"),
            "pingMs" : NumberLong(0),
            "electionTime" : Timestamp(1488521470, 1),
            "electionDate" : ISODate("2017-03-03T06:11:10Z"),
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "172.17.0.4:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 3725,
            "optime" : {
                "ts" : Timestamp(1488521961, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-03-03T06:19:21Z"),
            "syncingTo" : "172.17.0.3:27017",
            "configVersion" : 1,
            "self" : true
        }
    ],
    "ok" : 1
}


7、java程序连接副本集测试。
三个节点有一个节点挂掉也不会影响应用程序客户端对整个副本集的读写!
[Java] 纯文本查看 复制代码
public class TestMongoDBReplSet {
 
        public static void main(String[] args) {
 
               try {
                     List<ServerAddress> addresses = new ArrayList<ServerAddress>();
                     ServerAddress address1 = new ServerAddress("172.17.0.2" , 27017);
                     ServerAddress address2 = new ServerAddress("172.17.0.3" , 27017);
                     ServerAddress address3 = new ServerAddress("172.17.0.4" , 27017);
                     addresses.add(address1);
                     addresses.add(address2);
                     addresses.add(address3);
 
                     MongoClient client = new MongoClient(addresses);
                     DB db = client.getDB( "test");
                     DBCollection coll = db.getCollection( "testdb");
 
                      // 插入
                     BasicDBObject object = new BasicDBObject();
                     object.append( "test2", "testval2" );
 
                     coll.insert(object);
 
                     DBCursor dbCursor = coll.find();
 
                      while (dbCursor.hasNext()) {
                           DBObject dbObject = dbCursor.next();
                           System. out.println(dbObject.toString());
                     }
 
              } catch (Exception e) {
                     e.printStackTrace();
              }
 
       }
 
}



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



常规写操作来说并没有读操作多,所以一台主节点负责写,两台副本节点负责读。
1、设置读写分离需要先在副本节点SECONDARY 设置 setSlaveOk。
2、在程序中设置副本节点负责读操作,如下代码:
[Java] 纯文本查看 复制代码
public class TestMongoDBReplSetReadSplit {
 
        public static void main(String[] args) {
 
               try {
                     List<ServerAddress> addresses = new ArrayList<ServerAddress>();
                     ServerAddress address1 = new ServerAddress("172.17.0.2" , 27017);
                     ServerAddress address2 = new ServerAddress("172.17.0.3" , 27017);
                     ServerAddress address3 = new ServerAddress("172.17.0.4" , 27017);
                     addresses.add(address1);
                     addresses.add(address2);
                     addresses.add(address3);
 
                     MongoClient client = new MongoClient(addresses);
                     DB db = client.getDB( "test" );
                     DBCollection coll = db.getCollection( "testdb" );
 
 
                     BasicDBObject object = new BasicDBObject();
                     object.append( "test2" , "testval2" );
 
                      //读操作从副本节点读取
                     ReadPreference preference = ReadPreference. secondary();
                     DBObject dbObject = coll.findOne(object, null , preference);
 
                     System. out .println(dbObject);
 
 
              } catch (Exception e) {
                     e.printStackTrace();
              }
       }
}

读参数除了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的故障转移会不会无故自动发生?什么条件会触发?频繁触发可能会带来系统负载加重

    参考:
    http://cn.docs.mongodb.org/manual/administration/replica-set-member-configuration/
    http://docs.mongodb.org/manual/reference/connection-string/
    http://www.cnblogs.com/magialmoon/p/3268963.html

QQ|小黑屋|手机版|Archiver|追梦Linux ( 粤ICP备14096197号  点击这里给我发消息

GMT+8, 2019-7-17 17:04 , Processed in 0.472310 second(s), 36 queries .

Powered by 追梦Linux! X3.3 Licensed

© 2015-2017 追梦Linux!.

快速回复 返回顶部 返回列表