mongodb性能优化的MapReduce很慢,有没有办法提高性能

本文是一篇转载文章,作者对刚刚发布的1.6版本与上一个稳定版本1.4进行了的,可以看出,除了在功能上有长足的进展外,1.6版本的性能也得到了不小的提升。
原文链接:
日,Mongodb 1.6正式发布了,这个版本增加和改进了很多功能,我了解的几个比较大的改进在:
Mongodb存储文件申请磁盘空间的方式做了改进。在mongodb1.4的时候是按128M,256M,512M,M这样的方式申请磁盘空间的;而在mongodb1.6中,已经是动态小量的申请磁盘空间了。
增加了$or等查询操作符,这在mongodb 1.4的时候是没有的。
改进和提高了并发性能。
Replication的同步方面做了改进。
etc…
详细的changelog可以看:
在我发表这篇文章时,发现Mongodb 1.6.1也已经发布了,主要是修复了一些bug。
居然说性能得到了提高,那么我们就对Mongodb 1.6和Mongodb 1.4分别做了一个性能测试,想对比下看看Mongodb 1.6性能到底比Mongodb 1.4提高了多少。
测试机器为一台普通台式机,安装在64位centos linux 5.4系统。cpu为Intel E7500,内存为2G,单个普通500G硬盘。
测试程序为自己用java写的,可在此下载:
测试程序每次测试都会insert 100万条记录(如10并发测试,每并发insert 10万条记录;20并发测试,每并发5万条记录…),每记录大小为1KB,然后再逐条update所有记录,最后逐条select出来。
测试程序是在本机跑的,所以本次测试忽略网络延时。好,下面我们看测试结果:
下面图中横轴10&#是指并发测试的并发线程。
在insert测试内,Mongodb1.4和Mongodb1.6平分秋色,基本上没有区别。虽然在mongodb 1.6中对申请磁盘空间方式做了改进,但对性能的提升没有体现出来。
随着并发的增加,性能快速下降的问题也没有得到改进。
在update方面,性能提升显著,合计有75%的性能提高。而且表现平稳,随着并发的增加,性能稳定。非常不错。
select方面表现也不错,合计有83%性能提高。在并发线程小时尤其明显。
通过上面几个方面测试的结果表明,Mongodb 1.6是还是非常值得我们升级的,不但增加了一些新功能,性能也得到了很大的提高。MapReduce: 提高MapReduce性能的七点建议 - lskyne的专栏
- 博客频道 - CSDN.NET
Cloudera提供给客户的服务内容之一就是调整和优化MapReducejob执行性能。MapReduce和HDFS组成一个复杂的分布式系统,并且它们运行着各式各样用户的代码,这样导致没有一个快速有效的规则来实现优化代码性能的目的。在我看来,调整cluster或job的运行更像一个医生对待病人一样,找出关键的“症状”,对于不同的症状有不同的诊断和处理方式。
&&&&&在医学领域,没有什么可以代替一位经验丰富的医生;在复杂的分布式系统上,这个道理依然正确—有经验的用户和操作者在面对很多常见问题上都会有“第六感”。我曾经为Cloudera不同行业的客户解决过问题,他们面对的工作量、数据集和cluster硬件有很大区别,因此我在这方面积累了很多的经验,并且想把这些经验分享给诸位。
& &&&&&在这篇blog里,我会高亮那些提高MapReduce性能的建议。前面的一些建议是面向整个cluster的,这可能会对cluster操作者和开发者有帮助。后面一部分建议是为那些用Java编写MapReducejob的开发者而提出。在每一个建议中,我列出一些“症状”或是“诊断测试”来说明一些针对这些问题的改进措施,可能会对你有所帮助。
& &&&&&请注意,这些建议中包含很多我以往从各种不同场景下总结出来的直观经验。它们可能不太适用于你所面对的特殊的工作量、数据集或cluster,如果你想使用它,就需要测试使用前和使用后它在你的cluster环境中的表现。对于这些建议,我会展示一些对比性的数据,数据产生的环境是一个4个节点的cluster来运行40GB的Wordcountjob。应用了我以下所提到的这些建议后,这个job中的每个maptask大概运行33秒,job总共执行了差不多8分30秒。
第一点&&正确地配置你的Cluster
诊断结果/症状:
1. Linux top命令的结果显示slave节点在所有map和reduce slot都有task运行时依然很空闲。
2. top命令显示内核的进程,如RAID(mdX_raid*)或pdflush占去大量的CPU时间。
3. Linux的平均负载通常是系统CPU数量的2倍。
4. 即使系统正在运行job,Linux平均负载总是保持在系统CPU数量的一半的状态。
5. 一些节点上的swap利用率超过几MB
& &优化你的MapReduce性能的第一步是确保你整个cluster的配置文件被调整过。对于新手,请参考这里关于配置参数的一篇blog:配置参数。除了这些配置参数 ,在你想修改job参数以期提高性能时,你应该参照下我这里的一些你应该注意的项:
1.&&确保你正在DFS和MapReduce中使用的存储mount被设置了noatime选项。这项如果设置就不会启动对磁盘访问时间的记录,会显著提高IO的性能。
2. 避免在TashTracker和DataNode的机器上执行RAID和LVM操作,这通常会降低性能
3. 在这两个参数mapred.local.dir和dfs.data.dir配置的值应当是分布在各个磁盘上目录,这样可以充分利用节点的IO读写能力。运行 Linux sysstat包下的iostat -dx5命令可以让每个磁盘都显示它的利用率。
4. 你应该有一个聪明的监控系统来监控磁盘设备的健康状态。MapReducejob的设计是可容忍磁盘失败,但磁盘的异常会导致一些task重复执行而使性能下降。如果你发现在某个TaskTracker被很多job中列入黑名单,那么它就可能有问题。
5.使用像Ganglia这样的工具监控并绘出swap和网络的利用率图。如果你从监控的图看出机器正在使用swap内存,那么减少mapred.child.java.opts属性所表示的内存分配。
基准测试:
& &很遗憾我不能为这个建议去生成一些测试数据,因为这需要构建整个cluster。如果你有相关的经验,请把你的建议及结果附到下面的留言区。
第二点&&使用LZO压缩
诊断结果/症状:
1. 对 job的中间结果数据使用压缩是很好的想法。
2. MapReduce job的输出数据大小是不可忽略的。
3. 在job运行时,通过linux top 和 iostat命令可以看出slave节点的iowait利用率很高。
& & 几乎每个Hadoop job都可以通过对maptask输出的中间数据做LZO压缩获得较好的空间效益。尽管LZO压缩会增加一些CPU的负载,但在shuffle过程中会减少磁盘IO的数据量,总体上总是可以节省时间的。
& &当一个job需要输出大量数据时,应用LZO压缩可以提高输出端的输出性能。这是因为默认情况下每个文件的输出都会保存3个幅本,1GB的输出文件你将要保存3GB的磁盘数据,当采用压缩后当然更能节省空间并提高性能。
& &为了使LZO压缩有效,请设置参数press.map.output值为true。
基准测试:
& &在我的cluster里,Wordcount例子中不使用LZO压缩的话,job的运行时间只是稍微增加。但FILE_BYTES_WRITTEN计数器却从3.5GB增长到9.2GB,这表示压缩会减少62%的磁盘IO。在我的cluster里,每个数据节点上磁盘数量对task数量的比例很高,但Wordcountjob并没有在整个cluster中共享,所以cluster中IO不是瓶颈,磁盘IO增长不会有什么大的问题。但对于磁盘因很多并发活动而受限的环境来说,磁盘IO减少60%可以大幅提高job的执行速度。
第三点&&调整map和reducetask的数量到合适的值
诊断结果/症状:
1. 每个map或reduce task的完成时间少于30到40秒。
2. 大型的job不能完全利用cluster中所有空闲的slot。
3. 大多数map或reduce task被调度执行了,但有一到两个task还在准备状态,在其它task完成之后才单独执行
& & 调整job中map和reducetask的数量是一件很重要且常常被忽略的事情。下面是我在设置这些参数时的一些直观经验:
1.如果每个task的执行时间少于30到40秒,就减少task的数量。Task的创建与调度一般耗费几秒的时间,如果task完成的很快,我们就是在浪费时间。同时,设置JVM重用也可以解决这个问题。
2. 如果一个job的输入数据大于1TB,我们就增加blocksize到256或者512,这样可以减少task的数量。你可以使用这个命令去修改已存在文件的block size: hadoopdistcp -Ddfs.block.size=$[256*]/path/to/inputdata&&/path/to/inputdata-with/largeblocks。在执行完这个命令后,你就可以删除原始的输入文件了(/path/to/inputdata)。
3. 只要每个task运行至少30到40秒,那么就增加map task的数量,增加到整个cluster上mapslot总数的几倍。如果你的cluster中有100个map slot,那就避免运行一个有101个map task的job —如果运行的话,前100个map同时执行,第101个task会在reduce执行之前单独运行。这个建议对于小型cluste和小型job是很重要的。
4. 不要调度太多的reduce task — 对于大多数job来说,我们推荐reducetask的数量应当等于或是略小于cluster中reduce slot的数量。
基准测试:
& & 为了让Wordcountjob有很多的task运行,我设置了如下的参数:Dmapred.max.split.size=$[16*]。以前默认会产生360个maptask,现在就会有2640个。当完成这个设置之后,每个task执行耗费9秒,并且在JobTracker的ClusterSummar视图中可以观看到,正在运行的maptask数量在0到24之间浮动。job在17分52秒之后结束,比原来的执行要慢两倍多。
第四点&&为job添加一个Combiner
诊断结果/症状:
1.job在执行分类的聚合时,REDUCE_INPUT_GROUPS计数器远小于REDUCE_INPUT_RECORDS计数器。
2. job执行一个大的shuffle任务(例如,map的输出数据每个节点就是好几个GB)。
3. 从job计数器中看出,SPILLED_RECORDS远大于MAP_OUTPUT_RECORDS。
& &如果你的算法涉及到一些分类的聚合,那么你就可以使用Combiner来完成数据到达reduce端之前的初始聚合工作。MapReduce框架很明智地运用Combiner来减少写入磁盘以及通过网络传输到reduce端的数据量。
基准测试:
& &我删去Wordcount例子中对setCombinerClass方法的调用。仅这个修改就让maptask的平均运行时间由33秒增长到48秒,shuffle的数据量也从1GB提高到1.4GB。整个job的运行时间由原来的8分30秒变成15分42秒,差不多慢了两倍。这次测试过程中开启了map输出结果的压缩功能,如果没有开启这个压缩功能的话,那么Combiner的影响就会变得更加明显。
第五点&&为你的数据使用最合适和简洁的Writable类型
诊断/症状:
1. Text 对象在非文本或混合数据中使用。
2. 大部分的输出值很小的时候使用IntWritable 或 LongWritable对象。
& &当一个开发者是初次编写MapReduce,或是从开发Hadoop Streaming转到JavaMapReduce,他们会经常在不必要的时候使用Text对象。尽管Text对象使用起来很方便,但它在由数值转换到文本或是由UTF8字符串转换到文本时都是低效的,且会消耗大量的CPU时间。当处理那些非文本的数据时,可以使用二进制的Writable类型,如IntWritable,FloatWritable等。
& &除了避免文件转换的消耗外,二进制Writable类型作为中间结果时会占用更少的空间。当磁盘IO和网络传输成为大型job所遇到的瓶颈时,减少些中间结果的大小可以获得更好的性能。在处理整形数值时,有时使用VIntWritable或VLongWritable类型可能会更快些—这些实现了变长整形编码的类型在序列化小数值时会更节省空间。例如,整数4会被序列化成单字节,而整数10000会被序列化成两个字节。这些变长类型用在统计等任务时更加有效,在这些任务中我们只要确保大部分的记录都是一个很小的值,这样值就可以匹配一或两个字节。
& &如果Hadoop自带的Writable类型不能满足你的需求,你可以开发自己的Writable类型。这应该是挺简单的,可能会在处理文本方面更快些。如果你编写了自己的Writable类型,请务必提供一个RawComparator类—你可以以内置的Writable类型做为例子。
基准测试:
& &对于Wordcount例子,我修改了它在map计数时的中间变量,由IntWritable改为Text。并且在reduce统计最终和时使用Integer.parseString(value.toString)来转换出真正的数值。这个版本比原始版本要慢近10%—整个job完成差不多超过9分钟,且每个maptask要运行36秒,比之前的33秒要慢。尽量看起来整形转换还是挺快的,但这不说明什么情况。在正常情况下,我曾经看到过选用合适的Writable类型可以有2到3倍的性能提升的例子。
第六点&&重用Writable类型
诊断/症状:
1. 在mapred.child.java.opts参数上增加-verbose:gc-XX:+PriintGCDetails,然后查看一些task的日志。如果垃圾回收频繁工作且消耗一些时间,你需要注意那些无用的对象。
2. 在你的代码中搜索&new Text& 或&newIntWritable&。如果它们出现在一个内部循环或是map/reduce方法的内部时,这条建议可能会很有用。
3. 这条建议在task内存受限的情况下特别有用。
& &很多MapReduce用户常犯的一个错误是,在一个map/reduce方法中为每个输出都创建Writable对象。例如,你的Wordcoutmapper方法可能这样写:
public void map(...) {& &
& &&&…& &
& &&&for (String word : words) {&&
& &&&&&&&&&output.collect(newText(word), new IntWritable(1));&&
public void map(...) {
& &&&for (String word : words) {
& &&&&&&&&&output.collect(newText(word), new IntWritable(1));
& &这样会导致程序分配出成千上万个短周期的对象。Java垃圾收集器就要为此做很多的工作。更有效的写法是:
class MyMapper … {& &
& &Text wordText = newText();& &
& &IntWritable one = newIntWritable(1);& &
& &public void map(...){& &
& &&&& &for (Stringword: words) {& &
& &&&&&&&&&wordText.set(word);&&
& &&&&&&&&&output.collect(wordText, one);&&
& &&&& &}& &
& &&& }& &
class MyMapper … {
& &Text wordText = newText();
& &IntWritable one = newIntWritable(1);
& &public void map(...) {
& &&&& &for (Stringword: words) {
& &&&&&&&&&wordText.set(word);
& &&&&&&&&&output.collect(wordText, one);
& &&&& & }
基准测试:
& &当我以上面的描述修改了Wordcount例子后,起初我发现job运行时与修改之前没有任何不同。这是因为在我的cluster中默认为每个task都分配一个1GB的堆大小,所以垃圾回收机制没有启动。当我重新设置参数,为每个task只分配200MB的堆时,没有重用Writable对象的这个版本执行出现了很严重的减缓—job的执行时间由以前的大概8分30秒变成现在的超过17分钟。原始的那个重用Writable的版本,在设置更小的堆时还是保持相同的执行速度。因此重用Writable是一个很简单的问题修正,我推荐大家总是这样做。它可能不会在每个job的执行中获得很好的性能,但当你的task有内存限制时就会有相当大的区别。
第七点&&使用简易的剖析方式查看task的运行
& & 这是我在查看MapReducejob性能问题时常用的一个小技巧。那些不希望这些做的人就会反对说这样是行不通的,但是事实是摆在面前的。
& &为了实现简易的剖析,可以当job中一些task运行很慢时,用ssh工具连接上task所在的机器。执行5到10次这个简单的命令 sudokillall -QUITjava(每次执行间隔几秒)。别担心,不要被命令的名字吓着,它不会导致任何东西退出。然后使用JobTracker的界面跳转到那台机器上某个task的stdout文件上,或者查看正在运行的机器上/var/log/hadoop/userlogs/目录中那个task的stdout文件。你就可以看到当你执行那段命令时,命令发送到JVM的SIGQUIT信号而产生的栈追踪信息的dump文件。
& &解析处理这个输出文件需要一点点以经验,这里我介绍下平时是怎样处理的:
对于栈信息中的每个线程,很快地查找你的java包的名字(假如是com.mycompany.mrjobs)。如果你当前线程的栈信息中没有找到任何与你的代码有关的信息,那么跳到另外的线程再看。
& &如果你在某些栈信息中看到你查找的代码,很快地查阅并大概记下它在做什么事。假如你看到一些与NumberFormat相关的信息,那么此时你需要记下它,暂时不需要关注它是代码的哪些行。
& &转到日志中的下一个dump,然后也花一些时间做类似的事情然后记下些你关注的内容。
& &在查阅了4到5个栈信息后,你可能会意识到在每次查阅时都会有一些似曾相识的东西。如果这些你意识到的问题是阻碍你的程序变快的原因,那么你可能就找到了程序真正的问题。假如你取到10个线程的栈信息,然后从5个里面看到过NumberFormat类似的信息,那么可能意味着你将50%的CPU浪费在数据格式转换的事情上了。
& &当然,这没有你使用真正的分析程序那么科学。但我发现这是一种有效的方法,可以在不需要引入其它工具的时候发现那些明显的CPU瓶颈。更重要的是,这是一种让你会变的更强的技术,你会在实践中知道一个正常的和有问题的dump是啥样子。
& &通过这项技术我发现了一些通常出现在性能调优方面的误解,列出在下面。
1. NumberFormat 相当慢,尽量避免使用它。
2. String.split—不管是编码或是解码UTF8的字符串都是慢的超出你的想像—参照上面提到的建议,使用合适的Writable类型。
3. 直接联接String,而不是使用StringBuffer.append([译]这条与以往的说法貌似不一样呀)。
& &上面只是一些提高MapReduce性能的建议。做基准测试的那些代码我放在了这里:performance blog code
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:307783次
积分:4878
积分:4878
排名:第2365名
原创:145篇
转载:237篇
评论:60条
(1)(1)(21)(22)(33)(2)(24)(12)(37)(38)(55)(35)(27)(5)(33)(33)(3)Mongodb的MapReduce很慢,有没有办法提高性能_百度知道
Mongodb的MapReduce很慢,有没有办法提高性能
提问者采纳
根据测试发现再多个mongod对于速度的影响不升反降,把他们用分片的形式加到集群中就可以了发现了一个简单办法可以大幅提高mongodb mapreduce的速度。这样就相当于多进程操作了,以前我在一台机器上只部署了一个mongod的数据库实例,可以在一台机器上多配几个Mongod数据库实例,起始如果机器配置可以的话。如果机器的cpu是12核的可以起6-8个mongod。改成多进程后。据我分析之前是因为单进程操作,是因为单个cpu达到瓶颈,避免了js单进程的尴尬,达到磁盘i/o瓶颈后速度就没法提升了
其他类似问题
mongodb的相关知识
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁MongoDB MapReduce速度提升20倍优化 - clh604的专栏
- 博客频道 - CSDN.NET
摘要:MongoDB提供的MapReduce非常灵活,对于大规模数据分析也相当实用。尽管MongoDB 2.4中MapReduce有了大幅改进,但是相对来说其性能还是有很大的提升空间。本文就来尝试找出让MapReduce速度最大化提升的方法。
自从MongoDB被越来越多的大型关键项目采用后,数据分析也成为了越来越重要的话题。人们似乎已经厌倦了使用不同的软件来进行分析(这都利用到了Hadoop),因为这些方法往往需要大规模的数据传输,而这些成本相当昂贵。
MongoDB提供了2种方式来对数据进行分析:(以下简称MR)和聚合框架(Aggregation Framework)。MR非常灵活且易于使用,它可以很好地与分片(sharding)结合使用,并允许大规模输出。尽管在MongoDB
v2.4版本中,由于JavaScript引擎从Spider切换到了V8,使得MR的性能有了大幅改进,但是与Agg Framework(使用C++)相比,MR的速度还是显得比较慢。本文就来看看,有哪些方法可以让MR的速度有所提升。
首先我们来做个测试,插入1000万文档,这些文档中包含了介于0和100万之间的单一整数值,这意味着,平均每10个文档具有相同的值。
&&for&(var&i&=&0;&i&&&;&++i){&db.uniques.insert({&dim0:&Math.floor(Math.random()*1000000)&});}&&
&&db.uniques.findOne()&&
{&&_id&&:&ObjectId(&51d3c386acd412e22c188dec&),&&dim0&&:&570859&}&&
&&db.uniques.ensureIndex({dim0:&1})&&
&&db.uniques.stats()&&
&&&&&&&&&ns&&:&&test.uniques&,&&
&&&&&&&&&count&&:&,&&
&&&&&&&&&size&&:&,&&
&&&&&&&&&avgObjSize&&:&36.0000052,&&
&&&&&&&&&storageSize&&:&,&&
&&&&&&&&&numExtents&&:&18,&&
&&&&&&&&&nindexes&&:&2,&&
&&&&&&&&&lastExtentSize&&:&,&&
&&&&&&&&&paddingFactor&&:&1,&&
&&&&&&&&&systemFlags&&:&1,&&
&&&&&&&&&userFlags&&:&0,&&
&&&&&&&&&totalIndexSize&&:&,&&
&&&&&&&&&indexSizes&&:&{&&
&&&&&&&&&&&&&&&&&_id_&&:&,&&
&&&&&&&&&&&&&&&&&dim0_1&&:&&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
这里我们想要得到文档中唯一值的计数,可以通过下面的MR任务来轻松完成:
&&db.runCommand(&&
{&mapreduce:&&uniques&,&&&
map:&function&()&{&emit(this.dim0,&1);&},&&&
reduce:&function&(key,&values)&{&return&Array.sum(values);&},&&&
out:&&mrout&&})&&
&&&&&&&&&result&&:&&mrout&,&&
&&&&&&&&&timeMillis&&:&1161960,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&,&&
&&&&&&&&&&&&&&&&&emit&&:&,&&
&&&&&&&&&&&&&&&&&reduce&&:&1059138,&&
&&&&&&&&&&&&&&&&&output&&:&999961&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
正如你看到的,输出结果大约需要1200秒(在EC2 M3实例上测试),共输出了1千万maps、100万reduces、999961个文档。结果类似于:
&&db.mrout.find()&&
{&&_id&&:&1,&&value&&:&10&}&&
{&&_id&&:&2,&&value&&:&5&}&&
{&&_id&&:&3,&&value&&:&6&}&&
{&&_id&&:&4,&&value&&:&10&}&&
{&&_id&&:&5,&&value&&:&9&}&&
{&&_id&&:&6,&&value&&:&12&}&&
{&&_id&&:&7,&&value&&:&5&}&&
{&&_id&&:&8,&&value&&:&16&}&&
{&&_id&&:&9,&&value&&:&10&}&&
{&&_id&&:&10,&&value&&:&13&}&&
下面就来看看如何进行优化。
我在之前的这篇中简要说明了使用排序对于MR的好处,这是一个鲜为人知的特性。在这种情况下,如果处理未排序的输入,意味着MR引擎将得到随机排序的值,
基本上没有机会在RAM中进行reduce,相反,它将不得不通过一个临时collection来将数据写回磁盘,然后按顺序读取并进行reduce。
下面来看看如果使用排序,会有什么帮助:
&&db.runCommand(&&
{&mapreduce:&&uniques&,&&&
map:&function&()&{&emit(this.dim0,&1);&},&&&
reduce:&function&(key,&values)&{&return&Array.sum(values);&},&&&
out:&&mrout&,&&&
sort:&{dim0:&1}&})&&
&&&&&&&&&result&&:&&mrout&,&&
&&&&&&&&&timeMillis&&:&192589,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&,&&
&&&&&&&&&&&&&&&&&emit&&:&,&&
&&&&&&&&&&&&&&&&&reduce&&:&1000372,&&
&&&&&&&&&&&&&&&&&output&&:&999961&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
现在时间降到了192秒,速度提升了6倍。其实reduces的数量是差不多的,但是它们在被写入磁盘之前已经在RAM中完成了。
使用多线程
在MongoDB中,一个单一的MR任务并不能使用多线程——只有在多个任务中才能使用多线程。但是目前的多核CPU非常有利于在单一服务器上进行并行化工作,就像Hadoop。我们需要做的是,将输入数据分割成若干块,并为每个块分配一个MR任务。splitVector命令可以帮助你非常迅速地找到分割点,如果你有更简单的分割方法更好。
&&db.runCommand({splitVector:&&test.uniques&,&keyPattern:&{dim0:&1},&maxChunkSizeBytes:&})&&
&&&&&timeMillis&&:&6006,&&
&&&&&splitKeys&&:&[&&
&&&&&&&&{&&
&&&&&&&&&&&&&dim0&&:&18171&&
&&&&&&&&},&&
&&&&&&&&{&&
&&&&&&&&&&&&&dim0&&:&36378&&
&&&&&&&&},&&
&&&&&&&&{&&
&&&&&&&&&&&&&dim0&&:&54528&&
&&&&&&&&},&&
&&&&&&&&{&&
&&&&&&&&&&&&&dim0&&:&72717&&
&&&&&&&&},&&
&&&&&&&&{&&
&&&&&&&&&&&&&dim0&&:&963598&&
&&&&&&&&},&&
&&&&&&&&{&&
&&&&&&&&&&&&&dim0&&:&981805&&
&&&&&&&&}&&
&&&&&ok&&:&1&&
从1千万文档中找出分割点,使用splitVector命令只需要大约5秒,这已经相当快了。所以,下面我们需要做的是找到一种方式来创建多个MR任务。从应用服务器方面来说,使用多线程和$gt / $lt查询命令会非常方便。从shell方面来说,可以使用ScopedThread对象,它的工作原理如下:
&&var&t&=&new&ScopedThread(mapred,&1805)&&
&&t.start()&&
&&t.join()&&
现在我们可以放入一些JS代码,这些代码可以产生4个线程,下面来等待结果显示:
&&var&res&=&db.runCommand({splitVector:&&test.uniques&,&keyPattern:&{dim0:&1},&maxChunkSizeBytes:&32&*1024&*&1024&})&&
&&var&keys&=&res.splitKeys&&
&&keys.length&&
&&var&mapred&=&function(min,&max)&{&&&
return&db.runCommand({&mapreduce:&&uniques&,&&&
map:&function&()&{&emit(this.dim0,&1);&},&&&
reduce:&function&(key,&values)&{&return&Array.sum(values);&},&&&
out:&&mrout&&+&min,&&&
sort:&{dim0:&1},&&&
query:&{&dim0:&{&$gte:&min,&$lt:&max&}&}&})&}&&
&&var&numThreads&=&4&&
&&var&inc&=&Math.floor(keys.length&/&numThreads)&+&1&&
&&threads&=&[];&for&(var&i&=&0;&i&&&numT&++i)&{&var&min&=&(i&==&0)&?&0&:&keys[i&*&inc].dim0;&var&max&=&(i&*&inc&+&inc&&=&keys.length)&?&MaxKey&:&keys[i&*&inc&+&inc].dim0&;&print(&min:&&+&min&+&&&max:&&+&max);&var&t&=&new&ScopedThread(mapred,&min,&max);&threads.push(t);&t.start()&}&&
min:0&max:274736&&
min:274736&max:524997&&
min:524997&max:775025&&
min:775025&max:{&&$maxKey&&:&1&}&&
connecting&to:&test&&
connecting&to:&test&&
connecting&to:&test&&
connecting&to:&test&&
&&for&(var&i&in&threads)&{&var&t&=&threads[i];&t.join();&printjson(t.returnData());&}&&
&&&&&&&&&result&&:&&mrout0&,&&
&&&&&&&&&timeMillis&&:&205790,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&2750002,&&
&&&&&&&&&&&&&&&&&emit&&:&2750002,&&
&&&&&&&&&&&&&&&&&reduce&&:&274828,&&
&&&&&&&&&&&&&&&&&output&&:&274723&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
&&&&&&&&&result&&:&&mrout274736&,&&
&&&&&&&&&timeMillis&&:&189868,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&2500013,&&
&&&&&&&&&&&&&&&&&emit&&:&2500013,&&
&&&&&&&&&&&&&&&&&reduce&&:&250364,&&
&&&&&&&&&&&&&&&&&output&&:&250255&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
&&&&&&&&&result&&:&&mrout524997&,&&
&&&&&&&&&timeMillis&&:&191449,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&2500014,&&
&&&&&&&&&&&&&&&&&emit&&:&2500014,&&
&&&&&&&&&&&&&&&&&reduce&&:&250120,&&
&&&&&&&&&&&&&&&&&output&&:&250019&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
&&&&&&&&&result&&:&&mrout775025&,&&
&&&&&&&&&timeMillis&&:&184945,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&2249971,&&
&&&&&&&&&&&&&&&&&emit&&:&2249971,&&
&&&&&&&&&&&&&&&&&reduce&&:&225057,&&
&&&&&&&&&&&&&&&&&output&&:&224964&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
第1个线程所做的工作比其他的要多一点,但时间仍达到了190秒,这意味着多线程并没有比单线程快!
使用多个数据库
这里的问题是,线程之间存在太多锁争用。当锁时,MR不是非常无私(每1000次读取会进行yield)。由于MR任务做了大量写操作,线程之间结束时会等待彼此。由于MongoDB的每个数据库都有独立的锁,那么让我们来尝试为每个线程使用不同的输出数据库:
&&var&mapred&=&function(min,&max)&{&&&
return&db.runCommand({&mapreduce:&&uniques&,&&&
map:&function&()&{&emit(this.dim0,&1);&},&&&
reduce:&function&(key,&values)&{&return&Array.sum(values);&},&&&
out:&{&replace:&&mrout&&+&min,&db:&&mrdb&&+&min&},&&&
sort:&{dim0:&1},&&&
query:&{&dim0:&{&$gte:&min,&$lt:&max&}&}&})&}&&
&&threads&=&[];&for&(var&i&=&0;&i&&&numT&++i)&{&var&min&=&(i&==&0)&?&0&:&keys[i&*&inc].dim0;&var&max&=&(i&*&inc&+&inc&&=&keys.length)&?&MaxKey&:&keys[i&*&inc&+&inc].dim0&;&print(&min:&&+&min&+&&&max:&&+&max);&var&t&=&new&ScopedThread(mapred,&min,&max);&threads.push(t);&t.start()&}&&
min:0&max:274736&&
min:274736&max:524997&&
min:524997&max:775025&&
min:775025&max:{&&$maxKey&&:&1&}&&
connecting&to:&test&&
connecting&to:&test&&
connecting&to:&test&&
connecting&to:&test&&
&&for&(var&i&in&threads)&{&var&t&=&threads[i];&t.join();&printjson(t.returnData());&}&&
&&&&&&&&&result&&:&{&&
&&&&&&&&&&&&&&&&&db&&:&&mrdb274736&,&&
&&&&&&&&&&&&&&&&&collection&&:&&mrout274736&&&
&&&&&&&&},&&
&&&&&&&&&timeMillis&&:&105821,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&2500013,&&
&&&&&&&&&&&&&&&&&emit&&:&2500013,&&
&&&&&&&&&&&&&&&&&reduce&&:&250364,&&
&&&&&&&&&&&&&&&&&output&&:&250255&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
所需时间减少到了100秒,这意味着与一个单独的线程相比,速度约提高2倍。尽管不如预期,但已经很不错了。在这里,我使用了4个核心,只提升了2倍,如果使用8核CPU,大约会提升4倍。
使用纯JavaScript模式
在线程之间分割输入数据时,有一些非常有趣的东西:每个线程只拥有约25万主键来输出,而不是100万。这意味着我们可以使用“纯JS模式”——通过jsMode:true来启用。开启后,MongoDB不会在JS和BSON之间反复转换,相反,它会从内部的一个50万主键的JS字典来reduces所有对象。下面来看看该操作是否对速度提升有帮助。
&&var&mapred&=&function(min,&max)&{&&&
return&db.runCommand({&mapreduce:&&uniques&,&&&
map:&function&()&{&emit(this.dim0,&1);&},&&&
reduce:&function&(key,&values)&{&return&Array.sum(values);&},&&&
out:&{&replace:&&mrout&&+&min,&db:&&mrdb&&+&min&},&&&
sort:&{dim0:&1},&&&
query:&{&dim0:&{&$gte:&min,&$lt:&max&}&},&&&
jsMode:&true&})&}&&
&&threads&=&[];&for&(var&i&=&0;&i&&&numT&++i)&{&var&min&=&(i&==&0)&?&0&:&keys[i&*&inc].dim0;&var&max&=&(i&*&inc&+&inc&&=&keys.length)&?&MaxKey&:&keys[i&*&inc&+&inc].dim0&;&print(&min:&&+&min&+&&&max:&&+&max);&var&t&=&new&ScopedThread(mapred,&min,&max);&threads.push(t);&t.start()&}&&
min:0&max:274736&&
min:274736&max:524997&&
min:524997&max:775025&&
min:775025&max:{&&$maxKey&&:&1&}&&
connecting&to:&test&&
connecting&to:&test&&
connecting&to:&test&&
connecting&to:&test&&
&&for&(var&i&in&threads)&{&var&t&=&threads[i];&t.join();&printjson(t.returnData());&}&&
&&&&&&&&&result&&:&{&&
&&&&&&&&&&&&&&&&&db&&:&&mrdb274736&,&&
&&&&&&&&&&&&&&&&&collection&&:&&mrout274736&&&
&&&&&&&&},&&
&&&&&&&&&timeMillis&&:&70507,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&2500013,&&
&&&&&&&&&&&&&&&&&emit&&:&2500013,&&
&&&&&&&&&&&&&&&&&reduce&&:&250156,&&
&&&&&&&&&&&&&&&&&output&&:&250255&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
现在时间降低到70秒。看来jsMode确实有帮助,尤其是当对象有很多字段时。该示例中是一个单一的数字字段,不过仍然提升了30%。
MongoDB v2.6版本中的改进
在MongoDB v2.6版本的开发中,移除了一段关于在JS函数调用时的一个可选“args”参数的代码。该参数是不标准的,也不建议使用,它由于历史原因遗留了下来(见)。让我们从Git库中pull最新的MongoDB并编译,然后再次运行测试用例:
&&&&&&&&&result&&:&{&&
&&&&&&&&&&&&&&&&&db&&:&&mrdb274736&,&&
&&&&&&&&&&&&&&&&&collection&&:&&mrout274736&&&
&&&&&&&&},&&
&&&&&&&&&timeMillis&&:&62785,&&
&&&&&&&&&counts&&:&{&&
&&&&&&&&&&&&&&&&&input&&:&2500013,&&
&&&&&&&&&&&&&&&&&emit&&:&2500013,&&
&&&&&&&&&&&&&&&&&reduce&&:&250156,&&
&&&&&&&&&&&&&&&&&output&&:&250255&&
&&&&&&&&},&&
&&&&&&&&&ok&&:&1&&
从结果来看,时间降低到了60秒,速度大约提升了10-15%。同时,这种更改也改善了JS引擎的整体堆消耗量。
回头来看,对于同样的MR任务,与最开始时的1200秒相比,速度已经提升了20倍。这种优化应该适用于大多数情况,即使一些技巧效果不那么理想(比如使用多个输出dbs /集合)。但是这些技巧可以帮助人们来提升MR任务的速度,未来这些特性也许会更加易用——比如,这个&将会使splitVector命令更加可用,这个将会改进同一数据库中的多个MR任务。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:306873次
积分:4287
积分:4287
排名:第2891名
原创:120篇
转载:86篇
评论:50条
(1)(3)(2)(3)(3)(6)(3)(6)(8)(15)(3)(11)(6)(8)(25)(10)(7)(11)(6)(3)(27)(15)(19)(6)

我要回帖

更多关于 mongodb mysql 性能 的文章

 

随机推荐