Johannes Brandstettercs目前是omSysto GmbH开发运营总监,曾担任1&1 IT经理;致力于MongoDB、Hadoop、AWS上的项目研究。
纵观最领先的应用程序建模和基础设施最佳实现的设计,很容易的发现一个问题:如果需要性能的快速增长并适应从0到千万请求的自由缩放,只有一条路可走 —— 横向扩展(Scale Out)!
扩展通常是描述给系统添加更多资源的方法。需要从两个方面区别:
纵向扩展(Scale vertically/up)
纵向扩展意味着给系统中单个节点增加资源,代表性的添加有单计算机的cpu或者内存提升。
横向扩展(Scale horizontally/out)
横向扩展意味着增加系统中节点的数目,比如说给一个分布式软件应用程序中添加计算机。举个简单的例子,将Web服务器系统中的服务器从1个提升到3个。
使用MongoDB,你可以根据你的需求从两个方面去扩展:专注读操作或是写操作的提升。
10gen告诉了我们一些扩展选项:
“自带横向扩展能力,MongoDB允许用户快速的建立和提高他们的应用程序。通过自动分片,你可以轻松的将数据分配到不同的节点上。副本集提供高可靠性,跨数据中心实现节点故障自动转移和恢复。”
听起来不错,事实上呢?现在开始着手把2400万条数据导入由6个节点组成的MongoDB集群,这里使用的是自动分片:
从上图看结果并不是很好,呈现出很不均匀的写入性能。假专注于单个节点的性能,情况比上面看起来更加的糟糕:
分别点击查看大图
这里不仅要看到所有节点上不均匀的写入速度,还要看到节点间每秒写入操作的巨大差别。有一瞬间Node2达到 4000 insert/s的,而那个时刻node5只有 740 insert/s !
为什么会这样?我们使用mongoimport监视了整个导入过程发现:开始的一段时间内一切运行的很好,我们的集群也提供了一个很好的吞吐量;但是随着时间的推移,速度在逐渐的减缓。而通过mongostat,我们不难发现:有时候几秒内许多节点并没有分配到任何数据。
想了解原因,我们必须要看一下MongoDB的分片机制。这里不得不看一下3个基本组件:
1. MongoDB Sharding Router(mongos)
所有通往集群的连接都由mongos选择,分片对应用层来说是完全透明的 —— 通过Transparent Query Routing进行路由。
2. Mongo Config Server
决定元数据中某个部分该分配到哪个对应的节点上。
3. Mongo Shard Node
用来支撑数据的普通mongod进程。
那么要把数据储存到这样的集群中,你必须去做元数据查询来核对当前的数据该写到哪个节点上。然而即使每次写入前去决定目标节点会产生一定的开销,这里的性能损失也不该来的如此之大。
但是现实就是这么残酷!这里要提起的就是Mongo Config Server,也就是必须要告诉MongoDB哪个范围的数据需要被用来分片。这就是传说中的“shard key”!同样如果你在shard key上做了个错误的决定,将会引起一系列的麻烦。当然为了简洁明了,这里选择一个我们认为很适合我们数据类型的shard key。
那么MongoDB现在做的就是在内部把数据放入所谓的“chunk” —— 很像固定大小的data bucket。这样一来每个chunk中都包含了一定范围的数据并且只是一定容量的数据(默认64MB)。当我们把数据导入数据库,这些chunk将会被注入数据,一旦数据装满就会被MongoDB分割。这样一来必定会发生下面两种情况:
- 新的元数据必须被写入配置好的服务器
- Balancer必须考虑某个chunk是否要转到另一个节点上
现在我们就有必要关注一下第二点了!因为我们并没有告诉MongoDB任何shard key相关或者是怎么分布的,MongoDB在分片前则必须进行最好的预估然后尝试将数据平均的分配到分片中。它通过在任何给定时间内保持所有节点上chunk数量相同这一策略来保障数据的平均分配。在MongoDB 2.2中,新迁移阈值被引进并成功的将平衡集群带来的影响降到最低。然而Mongo同样不知道shard key的范围和分布,那么就很可能发生热点区域 —— 很大一部分的数据被写入了同一个分片,因此也只在同一个节点上。
既然我们知道了问题的所在,我们是否能够做点什么?
数据预分割
想做数据预分割必须对你将要导入的数据有充分的认识,所以取代让MongoDB完成chunk的选择你需要亲自动手。取决于你shard key的选择,你既可以使用一个脚本生成相关的命令(一个不错的用例)也可以使用MongoDB文档提供的方法。无论如何你都将完成对MongoDB chunk分配位置的告知:
- db.runCommand({split:"mydb.mycollection",middle:{shardkey:value}});
当然你还可以强制MongoDB给chunk分配相应的节点。同样这取决于你的用例及应用程序对数据的读取方式。这里需要注意的是保留数据的局部性。
增加chunk大小
文档中有具体的chunk大小说明:
1. 小的chunk以频繁迁移为代价获取数据更均匀的分布,这样会给Query Routing Layer增加更多的开销。
2. 大的chunk会显著的减少迁移,不管是从网络设计还是从内部Query Routing Layer开销上都会带来收益。当然这些收益是以潜在的数据分布不均为代价。
而为了能让MongoDB能运行的足够快,这里把chunk的大小提升为1GB。当然这是建立在对自己数据的了解上,所以务必让chunk的大小来的更有价值。
关闭Balancer
既然我们已经给chunk指定了具体节点,并且不希望在这过程中有新chunk的建立;我们可以使用以下的命令来关闭Balancer:
1.
- useconfig
2.
- db.settings.update({_id:"balancer"},{$set:{stopped:true}},true);
选择正确的shard key
Shard key是节点分布的核心。详情点击这里获得shard key相关的建议。在MongoDB 2.4中还支持MongoDB自主选择哈希shard key的选项。
通过这些努力,导入情况如下:
上图为集群中一个节点上的速率。当然还是有一些缝隙,因为数据不是绝对均与的进入。但总的来这是一个很大的提升,整个集群上的导入速度达到每秒4608次插入;现在导入速度只受到节点间网络接口的限制。
总结
天下没有免费的午餐!想获得卓越的性能,就必须亲力亲为。
原文链接:Scaling MongoDB – Know your Sharding Kung Fu (编译/仲浩 王旭东/审校)
欢迎关注@CSDN云计算微博,了解更多云信息。
本文为CSDN编译整理,未经允许不得转载。如需转载请联系market@csdn.net