2014年 2月到3月期间,我在研究生导师大数据公司的实习期间去武汉出差参与一个项目为某上市网络舆情R公司提供技术解决方案。R公司接下了一个为浙江省政府DNS数据分析的项目,但他们自身没有技术实力去解决海量数据的处理。DNS字段相对较小,所以一天的总量也就 1~2 TB 左右,涉及主要业务有从域名、顶级域名、客户端IP等不同维度出发的不同时间粒度的统计,并且运行有时限要求。
技术方案选择。因为这种处理是定时离线处理的(排除Storm等实时系统),而且他们提供的机器内存测试机才8G,线上也就16G(排除Spark等内存方案),再考虑到开发维护难度等,毫无疑问最佳选择便是MapReduce了。基本流程便是ETL(Extract-Transform-Load,即一般的数据预处理流程),然后做个过滤排重,接着业务统计,最后根据需求分别落地到HDFS、Oracle、Redis、Elastic Search等。
ETL一开始便遇到困难,平均一个小时10g的原始数据,而且是一开始放在单机上,要求半个小时做完处理任务。单机一个小时10g做IO还要走网络去分布式,可能光读取数据时间都不止三十分钟。其实最好的解决方案自然是一开始数据就放到HDFS上,但因为放数据那边的技术受限原因,最好的情况也就是放到FTP而已。因此为了提高数据读取效率,我们首先要求对方写了个一个定时压缩脚本,将数据压缩成gz格式,同时也为了提高MapReduce的IO效率,要求将一个小时数据分割成多个小压缩包,每个大概二十多M左右。这样一来在MapReduce解压缩后的大小基本就接近128M了,也就是我们配置的一个block的大小。同时我们还打开了MapReduce的JVM重用(机器数少但Mapper多),试图通过减少多次重启JVM的开销来达到优化,最后效果不是很明显,毕竟每次跑长时间任务的时间多少都会有误差,JVM重启最多也不过几秒到十几秒,相对来说不算是优化的重点。
一开始需求方本来是希望我们按照那个业务处理流程 ETL -> 排重 -> 统计 -> 同步 来按模块实现的,但是MapReduce瓶颈毕竟在那,纯技术类优化是有极限的,因此最后我们只能对业务流程做优化,将部分可以预处理的任务混合了起来。比如排重本来是要求每个不同维度的统计前做不同维度排重,我们在ETL做了一次排重然后按统计维度分发,如此一来接下来的多个统计MR的IO就大大减少,尽管从不同维度还得再做排重,但是数据量已经下降很多了。
但即使如此,统计时我们一开始仍然不太满意其性能,于是在多Mapper单Reducer的场景下自然而然就想到了应用Combiner。常有人提醒使用Combiner很容易犯些低级错误,这个每个初学Combiner的人应该都知道。当时我踌躇满志的觉得那种低级错误不会犯,于是在一个统计TOP N的任务中使用了Combiner,逻辑很简单,就是每个Combiner筛掉后面的选出TOP N,然后Reducer对来自所有的Combiner的record再统计做TOP N。道理很简单,每个山头选出最厉害的,再将这些最厉害的比一下选出全部中最厉害的,就是一个锦标赛原理嘛!但问题就出在这里了,要保证锦标赛的正确,前提是保证同一个top维度的数据必须在同一个Mapper,但当时读取的时候毕竟不是按top维度的key读取的。于是一开始有种错觉好像效率变快了,但是一看数据发现有不对,反复几次发现问题后,最终还是只能把Combiner去掉。
还有一个比较重要的业务是做diff,要统计相对于上一时间粒度,当前时间粒度新出现或未出现的客户端IP、域名、顶级域名等,类似做个集合差运算。这个设计很简单,读取两个目录的文件(上个时间粒度与当前时间粒度),然后根据读取的文件不同设key,在Reduce端过滤计算。这里要实现MapReduce读取多文件并能知道每条数据来自哪个文件,还要多输出。本来1.0.4内置是有个MultiOutput的,但是当时感觉不好用,于是自己通过看TextOutputFormat的源码重写了一个继承它的多文件读取类,Input也是自己重写的。一切都很美好,但没多久总觉得数据少了很多,挣扎了一两天终于发现bug来自自己写的那个Output,最后同事从Hadoop 1.1.2版本封装了一段MultiOutput类的代码才解决问题。于是最终变成自己写的多输入和抄了Hadoop 1.1.2的多输出共存的局面。
存储方面Redis自然要使用pipeline以及多次重试(Time out太常见了),Oracle自然要做好参数绑定,这些不赘述。
总结一下,我个人从这次项目出差的体会:
- 技术优化一部分是基础,需要自己养成良好习惯;另外很重要的一步是针对业务做优化,否则在这个层次空谈性能优化有点耍流氓。
- 在我们选择技术方案的时候,很多时候要靠经验,盲目相信别人的评测或选择常常导致自己会出问题而不知所措。
- 出问题了代码有机会最好和同事一起做做peer review,常会有意想不到的收获(bug)
- 对于我们还在实习的这类经验尚浅的程序员,设计架构方案的时候其实不需要过分地深入思考性能等问题,因为很多问题没有实际做过是想不到的;根据自己已有经验做个初步方案,然后通过发现问题逐渐修改迭代。