近期是对storm做了不少的研究与分享,包括我的前一篇文章的《数据处理神器storm的理解与思考 ——让你的数据化作行云流水》,无论是看官方的文档,还是看其他第三方文献介绍推荐,总会让你觉得各种高端先进,毕竟它代表了一种比较新潮的设计思想,刚开始接触了解的人更会跃跃欲试。然而storm是否真如看上去那么美?还是说,storm只是另一个喜好新鲜事物的开发者把玩的玩物?这些都需要亲自尝试过才会得知。归根到底,我们应该问的问题是:我们的任务是否适合利用storm来实现?
前段时间,为做日志分析系统的改造,尝试实践了一套flume->kafka->strom->database的实现。本文将着重以此作为实例,对storm作一次实践分析,并介绍在storm系统引入过程中所遇到的与填过的一些坑。
不用storm的日志分析
不涉及业务内容,首先看下一下,我们原先的日志分析的基本流程。
假设,一条日志记志里面有两个属性字段,一个是adId,代表广告的id,另一个是clicks,代表点击次数。现要求统计按小时进行统计:
将日志产生处会将小时存储日志文件(上图logHourFile1,logHourFile2),由logCollector将同一个小时的日志汇集成一个大日志文logHourFileAllInOne。日志系统LogAnalyzer逐行读取logHourFileAllIOne文件,每条纪录包含了adId与clicks信息,例如“adId=123,clicks=2”,表示广告123,被点击了两次。 logAnalyzer会初始化一个map,key为adId,value为点击次数。如果一条log纪录过来,发现map中将adId为kye的纪录不存在,将这条纪录存入map;如果已经存在,取map中的该纪录的value,累加当前纪录的clicks值到其上。当读到文件尾的时候,map中包含了每个广告在该小时内的总点击数。最终将map中的信息逐条存入数据库中,结束该小时统计。
过程应该算是相当简单清晰。但是这样的分析过程有几个明显的缺点:
当前小时的统计结果,必须下个小时才能看结果。因为必须等当前小时结束,才能得对该小时的日志进行汇总。
为了进行汇总工作,文件要统一传输到一个节点,会瞬时占用较大的网络带宽。对其他的服务可能会带来冲击。
引入storm的日志分析
这次的改造,不仅引入了storm。我们还使用了flume,采用tail的方式代替了原有的分析系统对日志的采集功能,实现汇总,再利用kafka作为消息队列,使数据可以被storm逐条获取。关于kafka的介绍,可参考《闲扯kafka mq》。
剩下的主要工作便是设计实现一个storm的topology,将原系统的分析逻辑移到其中。刚开始的时候,我的topology大抵是这样的:
LogSpout基于kafka-storm(https://github.com/joshdevins/storm-kafka)实现,主要负责将kafka中的log消息读出,shuffle到下一个LogBuildBolt。LogBuildBolt负责构建Log纪录相关的实体对象,实际的处理肯定会比上文中只有adId与clicks的情况复杂,所以有这个Bolt存在的必要。StatisticBolt为统计结点,也就是为每小时数据准备一个map ,根据adId,累加点击数。DataStoreBolt,负责与数据库交互,接收上一小时来自StatisticBolt的map中数据,存入数据库。
这时别扭的情况出现了。显然这里的设计DataStoreBolt的基本是一小时集中做一次计算,而不是一条条平滑的过来进入数据库。这里你就很可能需要做定时器,也就意味的我们要自行起一个线程。在我看来这就与storm正常使用思想是违背的,因为storm所做的工作的一部份就是让我们不用自行建立线程,不用操心线程安全的问题。这个问题也不大,可能是我的精神洁癖作祟。但还有个问题我不能忍:storm的ack机制默认设置是超时时间30秒,而StatisticBolt是会定时一小时去发送一批数据到DataStoreBolt,如此的话,该tuple老早就被认为超时失败了,若将超时时间设置延长至一小时,ack Bolt也会因为pending的tuple过多而出现内存泄露,虽说很多文章建议出于性能上的考虑可以直接去掉ack功能,可我还是不想因为设计上的原因而过早的放弃storm的可靠性机器。然而,此时的我,依旧保持乐观,既然如此,便将DataStoreBolt去掉,将数据库定时存储的功能移动StatisticBolt中去。StatisticBolt在收到数据并完成内存中的累加时,便直接ack,相当于Storm Ack机制的管辖范围到此提前结束。现在Topology变成了这样。