本文将介绍如何使用 MongoDB 提供的 Replica Set 和 Shards 功能构建一个分布式 MongoDB 集群。
Replica Set 部署
我们先从部署一个三节点的 Replica Set 开始。

首先,我们要为每个 mongod 实例创建它自己的 dbpath:
1 2 3
|
mkdir 1 mkdir 2 mkdir 3
|
然后,我们便可以开始启动这三个 mongod 实例了:
1 2 3
|
mongod --dbpath 1 --port 27001 --replSet myRS mongod --dbpath 2 --port 27002 --replSet myRS mongod --dbpath 3 --port 27003 --replSet myRS
|
注意,这里我是为了在同一台机器上运行三个 mongod 实例,所以需要为它们分别指定不同的端口。如果是真实的分布式 Replica Set,在每台机器上使用默认的 27017 端口是完全可行的。
除此之外,我使用 --replSet 参数指定了 mongod 实例所属 Replica Set 的名字。这个名字是可以随意起的,但必须确保属于同一个 Replica Set 的 mongod 实例设置了相同的 --replSet,否则可能会产生一些不可预期的后果。
在顺利打开这些 mongod 实例后以后,不出意外的话我们应该能在输出的日志信息中看到如下记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
2015-11-14T16:25:46.060+0800 I JOURNAL [initandlisten] journal dir=3\journal 2015-11-14T16:25:46.061+0800 I JOURNAL [initandlisten] recover : no journal files present, no recovery needed 2015-11-14T16:25:46.078+0800 I JOURNAL [durability] Durability thread started 2015-11-14T16:25:46.078+0800 I JOURNAL [journal writer] Journal writer thread started 2015-11-14T16:25:46.613+0800 I CONTROL [initandlisten] MongoDB starting : pid=9812 port=27003 dbpath=3 64-bit host=mrdai-Laptop 2015-11-14T16:25:46.613+0800 I CONTROL [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2 2015-11-14T16:25:46.613+0800 I CONTROL [initandlisten] db version v3.0.7 2015-11-14T16:25:46.614+0800 I CONTROL [initandlisten] git version: 6ce7cbe8c6b899552dadd907604559806aa2e9bd 2015-11-14T16:25:46.614+0800 I CONTROL [initandlisten] build info: windows sys.getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49 2015-11-14T16:25:46.614+0800 I CONTROL [initandlisten] allocator: tcmalloc 2015-11-14T16:25:46.614+0800 I CONTROL [initandlisten] options: { net: { port: 27003 }, replication: { replSet: "myRS" }, storage: { dbPath: "3" } } 2015-11-14T16:25:46.615+0800 I INDEX [initandlisten] allocating new ns file 3\local.ns, filling with zeroes... 2015-11-14T16:25:47.542+0800 I STORAGE [FileAllocator] allocating new datafile 3\local.0, filling with zeroes... 2015-11-14T16:25:47.543+0800 I STORAGE [FileAllocator] creating directory 3\_tmp 2015-11-14T16:25:47.544+0800 I STORAGE [FileAllocator] done allocating datafile 3\local.0, size: 64MB, took 0 secs 2015-11-14T16:25:47.551+0800 I REPL [initandlisten] Did not find local replica set configuration document at startup; NoMatchingDocument Did not find replica set configuration document in local.system.replset 2015-11-14T16:25:47.552+0800 I NETWORK [initandlisten] waiting for connections on port 27003
|
可以注意到,倒数第二条记录显示 mongod 未能在本地数据中找到 Replica Set 的设置信息。这是正常的,因为这是第一次创建的 Replica Set。最后一条信息显示 mongod 启动完毕,等待外界连接它的端口。
那么,我们开始启动 Replica Set。使用 mongo 连入随便一个 mongod 实例,并进行设置:
1 2 3 4 5 6 7 8 9 10
|
var conf = { _id : "myRS", members : [ { _id : 1, host : "localhost:27001" }, { _id : 2, host : "localhost:27002" }, { _id : 3, host : "localhost:27003" } ] }
rs.initiate(conf)
|
在 conf 中,我们将 _id 设置为 Replica Set 的名称,并在 members 中设置了 Replica Set 所有成员的信息,其中包括成员的名称 _id 以及成员的主机名 host。
注意,尽管这里可以直接使用了 IP:端口 的形式来指定 mongod 实例,但在真实环境中,不要这么做,这种做法十分糟糕。不过现在搭建分布式,大家的做法似乎更倾向于为每台机器修改 hosts 文件。同样,不要这么做,这两种做法都属于 bad practice。最好的做法,是在你的集群环境中配置一台 DNS 服务器。这样,当你的某一个结点的 IP 发生变化时,你就只需要修改 DNS 服务器中的那条解析条目,而不需要修改每个结点的 hosts 文件了。
直接以数字作为每个结点的名称也是不好的做法,因为这个名称在 mongod 的日志信息中会经常出现。使用更加可读的名称是更好的做法。
一切正常的话,你应该会在其中一个结点上看到如下日志信息:
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
|
2015-11-14T16:41:54.946+0800 I NETWORK [initandlisten] connection accepted from 127.0.0.1:61875 #1 (1 connection now open) 2015-11-14T16:41:54.951+0800 I NETWORK [conn1] end connection 127.0.0.1:61875 (0 connections now open) 2015-11-14T16:41:54.953+0800 I NETWORK [initandlisten] connection accepted from 127.0.0.1:61877 #2 (1 connection now open) 2015-11-14T16:41:55.013+0800 I NETWORK [initandlisten] connection accepted from 127.0.0.1:61882 #3 (2 connections now open) 2015-11-14T16:41:55.018+0800 I NETWORK [conn3] end connection 127.0.0.1:61882 (1 connection now open) 2015-11-14T16:41:55.078+0800 I REPL [WriteReplSetConfig] Starting replication applier threads 2015-11-14T16:41:55.082+0800 I REPL [ReplicationExecutor] New replica set config in use: { _id: "myRS", version: 1, members: [ { _id: 1, host: "localhost:27001", arbiterOnly: false, buildIndexes: true, hidden: false, priority: 1.0, tags: {}, slaveDelay: 0, votes: 1 }, { _id: 2, host: "localhost:27002", arbiterOnly: false, buildIndexes: true, hidden: false, priority: 1.0, tags: {}, slaveDelay: 0, votes: 1 }, { _id:3, host: "localhost:27003", arbiterOnly: false, buildIndexes: true, hidden: false, priority...(line truncated)... 2015-11-14T16:41:55.086+0800 I NETWORK [initandlisten] connection accepted from 127.0.0.1:61884 #4 (2 connections now open) 2015-11-14T16:41:55.115+0800 I REPL [ReplicationExecutor] This node is localhost:27003 in the config 2015-11-14T16:41:55.128+0800 I REPL [ReplicationExecutor] transition to STARTUP2 2015-11-14T16:41:55.134+0800 I REPL [rsSync] ****** 2015-11-14T16:41:55.136+0800 I REPL [rsSync] creating replication oplog of size: 6172MB... 2015-11-14T16:41:55.137+0800 I STORAGE [FileAllocator] allocating new datafile 3\local.1, filling with zeroes... 2015-11-14T16:41:55.139+0800 I REPL [ReplicationExecutor] Member localhost:27001 is now in state STARTUP2 2015-11-14T16:41:55.151+0800 I STORAGE [FileAllocator] done allocating datafile 3\local.1, size: 2047MB, took 0.001 secs 2015-11-14T16:41:55.153+0800 I STORAGE [FileAllocator] allocating new datafile 3\local.2, filling with zeroes... 2015-11-14T16:41:55.161+0800 I STORAGE [FileAllocator] done allocating datafile 3\local.2, size: 2047MB, took 0.001 secs 2015-11-14T16:41:55.170+0800 I STORAGE [FileAllocator] allocating new datafile 3\local.3, filling with zeroes... 2015-11-14T16:41:55.171+0800 I REPL [ReplicationExecutor] Member localhost:27002 is now in state STARTUP2 2015-11-14T16:41:55.186+0800 I STORAGE [FileAllocator] done allocating datafile 3\local.3, size: 2047MB, took 0.001 secs 2015-11-14T16:41:56.198+0800 I REPL [rsSync] ****** 2015-11-14T16:41:56.198+0800 I REPL [rsSync] initial sync pending 2015-11-14T16:41:56.200+0800 I REPL [rsSync] no valid sync sources found in current replset to do an initial sync 2015-11-14T16:41:57.139+0800 I REPL [ReplicationExecutor] Member localhost:27001 is now in state SECONDARY 2015-11-14T16:41:57.206+0800 I REPL [rsSync] initial sync pending 2015-11-14T16:41:57.206+0800 I REPL [ReplicationExecutor] syncing from: localhost:27001 2015-11-14T16:41:57.221+0800 I REPL [rsSync] initial sync drop all databases 2015-11-14T16:41:57.222+0800 I STORAGE [rsSync] dropAllDatabasesExceptLocal 1 2015-11-14T16:41:57.222+0800 I REPL [rsSync] initial sync clone all databases 2015-11-14T16:41:57.229+0800 I REPL [rsSync] initial sync data copy, starting syncup 2015-11-14T16:41:57.234+0800 I REPL [rsSync] oplog sync 1 of 3 2015-11-14T16:41:57.239+0800 I REPL [rsSync] oplog sync 2 of 3 2015-11-14T16:41:57.254+0800 I REPL [rsSync] initial sync building indexes 2015-11-14T16:41:57.258+0800 I REPL [rsSync] oplog sync 3 of 3 2015-11-14T16:41:57.265+0800 I REPL [rsSync] initial sync finishing up 2015-11-14T16:41:57.268+0800 I REPL [rsSync] replSet set minValid=5646f3d4:1 2015-11-14T16:41:57.274+0800 I REPL [rsSync] initial sync done 2015-11-14T16:41:57.290+0800 I REPL [ReplicationExecutor] transition to RECOVERING 2015-11-14T16:41:57.292+0800 I REPL [ReplicationExecutor] transition to SECONDARY 2015-11-14T16:41:58.136+0800 I REPL [ReplicationExecutor] could not find member to sync from 2015-11-14T16:41:58.971+0800 I REPL [ReplicationExecutor] replSetElect voting yea for localhost:27001 (1) 2015-11-14T16:41:59.140+0800 I REPL [ReplicationExecutor] Member localhost:27001 is now in state PRIMARY 2015-11-14T16:41:59.171+0800 I REPL [ReplicationExecutor] Member localhost:27002 is now in state SECONDARY
|
从日志中,我们可以很清晰地看到,发起 rs.initiate 的 mongod 向其他 mongod 开启了连接,其他 mongod 获取到了我们配置的 conf 信息。而后,Replica Set 开始启动。首先是各结点进行初始化同步,从发起 rs.initiate 的 mongod 处同步了 oplog,并进入 Secondary 状态。然后,3 个 Secondary 发现 Replica Set 中没有 Primary,于是发起选举。日志里,我们甚至可以看到这个 mongod 把票投给了谁。最后,选举结束,localhost:27001 成为了 Primary。
使用 Java 驱动连接至 Replica Set
我们通过如下语句连接至单一的 MongoDB 实例:
1
|
MongoClient client = new MongoClient("localhost", 27001);
|
我们为 MongoClient 对象指定了一个 MongoDB 实例的主机名和端口号。以这种方式初始化的 MongoClient 会假设目标 MongoDB 实例只是一个 standalone 的实例,如果该实例不是 Primary 时,客户端执行写操作则可能被该 MongoDB 实例拒绝。
通过如下语句可使 MongoClient 进入 Replica Set 模式:
1 2 3
|
MongoClient client = new MongoClient(asList( new ServerAddress("localhost", 27001) ));
|
我们通过 Arrays#asList 方法为 MongoClient 传入了一个 List,MongoClient 便会进入 Replica Set 模式。在这种模式下,客户端会利用给定的主机(seedlist)来发现 Replica Set 的其他所有结点,其中就包括了 Primary。因此,即使 localhost:27001 不是 Primary 也没关系,客户端会通过它获知 Primary 的地址并自动连接至 Primary。
但以上做法仍不全面:如果 localhost:27001 进程已经挂了,或者它并不是 Replica Set 的成员,我们便无法通过上述语句连接至 Replica Set。 我们可以为构造函数传入更多的 MongoDB 实例的地址来降低这种情况发生的几率:
1 2 3 4 5
|
MongoClient client = new MongoClient(asList( new ServerAddress("localhost", 27001), new ServerAddress("localhost", 27002), new ServerAddress("localhost", 27003) ));
|
当然,也有可能正好你指定的这多个结点都同时挂掉,那样自然是防不胜防了。不过,提高 Replica Set 拓扑可用性就是网络架构的问题了。当我们在执行写操作时,我们还需要考虑 Primary 会突然挂掉。比如说,我们正在执行这样的写操作:
1 2 3 4 5 6
|
MongoCollection collection = client.getDatabase("foo").getCollection("bar");
for (int i = 0; i < Integer.MAX_VALUE; i++) { collection.insertOne(new Document("_id", new ObjectId()).append("i", i)); Thread.sleep(500); }
|
在执行插入时,如果 Primary 突然失效(如调用了 rs.stepDown()),那么上述代码中的 insertOne 方法会抛出一个错误。因此,更为健壮的做法,是为该 insertOne 语句加上 try/catch 块:
1 2 3 4 5 6 7 8
|
for (int i = 0; i < Integer.MAX_VALUE; i++) { try { collection.insertOne(new Document("_id", new ObjectId()).append("i", i)); } catch (MongoException e) { // Handle the exception } Thread.sleep(500); }
|
遗憾的是,抛出错误的 insertOne 操作恐怕无法由 MongoDB 驱动自动重试。实际上,不只是触发错误的那一次操作,在 Replica Set 自动选举出新的 Primary 前,所有写操作都会抛出错误。但幸运的是,由于加上了 try/catch 块,应用程序不会因为单次写入失败便直接退出。在触发错误后,下一次插入前驱动都会重新尝试利用 seedlist 来获取新的 Primary 的地址。当 Replica Set 重新选举出新的 Primary 后,驱动便可以再次进行写操作了。
通过观察 MongoDB Java 驱动输出的日志信息,你可以更细致地观察驱动的行为。这里就不直接给出了,有兴趣可自己尝试。
Shard 集群部署
在本节中,我们将会在本机上部署一个完整的生产级别的 MongoDB Shard 集群。集群由 4 个 Shard 负责存储数据,其中每个 Shard 都是包含三个结点的 Replica Set。除此之外,集群还包括 4 个 mongos 和 3 个 Config Server。
注意,用于生产环境的 Shard 集群必须遵循如下几个原则:必须使用 Replica Set 来作为 Shard,任何一个 Shard 的不可用都会导致集群出现异常;必须使用正好 3 个 Config Server,Config Server 不可用将导致整个集群不可用。除此之外,使用两个以上的 mongos 实例可以更好地分散压力。
4 个 Replica Set 的信息分别如下:
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
|
{ _id : "a", members : [ { _id : "a1", host : "localhost:27001" }, { _id : "a2", host : "localhost:27002" }, { _id : "a3", host : "localhost:27003" } ] }
{ _id : "b", members : [ { _id : "b1", host : "localhost:27101" }, { _id : "b2", host : "localhost:27102" }, { _id : "b3", host : "localhost:27103" } ] }
{ _id : "c", members : [ { _id : "c1", host : "localhost:27201" }, { _id : "c2", host : "localhost:27202" }, { _id : "c3", host : "localhost:27203" } ] }
{ _id : "d", members : [ { _id : "d1", host : "localhost:27301" }, { _id : "d2", host : "localhost:27302" }, { _id : "d3", host : "localhost:27303" } ] }
|
集群各成员启动
首先我们分别启动集群的各个成员,分别是 Shard、Config Server 和 Query Router。其中前两种成员均为 mongod,而 Query Router 则是 mongos。
单个 Replica Set 的配置方式大致上无太大变化,只是作为 Shard Server 在启动 mongod 时需要加上–shardsvr选项。 以 Replica Set a 为例:
1 2 3 4 5
|
mkdir a{1,2,3}
mongod --shardsvr --replSet a --dbpath a1 --logpath log.a1 --port 27001 --fork mongod --shardsvr --replSet a --dbpath a2 --logpath log.a2 --port 27002 --fork mongod --shardsvr --replSet a --dbpath a3 --logpath log.a3 --port 27003 --fork
|
注意:当 --shardsvr 选项被打开时,mongod 的默认端口号变为 27018。
再使用 mongo 连接至任意一个 mongod 实例,启动 Replica Set:
1 2 3 4 5 6 7 8 9 10
|
var conf = { _id : "a", members : [ { _id : "a1", host : "localhost:27001" }, { _id : "a2", host : "localhost:27002" }, { _id : "a3", host : "localhost:27003" } ] }
rs.initiate(conf)
|
重复上述操作即可启动其余三个 Replica Set。
接下来开始启动 Config Server:
1 2 3 4 5
|
mkdir cfg{1,2,3}
mongod --configsvr --dbpath cfg1 --logpath log.cfg1 --port 26050 --fork mongod --configsvr --dbpath cfg2 --logpath log.cfg2 --port 26051 --fork mongod --configsvr --dbpath cfg3 --logpath log.cfg3 --port 26052 --fork
|
注意:当 --configvr 选项被打开时,mongod 的默认端口号变为27019。
最后,启动 Query Router:
1 2 3 4
|
mongos --configdb localhost:26050,localhost:26051,localhost:26052 --logpath log.mongos1 --fork mongos --configdb localhost:26050,localhost:26051,localhost:26052 --logpath log.mongos2 --port 26061 --fork mongos --configdb localhost:26050,localhost:26051,localhost:26052 --logpath log.mongos3 --port 26062 --fork mongos --configdb localhost:26050,localhost:26051,localhost:26052 --logpath log.mongos4 --port 26063 --fork
|
注意:mongos 的默认端口号为27017,与 mongod 、 mongo 的默认端口号相同。
如此一来,集群的各个成员都启动完毕了,可以开始配置集群了。
添加 Shard
实际上,在启动 mongos 时,我们已经指定了集群所使用的 Config Server 的地址。接下来就是为集群指定每个 Shard 的地址了。
打开 mongo 连接至任意一个 mongos,并执行如下指令:
1 2 3 4
|
sh.addShard("a/localhost:27001") sh.addShard("b/localhost:27101") sh.addShard("c/localhost:27201") sh.addShard("d/localhost:27301")
|
注意到,我们添加 Shard 时,输入了 Replica Set 的名称以及其中一个成员的地址。该成员并不一定得是 Primary,只要它是该 Replica Set 的成员,mongos 就能自动发现 Replica Set 的其他所有成员。
在添加了 4 个 Shard 以后,整个 Shard 集群便配置完毕,可以开始使用了。
from: https://mr-dai.github.io/mongodb_distribution_tutorial/
- MongoDB单机, 主从, 分布式部署
MongoDB是最易用的NoSQL,比较适合取代MySQL做一些存储,不过不是强一致性的.本文介绍一下MongoDB各种部署方式,并分享一些感受.前两部分“单机部署”和“主从部署”是“分片部署”的基础 ...
- 大数据学习笔记——Spark完全分布式完整部署教程
Spark完全分布式完整部署教程 继Mapreduce之后,作为新一代并且是主流的计算引擎,学好Spark是非常重要的,这一篇博客会专门介绍如何部署一个分布式的Spark计算框架,在之后的博客中,更会 ...
- 大数据学习笔记——Hbase高可用+完全分布式完整部署教程
Hbase高可用+完全分布式完整部署教程 本篇博客承接上一篇sqoop的部署教程,将会详细介绍完全分布式并且是高可用模式下的Hbase的部署流程,废话不多说,我们直接开始! 1. 安装准备 部署Hba ...
- Asp.Net Core Web Api图片上传(一)集成MongoDB存储实例教程
Asp.Net Core Web Api图片上传及MongoDB存储实例教程(一) 图片或者文件上传相信大家在开发中应该都会用到吧,有的时候还要对图片生成缩略图.那么如何在Asp.Net Core W ...
- mongodb分布式集群搭建手记
一.架构简介 目标单机搭建mongodb分布式集群(副本集 + 分片集群),演示mongodb分布式集群的安装部署.简单操作. 说明在同一个vm启动由两个分片组成的分布式集群,每个分片都是一个PSS( ...
- 第六章 Fisco Bcos 多服务器分布式部署
想了解相关区块链开发,技术提问,请加QQ群:538327407 前提概要 前面几章,我们通过单机部署,在单台服务器上搭建四个节点,完成Fisco Bcos 底层搭建,并完成相关合约开发.sdk 开发. ...
- 服务器小白的我,是如何将 node+mongodb 项目部署在服务器上并进行性能优化的
前言 本文讲解的是:做为前端开发人员,对服务器的了解还是小白的我,是如何一步步将 node+mongodb 项目部署在阿里云 centos 7.3 的服务器上,并进行性能优化,达到页面 1 秒内看到 ...
- MongoDB安装启动教程
MongoDB安装启动教程 简易教程:鉴于第一次大家使用分布式数据库,提供一个简易教程(也可看老师的PPT或者视频) 1.点击安装包(老师给的),安装目录不要更改,否则后面配置需要改,可能导致装不上 ...
- 2020重新出发,NOSQL,MongoDB分布式集群架构
MongoDB分布式集群架构 看到这里相信你已经掌握了 MongoDB 的大部分基本知识,现在在单机环境下操作 MongoDB 已经不存在问题,但是单机环境只适合学习和开发测试,在实际的生产环境中,M ...
随机推荐
- 搭建LDAP服务器
1. 使用SSH 登陆服务器. 2. 更新有效的包. sudo apt-get update 3. 安装LDAP和一些其它LDAP相关的工具. sudo apt-get install slapd l ...
- LOOPS 概率dp
题意:迷宫是一个R*C的布局,每个格子中给出停留在原地,往右走一个,往下走一格的概率,起点在(1,1),终点在(R,C),每走一格消耗两点能量,求出最后所需要的能量期望 简单概率dp 注意 原地不 ...
- 001 SpringMVC的helloWorld程序
一:helloworld程序 1.结构目录 2.添加lib包 3.在web.xml中配置DispatchServlet 这个就是主servlet. <?xml version="1.0 ...
- Ubuntu安装及卸载brew
网站:http://linuxbrew.sh/ 一.安装: 不能在root下运行 $sh -c "$(curl -fsSL https://raw.githubusercontent.com ...
- 通过jstack与jmap分析一次线上故障
一.发现问题 下面是线上机器的cpu使用率,可以看到从4月8日开始,随着时间cpu使用率在逐步增高,最终使用率达到100%导致线上服务不可用,后面重启了机器后恢复. 二.排查思路 简单分析下可能出问题 ...
- 通过css属性hack完成select样式美化,并兼容IE
最近在重构时遇到了select样式问题,并且需要在不影响语义化的情况下,兼容IE8. 经过一番的百度后始终没有找到合适的纯CSS解决方案,最后换了一下思路,大胆使用了属性hack: 在chrome和F ...
- beta6
吴晓晖(组长) 过去两天完成了哪些任务 对手写输入进行了重构,然后重新捋了一下bayes的思路 展示GitHub当日代码/文档签入记录 接下来的计划 推荐算法 还剩下哪些任务 过去两天完成了哪些任务: ...
- hdu 5723 Abandoned country 最小生成树 期望
Abandoned country 题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=5723 Description An abandoned coun ...
- JavaScript学习方法
首先要说明的是,咱现在不是高手,最多还是一个半桶水,算是入了JS的门. 谈不上经验,都是一些教训. 这个时候有人要说,“靠,你丫半桶水,凭啥教我们”.您先别急着骂,先听我说. 你叫一个大学生去教小学数 ...
- 重读JavaScript高级程序设计
不断更新中--- 第三章 基本概念 1.变量声明但未初始化值是undefined,而未声明的变量只能执行typeof操作,并且未初始化和未声明用typeof都同样返回undefined 2.Numbe ...