大规模日志收集处理项目的技术总结

以下是2012年一个公司内部项目的技术总结,涉及到的方面比较多比较杂,拿出来和大家分享下。如果有更好的方案或者想法请联系我,谢谢~!注:文章中提到的其他系统(如哈勃Agent、EagleEye)是公司内部的其他系统,这里就不详细介绍了。

简介

TLog是一个分布式的,可靠的,对大量数据进行收集、分析、展现的的系统。主要应用场景是收集大量的运行时日志,分析并结构化存储,提供数据查询和展现。

服务能力技术选型

一个海量数据收集的系统,首先需要考虑的就是收集模型:推送(push),还是拉取(pull)。两种模式都有各自的优缺点。业界的很多系统都是push模型,比如facebook的scribe,而我们主要选择的是pull模型(push模型后续支持),这个决策和我们所处的环境有关:

TLog集群可用资源非常有限

选用push模型,就需要要求日志收集器的容量需要大于高峰期数据的生成量,否则主动推送过来的数据不能及时处理会带来更多更复杂的问题:比如信息在收集器端如何先暂存慢慢处理,这又牵扯到收集器端是否有这么多的缓存空间(比如硬盘是否够大来临时保存汹涌而至的数据,或者转移到其他地方的网络开销等);如果在日志生成端临时保存,则需要有一系列状态的变化,比如收集器正常则直接发数据,否则则保存本地硬盘,等到收集器恢复了再把硬盘数据发送,然后在恢复到直接发送模式等。

最初TLog集群只有6台虚拟机,后期扩展到12台。硬件处理能力的限制,决定了我们处理海量数据时压力非常大,如果还选用push模型,在数据生成的高峰期,必然无法处理瞬间大量的日志。而选择pull模型,控制权掌握在自己手里,收集器可以根据自己的节奏游刃有余的拉取日志,高峰期产生的日志会在接下来的时间慢慢的被消化(当然收集器的能力需要高于日志产生的平均值)。当然,这样的缺陷是处理延迟增加了。

信息的时效性

push模型能带来很高的信息时效性,可以最快的收集、整理,并查询出来。而我们的先期定位并不是特别在意这样的实时性,因为接入的应用主要是使用这些数据做日报、周报等,能够接受5~10分钟甚至更张的数据延迟。而且有些环境的约束导致做到秒级别的准实时也没有意义,比如HSF的哈勃日志,一个数据单元每2分钟才输出一次,从日志的输出端就已经造成了2分钟的延迟了,后面在快也意义不大。所以选用pull模型,在数据高峰期,大量数据临时挤压,后期慢慢处理对我们来说是可以接受的。

可靠性

可靠是必须的。众多push模型的产品在保证可靠性做了很多事情,使得事情变得非常的复杂,比如:

而选择pull模型,再借助哈勃Agent这个基础设施,事情会变得非常简单!这里不得不提一下:哈勃Agent是个很不错的产品,简单而有效!而且它的存在使得TLog设计和部署变得简单很多:

当然,选择pull模型也是有自己的问题:

日志收集任务的管理:因为信息不会主动推送过来,所以需要自己记得去哪里取。收集任务的管理是个不小的问题:比如有些任务链接会失败(比如哈勃Agent没有部署,需要找PE解决);有些任务会忙不过来,需要增加处理器节点(如某个任务的收集器负荷重导致收集频率降低,长时间没有抓取动作);新的应用接入需要配置相关的抓取任务,应用的服务器变更后相关的任务也需要变更等,虽然很多都做了自动化处理(比如定时同步Armory来获取应用和ip的映射关系等),但不得不承认任务的管理是个不小的负担。技术挑战

TLog做的事情非常简单,但是再海量数据的冲击下,系统很容易变得千疮百孔。

JVM内存溢出

TLog首先遇到的问题就是OOM。收集器所在的虚拟机,15MB/秒的数据流入10MB/秒的数据流出(这还是平常业务压力不大的时候)。很容易想象,10+MB的数据解析成大量的对象,稍微处理不好就会导致大量的JVM堆内存被占用,很容易OOM。结合应用自身的状况,经过很多尝试,最终找到了解决办法,这也让我对很多东西有了新的认识:

线程池的大小

线程池的大小对于TLog来说不是性能的问题,而是会不会死人的问题。线程池在TLog内部主要是任务调度使用(Quarz),每一个日志收集任务启动会占用一个线程,后续的所有动作都在这个线程完成:收集一批增量日志;使用不同的解析器把日志解析成结构化对象;持久化(入HBase或者云梯或者消息中间件)。这样的划分方式使得线程之间没有任何通信(也就没有锁的竞争),有因为整个处理任务的两头有大量的IO动作(拉取日志和持久化),中间过程是纯CPU运算(解析),所以多个线程大家互补忙闲能做到很高的效率(CPU和IO双忙……)。

但是线程池开多少?当初拍脑袋定了200,结果只要日志有积压(业务高峰,或者TLog下线一段时间)TLog直接OOM。中间甚至使用过“延迟启动任务”的方式,即收集器把任务以一定间隔(比如2秒)一个一个启动,有一定效果,但还是很容易挂,而且一个收集器一般会有5k+个任务,两秒启动一个的话……这很显然不靠谱。分析了状况后,发现事情是这样的:

原本很简单的事情(拉日志,解析,入库)变得无法稳定运行,经过一步步测试,最终把线程池大小控制在30(后续因为逻辑更加复杂,但任务占用内存量又增加,而调整到25),之所以调整到这么小是因为如果再大,比如35个任务同时处理,就会导致内存占用非常紧张(虽说有5G的堆内存,old、eden、s1和s0分分就没多少了),导致Full GC,但又没有成果,GC完内存一样不够用,就再Full GC,结果导致90%以上的时间都在做无用的GC。那还不如把内存控制的留点余量,不至于频繁触发FGC,而留下大量的时间专心干活呢。当然除此之外还有很多其他的优化,比如把先批量解析再批量保存改为边解析边保存,保存过后的对象就可以被GC了,降低对象的存活时间。另外一个很重要的点:通过哈勃Agent拉取的一批增量日志一下都被加载到内存中,随后慢慢的解析处理。在极端情况下只要这批日志没有处理完,就会有10MB(哈勃Agent单次拉取日志的上限)的字节无法释放。应该改为流式的处理,读一部分处理一部分,然后再读取下一部分。但因为这个改动对整个解析结构会有很大的调整,所以就放到了后面迁移Storm时统一做了修改,整个jvm内存占用量减少一半左右,不再成为系统的瓶颈。

经过了一番改动后,TLog变得“压不死”了,即使积压了大量的数据,启动后网络流量涌入30MB/每秒,cpu会稳定再85%,系统load 40(夸张的时候有80),但是很稳定,不到2秒一次YGC,10分钟一次FGC,系统可以保持这样的压力运行6个小时,当积压的所有数据都处理完后,机器的负载,cpu及内存的占用自动恢复到正常水平。

JVM GC参数自己要先看得起自己,别人才会看得起你

大规模日志收集处理项目的技术总结

相关文章:

你感兴趣的文章:

标签云: