本文共 12687 字,大约阅读时间需要 42 分钟。
文章转自:http://www.lanceyan.com/category/tech/mongodb
在系统早期,数据量还小的时候不会引起太大的问题,但是随着数据量持续增多,后续迟早会出现一台机器硬件瓶颈问题的。而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 2 | #存放mongodb数据文件 mkdir -p /data/mongodbtest |
1 2 | #进入mongodb文件夹 cd /data/mongodbtest |
1 | wget http: //fastdl .mongodb.org /linux/mongodb-linux-x86_64-2 .4.8.tgz |
1 2 | #解压下载的压缩包 tar xvzf mongodb-linux-x86_64-2.4.8.tgz |
1 2 | #建立mongos目录 mkdir -p /data/mongodbtest/mongos/log |
1 2 | #建立config server 数据文件存放目录 mkdir -p /data/mongodbtest/config/data |
1 2 | #建立config server 日志文件存放目录 mkdir -p /data/mongodbtest/config/log |
1 2 | #建立config server 日志文件存放目录 mkdir -p /data/mongodbtest/mongos/log |
1 2 | #建立shard1 数据文件存放目录 mkdir -p /data/mongodbtest/shard1/data |
1 2 | #建立shard1 日志文件存放目录 mkdir -p /data/mongodbtest/shard1/log |
1 2 | #建立shard2 数据文件存放目录 mkdir -p /data/mongodbtest/shard2/data |
1 2 | #建立shard2 日志文件存放目录 mkdir -p /data/mongodbtest/shard2/log |
1 2 | #建立shard3 数据文件存放目录 mkdir -p /data/mongodbtest/shard3/data |
1 2 | #建立shard3 日志文件存放目录 mkdir -p /data/mongodbtest/shard3/log |
1 | /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongod --configsvr --dbpath /data/mongodbtest/config/data --port 21000 --logpath /data/mongodbtest/config/log/config .log --fork |
1 | /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongos --configdb 192.168.0.136:21000,192.168.0.137:21000,192.168.0.138:21000 --port 20000 --logpath /data/mongodbtest/mongos/log/mongos .log --fork |
1 2 | #在每个机器里分别设置分片1服务器及副本集shard1 /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongod --shardsvr --replSet shard1 --port 22001 --dbpath /data/mongodbtest/shard1/data --logpath /data/mongodbtest/shard1/log/shard1 .log --fork --nojournal --oplogSize 10 |
为了快速启动并节约测试环境存储空间,这里加上 nojournal 是为了关闭日志信息,在我们的测试环境不需要初始化这么大的redo日志。同样设置 oplogsize是为了降低 local 文件的大小,oplog是一个固定长度的 capped collection,它存在于”local”数据库中,用于记录Replica Sets操作日志。注意,这里的设置是为了测试!
1 2 | #在每个机器里分别设置分片2服务器及副本集shard2 /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongod --shardsvr --replSet shard2 --port 22002 --dbpath /data/mongodbtest/shard2/data --logpath /data/mongodbtest/shard2/log/shard2 .log --fork --nojournal --oplogSize 10 |
1 2 | #在每个机器里分别设置分片3服务器及副本集shard3 /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongod --shardsvr --replSet shard3 --port 22003 --dbpath /data/mongodbtest/shard3/data --logpath /data/mongodbtest/shard3/log/shard3 .log --fork --nojournal --oplogSize 10 |
分别对每个分片配置副本集,深入了解副本集参考本系列前几篇文章。
任意登陆一个机器,比如登陆192.168.0.136,连接mongodb
1 2 | #设置第一个分片副本集 /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongo 127.0.0.1:22001 |
1 2 | #使用admin数据库 use admin |
1 2 3 4 5 6 7 | #定义副本集配置 config = { _id: "shard1" , members:[ {_id:0,host: "192.168.0.136:22001" }, {_id:1,host: "192.168.0.137:22001" }, {_id:2,host: "192.168.0.138:22001" ,arbiterOnly: true } ] } |
1 2 | #初始化副本集配置 rs.initiate(config); |
1 2 | #设置第二个分片副本集 /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongo 127.0.0.1:22002 |
1 2 | #使用admin数据库 use admin |
1 2 3 4 5 6 7 | #定义副本集配置 config = { _id: "shard2" , members:[ {_id:0,host: "192.168.0.136:22002" }, {_id:1,host: "192.168.0.137:22002" }, {_id:2,host: "192.168.0.138:22002" ,arbiterOnly: true } ] } |
1 2 | #初始化副本集配置 rs.initiate(config); |
1 2 | #设置第三个分片副本集 /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongo 127.0.0.1:22003 |
1 2 | #使用admin数据库 use admin |
1 2 3 4 5 6 7 | #定义副本集配置 config = { _id: "shard3" , members:[ {_id:0,host: "192.168.0.136:22003" }, {_id:1,host: "192.168.0.137:22003" }, {_id:2,host: "192.168.0.138:22003" ,arbiterOnly: true } ] } |
1 2 | #初始化副本集配置 rs.initiate(config); |
1 2 | #连接到mongos /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongo 127.0.0.1:20000 |
1 2 | #使用admin数据库 user admin |
1 2 | #串联路由服务器与分配副本集1 db.runCommand( { addshard : "shard1/192.168.0.136:22001,192.168.0.137:22001,192.168.0.138:22001" }); |
如里shard是单台服务器,用 db.runCommand( { addshard : “[:]” } )这样的命令加入,如果shard是副本集,用db.runCommand( { addshard : “replicaSetName/[:port][,serverhostname2[:port],…]” });这样的格式表示 。
1 2 | #串联路由服务器与分配副本集2 db.runCommand( { addshard : "shard2/192.168.0.136:22002,192.168.0.137:22002,192.168.0.138:22002" }); |
1 2 | #串联路由服务器与分配副本集3 db.runCommand( { addshard : "shard3/192.168.0.136:22003,192.168.0.137:22003,192.168.0.138:22003" }); |
1 2 | #查看分片服务器的配置 db.runCommand( { listshards : 1 } ); |
#内容输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | { "shards" : [ { "_id" : "shard1", "host" : "shard1/192.168.0.136:22001,192.168.0.137:22001" }, { "_id" : "shard2", "host" : "shard2/192.168.0.136:22002,192.168.0.137:22002" }, { "_id" : "shard3", "host" : "shard3/192.168.0.136:22003,192.168.0.137:22003" } ], "ok" : 1 } |
因为192.168.0.138是每个分片副本集的仲裁节点,所以在上面结果没有列出来。
连接在mongos上,准备让指定的数据库、指定的集合分片生效。
1 2 | #指定testdb分片生效 db.runCommand( { enablesharding : "testdb" }); |
1 2 | #指定数据库里需要分片的集合和片键 db.runCommand( { shardcollection : "testdb.table1" ,key : { id : 1} } ) |
我们设置testdb的 table1 表需要分片,根据 id 自动分片到 shard1 ,shard2,shard3 上面去。要这样设置是因为不是所有mongodb 的数据库和表 都需要分片!
1 2 | #连接mongos服务器 /data/mongodbtest/mongodb-linux-x86_64-2 .4.8 /bin/mongo 127.0.0.1:20000 |
1 2 | #使用testdb use testdb; |
1 2 3 | #插入测试数据 for (var i = 1; i <= 100000; i++) db.table1.save({ id :i, "test1" : "testval1" }); |
1 2 | #查看分片情况如下,部分无关信息省掉了 db.table1.stats(); |
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 | { "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。已经成功了!不过分的好像不是很均匀,所以这个分片还是很有讲究的,后续再深入讨论。
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 | public class TestMongoDBShards { public static void main(String[] args) { try { List<ServerAddress> addresses = new ArrayList<ServerAddress>(); ServerAddress address1 = new ServerAddress( "192.168.0.136" , 20000 ); ServerAddress address2 = new ServerAddress( "192.168.0.137" , 20000 ); ServerAddress address3 = new ServerAddress( "192.168.0.138" , 20000 ); addresses.add(address1); addresses.add(address2); addresses.add(address3); MongoClient client = new MongoClient(addresses); DB db = client.getDB( "testdb" ); DBCollection coll = db.getCollection( "table1" ); BasicDBObject object = new BasicDBObject(); object.append( "id" , 1 ); DBObject dbObject = coll.findOne(object); System. out .println(dbObject); } catch (Exception e) { e.printStackTrace(); } } } |
整个分片集群搭建完了,思考一下我们这个架构是不是足够好呢?其实还有很多地方需要优化,比如我们把所有的仲裁节点放在一台机器,其余两台机器承担了全部读写操作,但是作为仲裁的192.168.0.138相当空闲。让机器3 192.168.0.138多分担点责任吧!架构可以这样调整,把机器的负载分的更加均衡一点,每个机器既可以作为主节点、副本节点、仲裁节点,这样压力就会均衡很多了,如图:
当然生产环境的数据远远大于当前的测试数据,大规模数据应用情况下我们不可能把全部的节点像这样部署,硬件瓶颈是硬伤,只能扩展机器。要用好mongodb还有很多机制需要调整,不过通过这个东东我们可以快速实现高可用性、高扩展性,所以它还是一个非常不错的Nosql组件。
再看看我们使用的mongodb java 驱动客户端 MongoClient(addresses),这个可以传入多个mongos 的地址作为mongodb集群的入口,并且可以实现自动故障转移,但是负载均衡做的好不好呢?打开源代码查看:
它的机制是选择一个ping 最快的机器来作为所有请求的入口,如果这台机器挂掉会使用下一台机器。那这样。。。。肯定是不行的!万一出现双十一这样的情况所有请求集中发送到这一台机器,这台机器很有可能挂掉。一但挂掉了,按照它的机制会转移请求到下台机器,但是这个压力总量还是没有减少啊!下一台还是可能崩溃,所以这个架构还有漏洞!不过这个文章已经太长了,后续解决吧。
参考:
文章二、转自:http://gong1208.iteye.com/blog/1622078
这是一种将海量的数据水平扩展的数据库集群系统,数据分表存储在sharding的各个节点上,使用者通过简单的配置就可以很方便地构建一个分布式MongoDB集群。
MongoDB 的数据分块称为 chunk。每个 chunk 都是 Collection中一段连续的数据记录,通常最大尺寸是 200MB,超出则生成新的数据块。
要构建一个 MongoDB Sharding Cluster,需要三种角色:
即存储实际数据的分片,每个Shard可以是一个mongod实例,也可以是一组mongod实例构成的Replica Set。为了实现每个Shard内部的auto-failover,MongoDB官方建议每个Shard为一组Replica Set。关于如何安装及搭建replica set请参考我的另一篇文章
为了将一个特定的collection存储在多个shard中,需要为该collection指定一个shard key,例如{age: 1} ,shard key可以决定该条记录属于哪个chunk。Config Servers就是用来存储:所有shard节点的配置信息、每个chunk的shard key范围、chunk在各shard的分布情况、该集群中所有DB和collection的sharding配置信息。
这是一个前端路由,客户端由此接入,然后询问Config Servers需要到哪个Shard上查询或保存记录,再连接相应的Shard进行操作,最后将结果返回给客户端。客户端只需要将原本发给mongod的查询或更新请求原封不动地发给Routing Process,而不必关心所操作的记录存储在哪个Shard上。
下面我们在同一台物理机器上构建一个简单的 Sharding Cluster:
架构图如下:
步骤一:
启动Shard Server
mkdir -p /opt/mongodb/data/shard/s0--创建数据目录
mkdir -p/opt/mongodb/data/shard/s1
mkdir -p/opt/mongodb/data/shard/log --创建日志目录
/Opt/mongodb/bin/mongod--port 27017 --dbpath /opt/mongodb/data/shard/s0 --fork --logpath/opt/mongodb/data/shard/log/s0.log --启动Shard Server实例1
/Opt/mongodb/bin/mongod--port 27018 --dbpath /opt/mongodb/data/shard/s1 --fork --logpath/opt/mongodb/data/shard/log/s1.log --启动Shard Server实例2
步骤二:
启动Config Server
mkdir -p/opt/mongodb/data/shard/config --创建数据目录
/Opt/mongodb/bin/mongod --port 27027 –dbpath/opt/mongodb/data/shard/config --fork --logpath /opt/mongodb/data/shard/log/config.log --启动Config Server实例
(注意,这里我们完全可以像启动普通mongodb服务一样启动,不需要添加—shardsvr和configsvr参数。因为这两个参数的作用就是改变启动端口的,所以我们自行指定了端口就可以)
步骤三:
启动Route Process
/Opt/mongodb/bin/mongos--port 40000 --configdb localhost:27027 --fork --logpath
/opt/mongodb/data/shard/log/route.log--chunkSize 1 --启动RouteServer实例
mongos启动参数中,chunkSize这一项是用来指定chunk的大小的,单位是MB,默认大小为200MB,为了方便测试Sharding效果,我们把chunkSize指定为 1MB。意思是当这个分片中插入的数据大于1M时开始进行数据转移
步骤四:
配置Sharding
接下来,我们使用MongoDB Shell登录到mongos,添加Shard节点
[root@localhost~]# /Opt/mongo/bin/mongo admin --port 40000 --此操作需要连接admin库
MongoDBshell version: 2.0.1
connectingto: 127.0.0.1:40000/admin
>db.runCommand({ addshard:"localhost:27017" }) --添加 Shard Server
{"shardAdded" : "shard0000", "ok" : 1 }
>db.runCommand({ addshard:"localhost:27018" })
{"shardAdded" : "shard0001", "ok" : 1 }
>db.runCommand({ enablesharding:"test" }) --设置分片存储的数据库
{"ok" : 1 }
> db.runCommand({shardcollection: "test.users", key: { id:1 }}) --设置分片的集合名称。且必须指定Shard Key,系统会自动创建索引
{"collectionsharded" : "test.users", "ok" : 1 }
注意这里我们要注意片键的选择,选择片键时需要根据具体业务的数据形态来选择,切不可随意选择,实际中尤其不要轻易选择自增_id作为片键,除非你很清楚你这么做的目的,具体原因我不在此分析,根据经验推荐一种较合理的片键方式,“自增字段+查询字段”,没错,片键可以是多个字段的组合。
另外这里说明一点,分片的基本机制:分片总是试图将现有数据均分到所有的分片上。举例说,现在有两个分片,我已经选择了id作为片键,假定插入的id是自增的,如1——10000,则分片后的结果是均分,即1——5000在片A,5000——10000在片B,当然,不一定有这么精确,但却是保证尽量的平均的,以此类推,如果有三块分片,同样均分三等分。
还需要说明的是,一开始插入数据时,数据是只插入到其中一块分片上的,插入完毕后,mongodb内部开始在各片之间进行数据的移动,这个过程可能不是立即的,mongodb足够智能会根据当前负载决定是立即进行移动还是稍后移动。
在插入数据后,立马执行db.users.stats();两次可以验证如上所说。
Ok,简单的分片就是这么搭建的,连接上mongos,然后开始插入数据进行验证吧。
转载地址:http://eirji.baihongyu.com/