mongodb集群有三种方式
1,主从模式,类似mysql master slave方式。
2,副本集模式,其实是一主多从,如果主节点挂掉,会重新在从节点选取一台为主节点。
3,分片模式,针对大数据量,高负载情况。
mysql要做到自动扩展需要加一个数据访问层用程序去扩展,数据库的增加、删除、备份还需要程序去控制。一但数据库的节点一多,要维护起来也是非常头疼的。不过mongodb所有的这一切通过他自己的内部机制就可以搞定!顿时石化了,这么牛X!还是上图看看mongodb通过哪些机制实现路由、分片:
<a href="http://s3.51cto.com/wyfs02/M02/6B/65/wKioL1UsvwCgUF0jAAG8apv8TOY057.jpg" target="_blank"></a>
从图中可以看到有四个组件:mongos、config server、shard、replica set。
mongos,数据库集群请求的入口,所有的请求都通过mongos进行协调,不需要在应用程序添加一个路由选择器,mongos自己就是一个请求分发中心,它负责把对应的数据请求请求转发到对应的shard服务器上。在生产环境通常有多mongos作为请求的入口,防止其中一个挂掉所有的mongodb请求都没有办法操作。
config server,顾名思义为配置服务器,存储所有数据库元信息(路由、分片)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,这个可不能丢失!就算挂掉其中一台,只要还有存货, mongodb集群就不会挂掉。
shard,这就是传说中的分片了。上面提到一个机器就算能力再大也有天花板,就像军队打仗一样,一个人再厉害喝血瓶也拼不过对方的一个师。俗话说三个臭皮匠顶个诸葛亮,这个时候团队的力量就凸显出来了。在互联网也是这样,一台普通的机器做不了的多台机器来做,如下图:
一台机器的一个数据表 Collection1 存储了 1T 数据,压力太大了!在分给4个机器后,每个机器都是256G,则分摊了集中在一台机器的压力。也许有人问一台机器硬盘加大一点不就可以了,为什么要分给四台机器呢?不要光想到存储空间,实际运行的数据库还有硬盘的读写、网络的IO、CPU和内存的瓶颈。在mongodb集群只要设置好了分片规则,通过mongos操作数据库就能自动把对应的数据操作请求转发到对应的分片机器上。在生产环境中分片的片键可要好好设置,这个影响到了怎么把数据均匀分到多个分片机器上,不要出现其中一台机器分了1T,其他机器没有分到的情况,这样还不如不分片!
replica set,上两节已经详细讲过了这个东东,怎么这里又来凑热闹!其实上图4个分片如果没有 replica set 是个不完整架构,假设其中的一个分片挂掉那四分之一的数据就丢失了,所以在高可用性的分片架构还需要对于每一个分片构建 replica set 副本集保证分片的可靠性。生产环境通常是 2个副本 + 1个仲裁。
说了这么多,还是来实战一下如何搭建高可用的mongodb集群:
首先确定各个组件的数量,mongos 3个, config server 3个,数据分3片 shard server 3个,每个shard 有一个副本一个仲裁也就是 3 * 2 = 6 个,总共需要部署15个实例。这些实例可以部署在独立机器也可以部署在一台机器,我们这里测试资源有限,只准备了 3台机器,在同一台机器只要端口不同就可以,看一下物理部署图:
下面是安装配置
1,准备三台机器
ip: <code>10.19</code><code>.</code><code>21.241</code> ,<code>10.19</code><code>.</code><code>21.242</code> ,<code>10.19</code><code>.</code><code>21.243</code>
分别在每台机器上,同步时间服务器
<code>yum install ntpdate -y</code>
<code>/usr/sbin/ntpdate cn.pool.ntp.org</code>
顺便把硬件时间也同步下
<code></code>
<code>hwclock --systohc</code>
<code>10.19</code><code>.</code><code>21.241</code> <code>| success | rc=</code><code>0</code> <code>>></code>
<code>10.19</code><code>.</code><code>21.243</code> <code>| success | rc=</code><code>0</code> <code>>></code>
<code>10.19</code><code>.</code><code>21.242</code> <code>| success | rc=</code><code>0</code> <code>>></code>
2,在一台机器上,下载mongodb
<code>wget https:</code><code>//fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-3.2.8.tgz</code>
3,解压安装mongodb
<code>tar zxvf mongodb-linux-x86_64-rhel70-</code><code>3.2</code><code>.</code><code>8</code><code>.tgz</code>
<code>mv mongodb-linux-x86_64-rhel70-</code><code>3.2</code><code>.</code><code>8</code> <code>/usr/local/mongodb</code>
4,在一台机器上,创建mongos config shard1 shard2 shard3五个目录,因为mongos不存储数据,只需建立日志文件目录
建立mongos目录和日志目录
<code>mkdir -p /usr/local/mongodb/mongos/log</code>
建立config server数据目录
<code>mkdir -p /usr/local/mongodb/config/data</code>
建立config server日志目录
<code>mkdir -p /usr/local/mongodb/config/log</code>
建立shard1数据目录和日志目录
<code>mkdir -p /usr/local/mongodb/shard1/data /usr/local/mongodb/shard1/log</code>
建立shard2数据目录和日志目录
<code>mkdir -p /usr/local/mongodb/shard2/data /usr/local/mongodb/shard2/log</code>
建立shard3数据目录和日志目录
5,规划5个组件对应的端口号,由于一个机器需要同时部署mongos, config server, shard1, shard2, shard3,所以需要用端口号进行区分
这个端口号可以自己定义,mongos:20000,config server:21000, shard1:22001, shard2:22002, shard3:22003
6,配置服务器的配置文件
<code>cat /usr/local/mongodb/config/mongod-configsvr.conf</code>
<code>dbpath=/usr/local/mongodb/config/data</code>
<code>port=</code><code>21000</code>
<code>logpath=/usr/local/mongodb/config/log/config.log</code>
<code>fork=</code><code>true</code>
<code>configsvr=</code><code>true</code>
<code>logappend=</code><code>true</code>
配置每台mongos服务器
<code>cat /usr/local/mongodb/config/mongod-mongos.conf</code>
<code>configdb=</code><code>10.19</code><code>.</code><code>21.241</code><code>:</code><code>21000</code><code>,</code><code>10.19</code><code>.</code><code>21.242</code><code>:</code><code>21000</code><code>,</code><code>10.19</code><code>.</code><code>21.243</code><code>:</code><code>21000</code>
<code>port=</code><code>20000</code>
<code>logpath=/usr/local/mongodb/mongos/log/mongos.log</code>
配置各个分片的副本集
<code>cat /usr/local/mongodb/config/mongod-shardsvr1.conf</code>
<code>shardsvr=</code><code>true</code>
<code>replSet=shard1</code>
<code>port=</code><code>22001</code>
<code>dbpath=/usr/local/mongodb/shard1/data</code>
<code>logpath=/usr/local/mongodb/shard1/log/shard1.log</code>
<code> </code>
<code>cat /usr/local/mongodb/config/mongod-shardsvr2.conf</code>
<code>replSet=shard2</code>
<code>port=</code><code>22002</code>
<code>dbpath=/usr/local/mongodb/shard2/data</code>
<code>logpath=/usr/local/mongodb/shard2/log/shard2.log</code>
<code>cat /usr/local/mongodb/config/mongod-shardsvr3.conf</code>
<code>replSet=shard3</code>
<code>port=</code><code>22003</code>
<code>dbpath=/usr/local/mongodb/shard3/data</code>
<code>logpath=/usr/local/mongodb/shard3/log/shard3.log</code>
7,打包mongodb目录,拷贝到另外两台服务器
<code>tar zcvf /mnt/mongodb.tar.gz /usr/local/mongodb</code>
<code>scp mongodb.tar.gz root</code><code>@10</code><code>.19.</code><code>21.242</code><code>:/mnt/</code>
<code>scp mongodb.tar.gz root</code><code>@10</code><code>.19.</code><code>21.243</code><code>:/mnt/</code>
被拷贝的两台服务器分别解压
<code>tar zxvf /mnt/mongodb.tar.gz -C /</code>
8,分别在每台机器上,启动服务,三台机器必须先全部启动mongod-configsvr.conf
<code>ln -s /usr/local/mongodb/bin/mongo* /usr/bin/mongod</code>
<code>mongod -f /usr/local/mongodb/config/mongod-configsvr.conf</code>
<code>mongos -f /usr/local/mongodb/config/mongod-mongos.conf (上面命令全部执行完再运行,否则报错)</code>
启动各个分片的副本集
<code>mongod -f /usr/local/mongodb/config/mongod-shardsvr1.conf</code>
<code>mongod -f /usr/local/mongodb/config/mongod-shardsvr2.conf</code>
<code>mongod -f /usr/local/mongodb/config</code><code>//mongod-shardsvr3.conf</code>
9,登陆任意一台,连接mongodb
设置第一个分片副本集
<code>[root</code><code>@VM</code><code>-</code><code>241</code> <code>~]# mongo </code><code>127.0</code><code>.</code><code>0.1</code><code>:</code><code>22001</code>
<code>MongoDB shell version: </code><code>3.2</code><code>.</code><code>8</code>
<code>connecting to: </code><code>127.0</code><code>.</code><code>0.1</code><code>:</code><code>22001</code><code>/test</code>
使用admin数据库
<code>mongos> use admin</code>
<code>switched to db admin</code>
定义副本集配置
<code>mongos> config={_id:</code><code>"shard1"</code><code>,members:[{_id:</code><code>0</code><code>,host:</code><code>"10.19.21.241:22001"</code><code>},{_id:</code><code>1</code><code>,host:</code><code>"10.19.21.242:22001"</code><code>},{_id:</code><code>2</code><code>,host:</code><code>"10.19.21.243:22001"</code><code>,arbiterOnly:</code><code>true</code><code>}]}</code>
<code>#####################################</code>
<code>{</code>
<code>"_id"</code> <code>: </code><code>"shard1"</code><code>,</code>
<code>"members"</code> <code>: [</code>
<code>"_id"</code> <code>: </code><code>0</code><code>,</code>
<code>"host"</code> <code>: </code><code>"10.19.21.241:22001"</code>
<code>},</code>
<code>"_id"</code> <code>: </code><code>1</code><code>,</code>
<code>"host"</code> <code>: </code><code>"10.19.21.242:22001"</code>
<code>"_id"</code> <code>: </code><code>2</code><code>,</code>
<code>"host"</code> <code>: </code><code>"10.19.21.243:22001"</code><code>,</code>
<code>"arbiterOnly"</code> <code>: </code><code>true</code>
<code>}</code>
<code>]</code>
初始化副本集配置
> rs.initiate(config);
<code>"info"</code> <code>: </code><code>"Config now saved locally. Should come online in about a minute."</code><code>,</code>
<code>"ok"</code> <code>: </code><code>1</code>
设置第二个分片副本集
<code>[root</code><code>@VM</code><code>-</code><code>241</code> <code>~]# mongo </code><code>127.0</code><code>.</code><code>0.1</code><code>:</code><code>22002</code>
<code>connecting to: </code><code>127.0</code><code>.</code><code>0.1</code><code>:</code><code>22002</code><code>/test</code>
<code>> use admin</code>
<code>> config={_id:</code><code>"shard2"</code><code>,members:[{_id:</code><code>0</code><code>,host:</code><code>"10.19.21.241:22002"</code><code>},{_id:</code><code>1</code><code>,host:</code><code>"10.19.21.242:22002"</code><code>},{_id:</code><code>2</code><code>,host:</code><code>"10.19.21.243:22002"</code><code>,arbiterOnly:</code><code>true</code><code>}]}</code>
<code> </code>
<code>初始化副本集配置</code>
<code>> rs.initiate(config);</code>
设置第三个分片副本集
<code>[root</code><code>@VM</code><code>-</code><code>241</code> <code>~]# mongo </code><code>127.0</code><code>.</code><code>0.1</code><code>:</code><code>22003</code>
<code>connecting to: </code><code>127.0</code><code>.</code><code>0.1</code><code>:</code><code>22003</code><code>/test</code>
<code>> config={_id:</code><code>"shard3"</code><code>,members:[{_id:</code><code>0</code><code>,host:</code><code>"10.19.21.241:22003"</code><code>},{_id:</code><code>1</code><code>,host:</code><code>"10.19.21.242:22003"</code><code>},{_id:</code><code>2</code><code>,host:</code><code>"10.19.21.243:22003"</code><code>,arbiterOnly:</code><code>true</code><code>}]}</code>
10,配置分片
目前搭建的mongodb配置服务器,路由服务器,各个分片服务器,不过应用程序连接到mongs路由服务器并不能使用分片机制,还需要在程序里设置分片配置,让分片生效。
连接mongos
<code>[root</code><code>@VM</code><code>-</code><code>241</code> <code>~]# mongo </code><code>127.0</code><code>.</code><code>0.1</code><code>:</code><code>20000</code>
<code>connecting to: </code><code>127.0</code><code>.</code><code>0.1</code><code>:</code><code>20000</code><code>/test</code>
串联路由服务器与分配副本集1
<code>mongos> db.runCommand({addshard:</code><code>"shard1/10.19.21.241:22001,10.19.21.242:22001,10.19.21.243:22001"</code><code>});</code>
<code>{ </code><code>"shardAdded"</code> <code>: </code><code>"shard1"</code><code>, </code><code>"ok"</code> <code>: </code><code>1</code> <code>}</code>
#如里shard是单台服务器,用 db.runCommand( { addshard : “[: ]” } )这样的命令加入,如果shard是副本集,用db.runCommand( { addshard : “replicaSetName/[:port][,serverhostname2[:port],…]” });这样的格式表示
串联路由服务器与分配副本集2
<code>mongos> db.runCommand({addshard:</code><code>"shard2/10.19.21.241:22002,10.19.21.242:22002,10.19.21.243:22002"</code><code>});</code>
<code>{ </code><code>"shardAdded"</code> <code>: </code><code>"shard2"</code><code>, </code><code>"ok"</code> <code>: </code><code>1</code> <code>}</code>
串联路由服务器与分配副本集3
<code>mongos> db.runCommand({addshard:</code><code>"shard3/10.19.21.241:22003,10.19.21.242:22003,10.19.21.243:22003"</code><code>});</code>
<code>{ </code><code>"shardAdded"</code> <code>: </code><code>"shard3"</code><code>, </code><code>"ok"</code> <code>: </code><code>1</code> <code>}</code>
查看分片服务器配置
<code>mongos> db.runCommand({listshards:</code><code>1</code><code>})</code>
<code>"shards"</code> <code>: [</code>
<code>"host"</code> <code>: </code><code>"shard1/10.19.21.241:22001,10.19.21.242:22001"</code>
<code>"_id"</code> <code>: </code><code>"shard2"</code><code>,</code>
<code>"host"</code> <code>: </code><code>"shard2/10.19.21.241:22002,10.19.21.242:22002"</code>
<code>"_id"</code> <code>: </code><code>"shard3"</code><code>,</code>
<code>"host"</code> <code>: </code><code>"shard3/10.19.21.241:22003,10.19.21.242:22003"</code>
<code>],</code>
由于10.19.21.243是每个分片副本集的仲裁节点,所以上面没有列出
11,目前配置服务、路由服务、分片服务、副本集服务都已经串联起来了,但我们的目的是希望插入数据,数据能够自动分片,连接在mongos上,准备让指定的数据库、指定的集合分片生效
指定数据库里面的索引
使用table1中的id字段的hash作为索引来分片,需要进入数据库中去建立
<code>db.table1.ensureIndex({id:</code><code>"hashed"</code><code>});</code>
<code>db.table1.ensureIndex({id:</code><code>1</code><code>},{unique:</code><code>true</code><code>});</code>
指定数据库testdb使分片生效,在admin库中执行以下命令
<code>mongos> db.runCommand({enablesharding:</code><code>"testdb"</code><code>});</code>
<code>{ </code><code>"ok"</code> <code>: </code><code>1</code> <code>}</code>
<code>或是</code>
<code>mongos> sh.enableSharding(</code><code>'testdb'</code><code>);</code>
指定数据库里需要分片的集合和片键
<code>mongos> db.runCommand({shardcollection:</code><code>"testdb.table1"</code><code>,key:{id:</code><code>"hashed"</code><code>}})</code>
<code>{ </code><code>"collectionsharded"</code> <code>: </code><code>"testdb.table1"</code><code>, </code><code>"ok"</code> <code>: </code><code>1</code> <code>}</code>
<code>mongos> sh.shardCollection(</code><code>'testdb.table1'</code><code>,{id:</code><code>'hashed'</code><code>})</code>
我们设置testdb的 table1 表需要分片,根据 id 自动分片到 shard1 ,shard2,shard3 上面去。要这样设置是因为不是所有mongodb 的数据库和表 都需要分片!
<code>mongos> use testdb</code>
<code>switched to db testdb</code>
插入测试数据
<code>for</code> <code>(var i=</code><code>1</code><code>;i<=</code><code>100000</code><code>;i++)db.table1.save({id:i,</code><code>"test1"</code><code>:</code><code>"testvar1"</code><code>});</code>
<code>WriteResult({ </code><code>"nInserted"</code> <code>: </code><code>1</code> <code>})</code>
查看分片情况如下:
<code>mongos> db.table1.stats();</code>
<code>"sharded"</code> <code>: </code><code>true</code><code>,</code>
<code>"capped"</code> <code>: </code><code>false</code><code>,</code>
<code>"ns"</code> <code>: </code><code>"testdb.table1"</code><code>,</code>
<code>"count"</code> <code>: </code><code>100000</code><code>,</code>
<code>"size"</code> <code>: </code><code>5400000</code><code>,</code>
<code>"storageSize"</code> <code>: </code><code>1822720</code><code>,</code>
<code>"totalIndexSize"</code> <code>: </code><code>3674112</code><code>,</code>
<code>"indexSizes"</code> <code>: {</code>
<code>"_id_"</code> <code>: </code><code>991232</code><code>,</code>
<code>"id_hashed"</code> <code>: </code><code>2682880</code>
<code>"avgObjSize"</code> <code>: </code><code>54</code><code>,</code>
<code>"nindexes"</code> <code>: </code><code>2</code><code>,</code>
<code>"nchunks"</code> <code>: </code><code>6</code><code>,</code>
<code>"shards"</code> <code>: {</code>
<code>"shard1"</code> <code>: {</code>
<code>"count"</code> <code>: </code><code>33755</code><code>,</code>
<code>"size"</code> <code>: </code><code>1822770</code><code>,</code>
<code>"storageSize"</code> <code>: </code><code>614400</code><code>,</code>
<code>"wiredTiger"</code> <code>: {</code>
<code>"metadata"</code> <code>: {</code>
<code>"formatVersion"</code> <code>: </code><code>1</code>
<code>......</code>
<code>"totalIndexSize"</code> <code>: </code><code>1224704</code><code>,</code>
<code>"_id_"</code> <code>: </code><code>335872</code><code>,</code>
<code>"id_hashed"</code> <code>: </code><code>888832</code>
<code>"shard2"</code> <code>: {</code>
<code>"count"</code> <code>: </code><code>33143</code><code>,</code>
<code>"size"</code> <code>: </code><code>1789722</code><code>,</code>
<code>"storageSize"</code> <code>: </code><code>606208</code><code>,</code>
<code>"totalIndexSize"</code> <code>: </code><code>1196032</code><code>,</code>
<code>"_id_"</code> <code>: </code><code>327680</code><code>,</code>
<code>"id_hashed"</code> <code>: </code><code>868352</code>
<code>"shard3"</code> <code>: {</code>
<code>"count"</code> <code>: </code><code>33102</code><code>,</code>
<code>"size"</code> <code>: </code><code>1787508</code><code>,</code>
<code>"storageSize"</code> <code>: </code><code>602112</code><code>,</code>
<code>"totalIndexSize"</code> <code>: </code><code>1253376</code><code>,</code>
<code>"id_hashed"</code> <code>: </code><code>925696</code>
可以看到数据分到3个分片,shard1:33755 shard2:33143 shard3:33102
----------------------------------------------------------------------------------------------
java程序调用分片集群,因为我们配置了三个mongos作为入口,就算其中哪个入口挂掉了都没关系,使用集群客户端程序如下:
<code>public</code> <code>class</code> <code>TestMongoDBShards {</code>
<code> </code><code>public</code> <code>static</code> <code>void</code> <code>main(String[] args) {</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>List<ServerAddress> addresses = </code><code>new</code> <code>ArrayList<ServerAddress>();</code>
<code> </code><code>ServerAddress address1 = </code><code>new</code> <code>ServerAddress(</code><code>"10.19.21.241"</code> <code>, </code><code>20000</code><code>);</code>
<code> </code><code>ServerAddress address2 = </code><code>new</code> <code>ServerAddress(</code><code>"10.19.21.242"</code> <code>, </code><code>20000</code><code>);</code>
<code> </code><code>ServerAddress address3 = </code><code>new</code> <code>ServerAddress(</code><code>"10.19.21.243"</code> <code>, </code><code>20000</code><code>);</code>
<code> </code><code>addresses.add(address1);</code>
<code> </code><code>addresses.add(address2);</code>
<code> </code><code>addresses.add(address3);</code>
<code> </code><code>MongoClient client = </code><code>new</code> <code>MongoClient(addresses);</code>
<code> </code><code>DB db = client.getDB( </code><code>"testdb"</code> <code>);</code>
<code> </code><code>DBCollection coll = db.getCollection( </code><code>"table1"</code> <code>);</code>
<code> </code><code>BasicDBObject object = </code><code>new</code> <code>BasicDBObject();</code>
<code> </code><code>object.append( </code><code>"id"</code> <code>, </code><code>1</code><code>);</code>
<code> </code><code>DBObject dbObject = coll.findOne(object);</code>
<code> </code><code>System. out .println(dbObject);</code>
<code> </code><code>} </code><code>catch</code> <code>(Exception e) {</code>
<code> </code><code>e.printStackTrace();</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
整个分片集群搭建完了,思考一下我们这个架构是不是足够好呢?其实还有很多地方需要优化,比如我们把所有的仲裁节点放在一台机器,其余两台机器承担了全部读写操作,但是作为仲裁的192.168.0.138相当空闲。让机器3 192.168.0.138多分担点责任吧!架构可以这样调整,把机器的负载分的更加均衡一点,每个机器既可以作为主节点、副本节点、仲裁节点,这样压力就会均衡很多了,如图:
当然生产环境的数据远远大于当前的测试数据,大规模数据应用情况下我们不可能把全部的节点像这样部署,硬件瓶颈是硬伤,只能扩展机器。要用好mongodb还有很多机制需要调整,不过通过这个东东我们可以快速实现高可用性、高扩展性,所以它还是一个非常不错的Nosql组件。
再看看我们使用的mongodb java 驱动客户端 MongoClient(addresses),这个可以传入多个mongos 的地址作为mongodb集群的入口,并且可以实现自动故障转移,但是负载均衡做的好不好呢?打开源代码查看:
<a href="http://s3.51cto.com/wyfs02/M00/86/9B/wKiom1fFK2KDiWHnAAAXWv5WbtE284.png" target="_blank"></a>
它的机制是选择一个ping 最快的机器来作为所有请求的入口,如果这台机器挂掉会使用下一台机器。那这样。。。。肯定是不行的!万一出现双十一这样的情况所有请求集中发送到这一台机器,这台机器很有可能挂掉。一但挂掉了,按照它的机制会转移请求到下台机器,但是这个压力总量还是没有减少啊!下一台还是可能崩溃,所以这个架构还有漏洞!不过这个文章已经太长了,后续解决吧。
参考:http://blog.jobbole.com/72643/
本文转自奔跑在路上博客51CTO博客,原文链接http://blog.51cto.com/qiangsh/1844023如需转载请自行联系原作者
qianghong000