前言:随着用户数量的膨胀,大多数应用的瓶颈往往都会出现在数据库上,MongDB虽然性能极高,但在面对海量数据时,单机版的架构依旧显得无力,垂直扩展成本较高,还好MongDB比较友好,支持横向扩展,MongDB支持数据分片,有用过Mycat的可以很好理解,其内置了类似于Mycat的分库分表的功能,相当于是Mysql+Mycat.网上关于集群搭建的教程也是颇多,但大部分是伪集群,另外MongDB版本迭代极快,如果你使用了最新的版本,比如4.X,而教程往往停留在2.X 3.X这种版本,很可能会有一些未知的坑,让人很难爬出来,浪费不必要的时间,顺便喷一下那些随意转别人博客都不亲自测试下的人,有些简直是误人子弟! 希望本篇可以帮到一些喜欢新版本新技术的人,所有过程本人亲测有效,遇到的所有坑和解决办法也会在文末提到.
相较于Redis集群,Mysql集群,个人感觉MongDB集群搭建起来难度要更大,涉及的知识点和内容也最多.
在正式搭建开始前,先再次梳理下MongDB集群的一些相关内容,这样可以更好的帮助我们搭建和理解MongDB集群.
1.MongDB的三种集群方式
1.1Master/Slave
Master/Slave这种方式基本上不再推荐使用,只能从Master复制数据到Slave,并不提供高可用,一旦Master结点出故障就比较难处理。具体细节就不说了,反正已经不推荐使用。
1.2Replica Set
即常说的复制集,复制集的主要目标有几个:
- 高可用(主要目标):当一个结点故障时自动切换到其他结点;
- 数据冗余(主要目标):数据复制到n个结点上,增加数据安全性,同时为高可用提供基础;
- 功能隔离(次要目标):使用不同的结点隔离某些有特殊需求的功能,比如使用一个结点进行OLAP运算(大规模资源占用),使用一个结点在远程做灾备(性能要求不如本地高),读写分离等等;
这种方式搭建起来相对容易,可靠性等各方面表现都不错,是用的比较多的一种方式.
1.3Sharded Cluster
即分片集,分片集的主要设计目标是:
- 水平扩展:当一台服务器满足不了需求的时候,我们可以选择垂直扩展(增加服务器硬件),它虽然简单,但很容易达到极限,并且面临成本高等明显缺点。成本更低的方式是使用n台服务器组成集群来满足系统需求。这就是分片集的主要设计目标;
- 缩短响应时间:因为可以把数据分散到多台服务器上,自然每台服务器的处理压力减小,处理时间就会缩短;
这里会出现一个问题:假设每台服务器出故障的机率是x%
,那么n
台服务器有一台出现故障的机率就是x% * n
,如果不做高可用设计,集群出现故障的概率就会随机器数量成正比增长,这在工程上是不能接受的。幸运的是我们已经有了解决高可用的方案,也就是复制集。所以MongoDB的分片集群要求每一个片都是复制集(当然测试环境也可以使用单结点,生产环境不推荐).
在面对海量数据时,这种方式表现更加游刃有余.
Replica Set 主要是为了数据的高可用性,有点类似于Mysql的主从复制,而Sharded Cluster就相当于是Mysql的分库分表,为了实现高可用性和高性能,本篇就主要讲复制集+分片集结合的方式,挑战最高难度!
2.MongDB的复制和分片
2.1MongDB的复制成员
前面提到,MongDB的Replica set 复制集 主要由三个成员组成:primary,secondary,arbiter,如图所示:
其中primary是主节点,secondary是从节点,arbiter是仲裁者,一个集群只能有一个主节点,当主节点挂了以后,复制集会自动投票选出一个从节点作为新的主节点,当挂了的主节点恢复正常以后,挂了的主节点会变为从节点加入该集群中,跟zookeeper一样.
仲裁者的主要作用就是投票,它不参与数据的复制等,仅占用系统较少的资源.
当然你也可以采用下面这种没有arbiter仲裁者的集群架构:
当你的系统节点数量为2n+1时,你可以采用这种架构,当然n必须大于1,否则仅有3个节点时,当主挂了剩下2个节点无法投出奇数个票,所以如果你的集群仅有3个节点,是需要仲裁者的,也比较推荐第一种有仲裁者的方式.
2.2MongoDB的分片集群成员
- shard: 分片,一个集合中的文档可能被拆分成若干个块,分别存储在不同服务器的MongoDB中
- mongos: 类似于路由器,将增删改查请求路由至对应的分片上,如果你用过Mycat的话就更好理解了,把它当作Mycat即可.
- config servers: 配置中心,用于存储Mongo的配置文件,当配置发生改变或者有节点宕机后恢复时提供配置信息,有点类似于spring-cloud中的统一配置中心config.
3.集群搭建
3.1环境
本篇演示使用3台服务器搭建的具有2个分片的集群,其中A服务器充当仲裁者.
使用的mongodb版本:
服务器操作系统:centos 7.3
三台服务器Ip:
serverA:192.168.174.129
serverB:192.168.174.132
serverC:192.168.174.133
架构图:
主机设计:
3.2步骤
MongoDB集群分片搭建步骤其实就是2.2中的那张图,搭建路由,搭建配置中心,搭建分片.
在正式搭建前,先做一些准备工作:
①关闭3台服务器上的防火墙(测试环境推荐,简单):
systemctl stop firewalld 关闭防火墙
systemctl status firewalld 查看防火墙状态
生成环境的话建议把我在上面表格里提到的所有端口(20000,27017...)都给打开:
/sbin/iptables -I INPUT -p tcp --dport 20000 -j ACCEPT
iptables-save
...
②另外可以三台服务器互相ping一下,通过这些操作来避免到时候三台服务器无法互通.
③在每台服务器上分别安装MongoDB,安装我就不赘述了,不会的翻我前面的博客.
④在三台服务器上分别创建对应的数据文件夹:
#在serverA上
mkdir -p /data/{shard1-1,shard2-1,config}
#在serverB上
mkdir -p /data/{shard1-2,shard2-2,config}
#在serverC上
mkdir -p /data/{shard1-3,shard2-3,config}
#当然你也可以放在其他路径,在接下来操作中把对应的参数地址改掉即可
在完成以上操作以后,就可以正式进入配置步骤了.
步骤一:配置分片的复制集
①配置shard1的Replica Sets:
#在serverA上启动shard1-1
./mongod --fork --shardsvr --bind_ip 0.0.0.0 --port 27018 --dbpath /data/shard1-1/ --logpath /data/shard1-1/shard1-1.log --logappend --replSet shard1 --directoryperdb
#在serverB上启动shard1-2
./mongod --fork --shardsvr --bind_ip 0.0.0.0 --port 27018 --dbpath /data/shard1-2/ --logpath /data/shard1-2/shard1-2.log --logappend --replSet shard1 --directoryperdb
#在serverC上启动shard1-3
./mongod --fork --shardsvr --bind_ip 0.0.0.0 --port 27018 --dbpath /data/shard1-3/ --logpath /data/shard1-3/shard1-3.log --logappend --replSet shard1 --directoryperdb
#特别注意--bind_ip 0.0.0.0的添加,网上的教程配置不成功大多数是因为没有这句.
用mongo连接其中一台主机的27018端口的mongod,初始化复制集shard1:
比如连接服务器C的27018端口,进入C服务器的mongdb安装目录的bin目录,并连接该端口:
./mongo 127.0.0.1:27018
然后执行下面操作以初始化复制集shard1:
config={ _id:'shard1',members:[{_id:0,host:'192.168.174.133:27018',priority:2},{_id:1,host:'192.168.174.132:27018',priority:1},{_id:2,host:'192.168.174.129:27018',arbiterOnly:true}] }
rs.initiate(config)
正确操作完成后会提示类似下面这种格式信息:
如果出现任何error,均为未完成配置,可以检查自己的参数,ip,端口等有无输错,如果均无法解决,可以参考文末我提出的几种常见的错误和解决办法自行解决.
②配置shard2的Replica Sets:
#在serverA上启动shard2-1
./mongod --fork --shardsvr --bind_ip 0.0.0.0 --port 27019 --dbpath /data/shard2-1/ --logpath /data/shard2-1/shard2-1.log --logappend --replSet shard2 --directoryperdb
#在serverB上启动shard2-2
./mongod --fork --shardsvr --bind_ip 0.0.0.0 --port 27019 --dbpath /data/shard2-2/ --logpath /data/shard2-2/shard2-2.log --logappend --replSet shard2 --directoryperdb
#在serverC上启动shard2-3
./mongod --fork --shardsvr --bind_ip 0.0.0.0 --port 27019 --dbpath /data/shard2-3/ --logpath /data/shard2-3/shard2-3.log --logappend --replSet shard2 --directoryperdb
用mongo连接其中一台主机的27019端口的mongod,初始化复制集shard1:
比如连接服务器B的27019端口,进入B服务器的mongdb安装目录的bin目录,并连接该端口:
./mongo 127.0.0.1:27019
然后执行下面操作以初始化复制集shard2:
config={ _id:'shard2',members:[{_id:0,host:'192.168.174.133:27019',priority:2},{_id:1,host:'192.168.174.132:27019',priority:1},{_id:2,host:'192.168.174.129:27019',arbiterOnly:true}] }
rs.initiate(config)
成功后的提示与shard1类似,不贴图了.
步骤二:配置config server
在3台主机中分别启动配置服务:
./mongod --configsvr --bind_ip 0.0.0.0 --port 20000 --dbpath /data/config/ --logpath /data/config/config.log --replSet docdetection --logappend --fork
用mongo连接其中一台主机的20000端口的mongod,初始化复制集docdetection/
比如我连接A服务器的20000端口,并初始化复制集docdetection,进入A服务器MongoDB安装目录的bin目录并执行:
./mongo 127.0.0.1:20000
连接后执行:
configdb1={ _id:'docdetection',members:[{_id:0,host:'192.168.174.129:20000',priority:3},{_id:1,host:'192.168.174.132:20000',priority:1},{_id:2,host:'192.168.174.133:20000',priority:2}] }
rs.initiate(configdb1)
执行后如果没有报错,说明config server也配置成功了.
步骤三:配置router server
#在3台主机中分别运行mongos服务
./mongos --configdb docdetection/192.168.174.129:20000,192.168.174.132:20000,192.168.174.133:20000 --bind_ip 0.0.0.0 --port 27017 --logpath /data/mongos.log --logappend --fork
步骤四:配置分片(shard cluster)
连接到其中一台机器的端口27017的mongos进程,并切换到admin数据库添加分片shard1和shard2.
这里我以A服务器为例,进入其安装目录的bin目录下,执行: ./mongon 127.0.0.1:20000连接到mongodb
然后并切换到admin数据库添加分片shard1和shard2:
use admin
db.runCommand({addshard:"shard1/192.168.174.129:27018,192.168.174.132:27018,192.168.174.133:27018"})
db.runCommand({addshard:"shard2/192.168.174.129:27019,192.168.174.132:27019,192.168.174.133:27019"})
#激活数据库(work)和集合(status)的分片功能:
db.runCommand({enablesharding:"work"})
db.runCommand({shardcollection:"work.status",key:{_id:1}})
如果以上操作都没有报错,至此一个具有2个复制集和2个分片一个仲裁者的高可用MongDB集群已经配置好了,我们可以使用命令查看一下状态:
pringShardingStatus()
可以看出shards里已经有我们设计的两个分片shard1和shard2了,下面我们进入测试阶段,测试下到底有没有按预想的那样,数据可以分散到这两个分片当中.
3.3测试
#设置分片chunk大小
use config
db.settings.save({ "_id" : "chunksize", "value" : 1 })
设置1M是为了测试,否则要插入大量数据才能分片
#指定test分片生效
sh.enableSharding("test")
#创建索引
use test
db.users.createIndex({user_id : 1})
#指定数据库里需要分片的集合和片键
use admin
sh.shardCollection("test.users", {user_id: 1})
我们设置testdb的 users容器要分片,根据 user_id 自动分片到 shard1 ,shard2 上面去。要这样设置是因为不是所有mongodb 的库和容器都需要分片!
然后我们连接到其中一台服务器的20000端口,测试分片配置结果:
进入A服务器Mongo的安装目录的bin目录并执行: ./mongo 127.0.0.1:20000 连接
use test;
#插入数据
for (var i = 1; i <=1000000; i++){
db.users.save({user_id: i, username: "user"+i});
}
插入过程会有点小卡顿,耐心等待下,如果没有响应的话可以过一会切换到其它服务器的20000端口连接进去查看.
#查看分片情况如下,部分无关信息省掉了
sh.status()
可以看到数据分到2个分片,各自分片chunk 数为: shard1 : 4,shard2 : 3 已经成功了!另外可以看到id从16914-36131的user被分布在了 shard1上,id从36131-54107的user被分布在了shard2上了,数据分布的还是比较均匀的,各项指标均符合预期.
4总结
一套配下来,感觉难度适中,只要不掉到坑里,还算可以接受,美中不足的是,希望Mongo官方在今后的版本里,能够像redis那样给出一些集群的配置脚本或者工具,不然在涉及服务器很多的情况下,很容易因为某些小细节配错导致流程走不通,即便都没有配错一套下来也会比较累.自己写shell脚本的话感觉也省不了太多操作,另外网上的教程良莠不齐,详略不一,谨慎参考,最靠谱的还是官网,如果看不懂可以去Mongo的中文社区看文档,教程结合官方文档基本上可以搞定过程中遇到的所有坑.
另外在正式环境中,可以将 ./mongo --configsvr --bind_ip 0.0.0.0 --port 20000 --dbpath /data/config/ --logpath......这种方式启动的mongo写进配置文件,比如A服务器的replica set的 可以写进 Areplica.yml(3.x以后的版本官方推荐使用yml格式,conf依旧支持),然后在启动./mongo时用 -f +路径/Areplica.yml去指定配置文件即可,这样做的好处就是如果服务器宕了,下次启动时无需再敲一遍了.
5.常见错误和坑
5.1防火墙 端口 bindIp 相关
> rs.initiate(config)
{
"ok" : 0,
"errmsg" : "replSetInitiate quorum check failed because not all proposed set members responded affirmatively: 192.168.xxx.xxx:27018 failed with No route to host, 192.168.xxx.xxx:27018 failed with No route to host",
"code" : 74,
"codeName" : "NodeNotFound"
}
如果你在初始化过程中出现类似上面这种错误,可以尝试关闭防火墙/生成环境下可以打开相应端口,然后将启动mongo时的参数中添加bindIp为0.0.0.0,使任何服务器都能连接到该服务,当然更为安全的做法是bingIp指定为该服务器所要连接到的服务器,比如A服务器和B服务器C服务器有联系,就添加它们2个的IP即可.
5.2初始化副本集报错
> rs.initiate(config)
{
"ok" : 0,
"errmsg" : "This node, 192.168.xxx.xxx:27019, with _id 0 is not electable under the new configuration version 1 for replica set shard2",
"code" : 93,
"codeName" : "InvalidReplicaSetConfig"
}
原因是:如果设置主机A为仲裁节点,那么不要在A主机上执行初始化操作,否则会报上述错误。
5.3启动路由器mongos报错
./mongos --configdb 192.168.xxx.xxx:20000,192.168.xxx.xxx:20000,192.168.xxx.xxx:20000 --port 27017 --logpath /data/mongos.log --logappend --forkFailedToParse: mirrored config server connections are not supported; for config server replica sets be sure to use the replica set connection stringtry './mongos --help' for more information
原因是:从Mongodb3.2之后,启动mongos时需使用副本集名称,否则会报上述错误。例如:
./mongos –configdb 副本集名称/192.168.xxx.xxx:20000,192.168.xxx.xxx:20000,192.168.xxx.xxx:20000 --port 27017 --logpath /data/mongos.log --logappend --fork
eg:./mongos --configdb docdetection/192.168.174.129:20000,192.168.174.132:20000,192.168.174.133:20000 --bind_ip 0.0.0.0 --port 27017 --logpath /data/mongos.log --logappend --fork