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)
- 对于我们这类经验尚浅的程序员,设计架构方案的时候其实不需要过分地深入思考性能等问题,因为很多问题没有实际做过是想不到的;根据自己已有经验做个初步方案,然后通过发现问题逐渐修改迭代。