- 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
- 数据压力大到机器支撑不了的时候能否做到自动扩展?
在系统早期,数据量还小的时候不会引起太大的问题,但是随着数据量持续增多,后续迟早会出现一台机器硬件瓶颈问题的。而mongodb主打的就是海量数据架构,他不能解决海量数据怎么行!不行!“分片”就用这个来解决这个问题。
传统数据库怎么做海量数据读写?其实一句话概括:分而治之。上图看看就清楚了,如下 taobao岳旭强在infoq中提到的 架构图:
上图中有个TDDL,是taobao的一个数据访问层组件,他主要的作用是SQL解析、路由处理。根据应用的请求的功能解析当前访问的sql判断是在哪个业务数据库、哪个表访问查询并返回数据结果。具体如图:
说了这么多传统数据库的架构,那Nosql怎么去做到了这些呢?mysql要做到自动扩展需要加一个数据访问层用程序去扩展,数据库的增加、删除、备份还需要程序去控制。一但数据库的节点一多,要维护起来也是非常头疼的。不过mongodb所有的这一切通过他自己的内部机制就可以搞定!顿时石化了,这么牛X!还是上图看看mongodb通过哪些机制实现路由、分片:
从图中可以看到有四个组件: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分别设置为: 192.168.100.61、192.168.100.62、192.168.100.63;
- 2、机器绑定域名,l1.spd.com、l2.spd.com、l3.spd.com;
- 2、分别在每台机器上建立mongodb分片对应文件夹。
#存放mongodb数据文件 mkdir -p /home/data ln -s /home/data / mkdir -p /data/mongodb #进入mongodb文件夹 cd /data/mongodb
3、下载mongodb的安装程序包
增加MongoDB yum 源
vim /etc/yum.repos.d/mongodb-org-3.2.repo [mongodb-org-3.2] name=MongoDB Repository baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.2/x86_64/ gpgcheck=0 enabled=1
yum安装MongoDB
sudo yum install -y mongodb-org
禁用SELINUX
vi /etc/selinux/config SELINUX=disabled #检查SELINUX状态 getenforce #临时关闭SELINUX setenforce 0
关闭默认mongodb
systemctl stop mongod
systemctl disable mongod
systemctl disable mongo
/sbin/chkconfig mongod off
设置时间自动同步
#安装ntpdate yum install -y ntpdate #当前时区调整为上海 cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime #ntpdate同步标准时间 ntpdate us.pool.ntp.org #同步bios时间 clock -w #加入定时计划任务,每隔10分钟同步一下时钟 crontab -e 0-59/10 * * * * /usr/sbin/ntpdate us.pool.ntp.org | logger -t NTP
4、分别在每台机器建立mongos 、config 、 shard1 、shard2、shard3 五个目录。
因为mongos不存储数据,只需要建立日志文件目录即可。
#建立mongos目录 mkdir -p /data/mongodb/mongos/log #建立config server 数据文件存放目录 mkdir -p /data/mongodb/config/data #建立config server 日志文件存放目录 mkdir -p /data/mongodb/config/log #建立config server 日志文件存放目录 mkdir -p /data/mongodb/mongos/log #建立shard1 数据文件存放目录 mkdir -p /data/mongodb/shard1/data #建立shard1 日志文件存放目录 mkdir -p /data/mongodb/shard1/log #建立shard2 数据文件存放目录 mkdir -p /data/mongodb/shard2/data #建立shard2 日志文件存放目录 mkdir -p /data/mongodb/shard2/log #建立shard3 数据文件存放目录 mkdir -p /data/mongodb/shard3/data #建立shard3 日志文件存放目录 mkdir -p /data/mongodb/shard3/log
5、规划5个组件对应的端口号,由于一个机器需要同时部署 mongos、config server 、shard1、shard2、shard3,所以需要用端口进行区分。
6、配置各个分片的副本集。
#在每个机器里分别设置分片1服务器及副本集shard1 mongod --shardsvr --replSet shard1 --port 27017 --dbpath /data/mongodb/shard1/data --logpath /data/mongodb/shard1/log/shard1.log --logappend --directoryperdb --fork #在每个机器里分别设置分片2服务器及副本集shard2 mongod --shardsvr --replSet shard2 --port 27018 --dbpath /data/mongodb/shard2/data --logpath /data/mongodb/shard1/log/shard2.log --logappend --directoryperdb --fork #在每个机器里分别设置分片3服务器及副本集shard3 mongod --shardsvr --replSet shard3 --port 27019 --dbpath /data/mongodb/shard3/data --logpath /data/mongodb/shard1/log/shard3.log --logappend --directoryperdb --fork
分别对每个分片配置副本集,深入了解副本集。
任意登陆一个机器,比如登陆192.168.100.61,连接mongodb
#建议在每个副本集主节点上执行(在仲裁节点上执行会报错) #设置第一个分片副本集 mongo --port 27017 #定义副本集配置 config = {_id:'shard1',members:[{_id:0,host:'l1.spd.com:27017',arbiterOnly:true},{_id:1,host:'l2.spd.com:27017'},{_id:2,host:'l3.spd.com:27017'}]} #初始化副本集配置 rs.initiate(config); #设置第二个分片副本集 mongo --port 27018 #定义副本集配置 config = {_id:'shard2',members:[{_id:0,host:'l1.spd.com:27018'},{_id:1,host:'l2.spd.com:27018',arbiterOnly:true},{_id:2,host:'l3.spd.com:27018'}]} #初始化副本集配置 rs.initiate(config); #设置第三个分片副本集 mongo --port 27019 #定义副本集配置 config = {_id:'shard3',members:[{_id:0,host:'l1.spd.com:27019'},{_id:1,host:'l2.spd.com:27019'},{_id:2,host:'l3.spd.com:27019',arbiterOnly:true}]} #初始化副本集配置 rs.initiate(config); #以下为 Mongodb 后期调整命令 #查看集群节点的状态 rs.status(); #增加副本集节点 rs.add("l4.spd.com:27017"); #删除副本集节点 rs.remove("l4.spd.com:27017"); #增加副本集中裁节点 rs.addArb("l4.spd.com:27017");
7、在每一台服务器分别启动配置服务器。
mongod --configsvr --dbpath /data/mongodb/config/data --port 20000 --logpath /data/mongodb/config/log/config.log --logappend --fork
8、在每一台服务器分别启动mongos服务器。
mongos --configdb l1.spd.com:20000,l2.spd.com:20000,l3.spd.com:20000 --port 30000 --logpath /data/mongodb/mongos/log/mongos.log --logappend --fork
注:需要检查主机时间是否一致,建议安装NTP定时同步时间。
#连接到mongos mongo --port 30000 #使用admin数据库 use admin #串联路由服务器与分配副本集1 db.runCommand( { addshard : "shard1/l1.spd.com:27017,l2.spd.com:27017,l3.spd.com:27017"});
如里shard是单台服务器,用 db.runCommand( { addshard : “[: ]” } )这样的命令加入,如果shard是副本集,用db.runCommand( { addshard : “replicaSetName/[:port][,serverhostname2[:port],…]” });这样的格式表示 。
#串联路由服务器与分配副本集2 db.runCommand( { addshard : "shard2/l1.spd.com:27018,l2.spd.com:27018,l3.spd.com:27018"}); #串联路由服务器与分配副本集3 db.runCommand( { addshard : "shard3/l1.spd.com:27019,l2.spd.com:27019,l3.spd.com:27019"}); #查看分片服务器的配置 db.runCommand( { listshards : 1 } );
{ "shards" : [ { "_id" : "shard1", "host" : "shard1/l1.spd.com:27017,l2.spd.com:27017,l3.spd.com:27017" }, { "_id" : "shard2", "host" : "shard2/l1.spd.com:27018,l2.spd.com:27018,l3.spd.com:27018" }, { "_id" : "shard3", "host" : "shard3/l1.spd.com:27019,l2.spd.com:27019,l3.spd.com:27019" } ], "ok" : 1 }
- 因为192.168.0.138是每个分片副本集的仲裁节点,所以在上面结果没有列出来。
- 10、目前配置服务、路由服务、分片服务、副本集服务都已经串联起来了,但我们的目的是希望插入数据,数据能够自动分片,就差那么一点点,一点点。。。连接在mongos上,准备让指定的数据库、指定的集合分片生效。
#指定testdb分片生效 db.runCommand( { enablesharding :"testdb"}); db.runCommand( { shardcollection : "testdb.table1",key : {id: 1} } ) #指定数据库里需要分片的集合和片键
- 我们设置testdb的 table1 表需要分片,根据 id 自动分片到 shard1 ,shard2,shard3 上面去。要这样设置是因为不是所有mongodb 的数据库和表 都需要分片!
- 11、测试分片配置结果。
-
mongo 127.0.0.1:30000 #连接mongos服务器 use testdb; #使用testdb #插入测试数据 for (var i = 1; i <= 100000; i++) db.table1.save({id:i,"test1":"testval1"}); db.table1.stats(); #查看分片情况如下,部分无关信息省掉了
{ "sharded": true, "ns": "testdb.table1", "count": 100000, "numExtents": 13, "size": 5600000, "storageSize": 22372352, "totalIndexSize": 6213760, "indexSizes": { "_id_": 3335808, "id_1": 2877952 }, "avgObjSize": 56, "nindexes": 2, "nchunks": 3, "shards": { "shard1": { "ns": "testdb.table1", "count": 42183, "size": 0, ..."ok": 1 }, "shard2": { "ns": "testdb.table1", "count": 38937, "size": 2180472, ..."ok": 1 }, "shard3": { "ns": "testdb.table1", "count": 18880, "size": 3419528, ..."ok": 1 } }, "ok": 1 }
- 可以看到数据分到3个分片,各自分片数量为: shard1 “count” : 42183,shard2 “count” : 38937,shard3 “count” : 18880。已经成功了!不过分的好像不是很均匀,所以这个分片还是很有讲究的,后续再深入讨论。
- 12、java程序调用分片集群,因为我们配置了三个mongos作为入口,就算其中哪个入口挂掉了都没关系,使用集群客户端程序如下:
publicclassTestMongoDBShards {
publicstaticvoidmain(String[] args) {
try{
List addresses = newArrayList ();
ServerAddress address1 = newServerAddress("192.168.0.136", 20000);
ServerAddress address2 = newServerAddress("192.168.0.137", 20000);
ServerAddress address3 = newServerAddress("192.168.0.138", 20000);
addresses.add(address1);
addresses.add(address2);
addresses.add(address3);
MongoClient client = newMongoClient(addresses);
DB db = client.getDB( "testdb");
DBCollection coll = db.getCollection( "table1");
BasicDBObject object = newBasicDBObject();
object.append( "id", 1);
DBObject dbObject = coll.findOne(object);
System. out .println(dbObject);
} catch(Exception e) {
e.printStackTrace();
}
}
}
当然生产环境的数据远远大于当前的测试数据,大规模数据应用情况下我们不可能把全部的节点像这样部署,硬件瓶颈是硬伤,只能扩展机器。要用好mongodb还有很多机制需要调整,不过通过这个东东我们可以快速实现高可用性、高扩展性,所以它还是一个非常不错的Nosql组件。
再看看我们使用的mongodb java 驱动客户端 MongoClient(addresses),这个可以传入多个mongos 的地址作为mongodb集群的入口,并且可以实现自动故障转移,但是负载均衡做的好不好呢?打开源代码查看:
它的机制是选择一个ping 最快的机器来作为所有请求的入口,如果这台机器挂掉会使用下一台机器。那这样。。。。肯定是不行的!万一出现双十一这样的情况所有请求集中发送到这一台机器,这台机器很有可能挂掉。一但挂掉了,按照它的机制会转移请求到下台机器,但是这个压力总量还是没有减少啊!下一台还是可能崩溃,所以这个架构还有漏洞!不过这个文章已经太长了,后续解决吧。
允许hugepage可以动态分配,而不是系统启动时预先分配
chmod +x /etc/rc.d/rc.local vi /etc/rc.d/rc.local echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never> /sys/kernel/mm/transparent_hugepage/defrag
相关脚本
完整启动脚本
#!/bin/sh #Martin 2016-01-30 echo Delete lock file ... rm -f /data/mongodb/shard1/data/mongod.lock rm -f /data/mongodb/shard2/data/mongod.lock rm -f /data/mongodb/shard3/data/mongod.lock echo Kill MongDB ... echo pkill -2 mongod echo echo Repair MongoDB ... numactl --interleave=all mongod --shardsvr --replSet shard1 --port 27017 --dbpath /data/mongodb/shard1/data --logpath /data/mongodb/shard1/log/shard1.log --logappend --directoryperdb --fork --repair numactl --interleave=all mongod --shardsvr --replSet shard2 --port 27018 --dbpath /data/mongodb/shard2/data --logpath /data/mongodb/shard2/log/shard2.log --logappend --directoryperdb --fork --repair numactl --interleave=all mongod --shardsvr --replSet shard3 --port 27019 --dbpath /data/mongodb/shard3/data --logpath /data/mongodb/shard3/log/shard3.log --logappend --directoryperdb --fork --repair echo echo Start MongoDB ... numactl --interleave=all mongod --shardsvr --replSet shard1 --port 27017 --dbpath /data/mongodb/shard1/data --logpath /data/mongodb/shard1/log/shard1.log --logappend --directoryperdb --fork numactl --interleave=all mongod --shardsvr --replSet shard2 --port 27018 --dbpath /data/mongodb/shard2/data --logpath /data/mongodb/shard1/log/shard2.log --logappend --directoryperdb --fork numactl --interleave=all mongod --shardsvr --replSet shard3 --port 27019 --dbpath /data/mongodb/shard3/data --logpath /data/mongodb/shard1/log/shard3.log --logappend --directoryperdb --fork echo echo Start MongoDB Config Service ... numactl --interleave=all mongod --configsvr --dbpath /data/mongodb/config/data --port 20000 --logpath /data/mongodb/config/log/config.log --logappend --fork echo echo Start MongoDB Route Service ... numactl --interleave=all mongos --configdb 192.168.100.42:20000,192.168.100.43:20000,192.168.100.44:20000 --port 30000 --logpath /data/mongodb/mongos/log/mongos.log --logappend --fork
滚动日志
#!/bin/env python import sys import os import commands import datetime,time #get mongo pid mongo_pid = commands.getoutput("/sbin/pidof mongod") #print mongo_pid #send Sig to mongo if mongo_pid != '': cmd = "/bin/kill -USR1 %s" %(mongo_pid) #print cmd mongo_rotate = commands.getoutput(cmd) else: print "mongod is not running..." #clean log which > 30 days num = 30 #shard1 log str_now = time.strftime("%Y-%m-%d") dat_now = time.strptime(str_now,"%Y-%m-%d") array_dat_now = datetime.datetime(dat_now[0],dat_now[1],dat_now[2]) lns = commands.getoutput("/bin/ls --full-time ../shard1/log/|awk '{print $6, $9}'") #print lns for ln in lns.split('\n'): ws = ln.split() if len(ws) != 2: continue ws1 = time.strptime(ws[0],"%Y-%m-%d") ws2 = datetime.datetime(ws1[0],ws1[1],ws1[2]) if (array_dat_now - ws2).days > num: v_del = commands.getoutput("/bin/rm -rf ../shard1/log//%s" % (ws[1])) #shard2 log str_now = time.strftime("%Y-%m-%d") dat_now = time.strptime(str_now,"%Y-%m-%d") array_dat_now = datetime.datetime(dat_now[0],dat_now[1],dat_now[2]) lns = commands.getoutput("/bin/ls --full-time ../shard2/log/|awk '{print $6, $9}'") #print lns for ln in lns.split('\n'): ws = ln.split() if len(ws) != 2: continue ws1 = time.strptime(ws[0],"%Y-%m-%d") ws2 = datetime.datetime(ws1[0],ws1[1],ws1[2]) if (array_dat_now - ws2).days > num: v_del = commands.getoutput("/bin/rm -rf ../shard2/log//%s" % (ws[1])) #shard3 log str_now = time.strftime("%Y-%m-%d") dat_now = time.strptime(str_now,"%Y-%m-%d") array_dat_now = datetime.datetime(dat_now[0],dat_now[1],dat_now[2]) lns = commands.getoutput("/bin/ls --full-time ../shard3/log/|awk '{print $6, $9}'") #print lns for ln in lns.split('\n'): ws = ln.split() if len(ws) != 2: continue ws1 = time.strptime(ws[0],"%Y-%m-%d") ws2 = datetime.datetime(ws1[0],ws1[1],ws1[2]) if (array_dat_now - ws2).days > num: v_del = commands.getoutput("/bin/rm -rf ../shard3/log//%s" % (ws[1]))
crontab -e 59 23 * * * /data/mongodb/bin/logRotate.sh
报错处理:
问题1:
> rs.initiate(config) { "ok" : 0, "errmsg" : "This node, 192.168.1.22:27017, with _id 2 is not electable under the new configuration version 1 for replica set Yunnex-MONGODB-TEST", "code" : 93 }
WARNING: /sys/kernel/mm/transparent_hugepage/enabled is ‘always’.We suggest setting it to ‘never’ WARNING: /sys/kernel/mm/transparent_hugepage/defrag is ‘always’.We suggest setting it to ‘never’ WARNING: soft rlimits too low. rlimits set to 1024 processes, 65535 files. Number of processes should be at least 32767.5 : 0.5 times number of files.
vi /etc/rc.d/rc.local echo "never" > /sys/kernel/mm/transparent_hugepage/enabled echo "never" > /sys/kernel/mm/transparent_hugepage/defrag
问题3:
yum install -y numactl #安装numactl 在原启动命令前面加numactl –interleave=all 如# numactl --interleave=all ${MONGODB_HOME}/bin/mongod --config conf/mongodb.conf 修改内核参数 echo 0 > /proc/sys/vm/zone_reclaim_mode
问题4:
解决方法:
可能由于集群机器时间不同步造成。