基于Flume的日志收集系统(二)改进和优化

转自http://www.aboutyun.com/thread-8318-1-1.html

问题导读:

1.Flume的存在些什么问题?

2.基于开源的Flume美团增加了哪些功能?

3.Flume系统如何调优?

在《基于Flume的日志收集系统(一)架构和设计》中,我们详述了基于Flume的美团日志收集系统的架构设计,以及为什么做这样的设计。在本节中,我们将会讲述在实际部署和使用过程中遇到的问题,对Flume的功能改进和对系统做的优化。

1 Flume的问题总结

在Flume的使用过程中,遇到的主要问题如下:

a. Channel“水土不服”:使用固定大小的MemoryChannel在日志高峰时常报队列大小不够的异常;使用FileChannel又导致IO繁忙的问题;

b. HdfsSink的性能问题:使用HdfsSink向Hdfs写日志,在高峰时间速度较慢;

c. 系统的管理问题:配置升级,模块重启等;

2 Flume的功能改进和优化点

从上面的问题中可以看到,有一些需求是原生Flume无法满足的,因此,基于开源的Flume我们增加了许多功能,修改了一些Bug,并且进行一些调优。下面将对一些主要的方面做一些说明。

2.1 增加Zabbix monitor服务

一方面,Flume本身提供了http, ganglia的监控服务,而我们目前主要使用zabbix做监控。因此,我们为Flume添加了zabbix监控模块,和sa的监控服务无缝融合。

另一方面,净化Flume的metrics。只将我们需要的metrics发送给zabbix,避免 zabbix server造成压力。目前我们最为关心的是Flume能否及时把应用端发送过来的日志写到Hdfs上, 对应关注的metrics为:

Source : 接收的event数和处理的event数

Channel : Channel中拥堵的event数

Sink : 已经处理的event数

2.2 为HdfsSink增加自动创建index功能

首先,我们的HdfsSink写到hadoop的文件采用lzo压缩存储。 HdfsSink可以读取hadoop配置文件中提供的编码类列表,然后通过配置的方式获取使用何种压缩编码,我们目前使用lzo压缩数据。采用lzo压缩而非bz2压缩,是基于以下测试数据:

event大小(Byte)sink.batch-sizehdfs.batchSize压缩格式总数据大小(G)耗时(s)平均events/s压缩后大小(G)

54430010000bz29.1244868331.36

54430010000lzo9.1612273333.49

其次,我们的HdfsSink增加了创建lzo文件后自动创建index功能。Hadoop提供了对lzo创建索引,使得压缩文件是可切分的,这样Hadoop Job可以并行处理数据文件。HdfsSink本身lzo压缩,但写完lzo文件并不会建索引,我们在close文件之后添加了建索引功能。

/**

* Rename bucketPath file from .tmp to permanent location.

*/

private void renameBucket() throws IOException, InterruptedException {

if(bucketPath.equals(targetPath)) {

return;

}

final Path srcPath = new Path(bucketPath);

final Path dstPath = new Path(targetPath);

callWithTimeout(new CallRunner() {

@Override

public Object call() throws Exception {

if(fileSystem.exists(srcPath)) { // could block

LOG.info("Renaming " + srcPath + " to " + dstPath);

fileSystem.rename(srcPath, dstPath); // could block

//index the dstPath lzo file

if (codeC != null && ".lzo".equals(codeC.getDefaultExtension()) ) {

LzoIndexer lzoIndexer = new LzoIndexer(new Configuration());

lzoIndexer.index(dstPath);

}

}

return null;

}

});

}

复制代码

2.3 增加HdfsSink的开关

我们在HdfsSink和DualChannel中增加开关,当开关打开的情况下,HdfsSink不再往Hdfs上写数据,并且数据只写向DualChannel中的FileChannel。以此策略来防止Hdfs的正常停机维护。

2.4 增加DualChannel

Flume本身提供了MemoryChannel和FileChannel。MemoryChannel处理速度快,但缓存大小有限,且没有持久化;FileChannel则刚好相反。我们希望利用两者的优势,在Sink处理速度够快,Channel没有缓存过多日志的时候,就使用MemoryChannel,当Sink处理速度跟不上,又需要Channel能够缓存下应用端发送过来的日志时,就使用FileChannel,由此我们开发了DualChannel,能够智能的在两个Channel之间切换。

其具体的逻辑如下:

/***

* putToMemChannel indicate put event to memChannel or fileChannel

* takeFromMemChannel indicate take event from memChannel or fileChannel

* */

private AtomicBoolean putToMemChannel = new AtomicBoolean(true);

private AtomicBoolean takeFromMemChannel = new AtomicBoolean(true);

void doPut(Event event) {

if (switchon && putToMemChannel.get()) {

//往memChannel中写数据

memTransaction.put(event);

if ( memChannel.isFull() || fileChannel.getQueueSize() > 100) {

putToMemChannel.set(false);

}

} else {

//往fileChannel中写数据

fileTransaction.put(event);

}

}

Event doTake() {

Event event = null;

if ( takeFromMemChannel.get() ) {

//从memChannel中取数据

event = memTransaction.take();

if (event == null) {

takeFromMemChannel.set(false);

}

} else {

//从fileChannel中取数据

event = fileTransaction.take();

if (event == null) {

takeFromMemChannel.set(true);

putToMemChannel.set(true);

}

}

return event;

}

复制代码

2.5 增加NullChannel

Flume提供了NullSink,可以把不需要的日志通过NullSink直接丢弃,不进行存储。然而,Source需要先将events存放到Channel中,NullSink再将events取出扔掉。为了提升性能,我们把这一步移到了Channel里面做,所以开发了NullChannel。

2.6 增加KafkaSink

为支持向Storm提供实时数据流,我们增加了KafkaSink用来向Kafka写实时数据流。其基本的逻辑如下:

public class KafkaSink extends AbstractSink implements Configurable {

private String zkConnect;

private Integer zkTimeout;

private Integer batchSize;

private Integer queueSize;

private String serializerClass;

private String producerType;

private String topicPrefix;

private Producer producer;

public void configure(Context context) {

//读取配置,并检查配置

}

@Override

public synchronized void start() {

//初始化producer

}

@Override

public synchronized void stop() {

//关闭producer

}

@Override

public Status process() throws EventDeliveryException {

Status status = Status.READY;

Channel channel = getChannel();

Transaction tx = channel.getTransaction();

try {

tx.begin();

//将日志按category分队列存放

Map> topic2EventList = new HashMap>();

//从channel中取batchSize大小的日志,从header中获取category,生成topic,并存放于上述的Map中;

//将Map中的数据通过producer发送给kafka

tx.commit();

} catch (Exception e) {

tx.rollback();

throw new EventDeliveryException(e);

} finally {

tx.close();

}

return status;

}

}

复制代码

2.7 修复和scribe的兼容问题

Scribed在通过ScribeSource发送数据包给Flume时,大于4096字节的包,会先发送一个Dummy包检查服务器的反应,而Flume的ScribeSource对于logentry.size()=0的包返回TRY_LATER,此时Scribed就认为出错,断开连接。这样循环反复尝试,无法真正发送数据。现在在ScribeSource的Thrift接口中,对size为0的情况返回OK,保证后续正常发送数据。

3. Flume系统调优经验总结3.1 基础参数调优经验

HdfsSink中默认的serializer会每写一行在行尾添加一个换行符,我们日志本身带有换行符,这样会导致每条日志后面多一个空行,修改配置不要自动添加换行符;

lc.sinks.sink_hdfs.serializer.appendNewline = false

复制代码

调大MemoryChannel的capacity,尽量利用MemoryChannel快速的处理能力;

调大HdfsSink的batchSize,增加吞吐量,减少hdfs的flush次数;

适当调大HdfsSink的callTimeout,避免不必要的超时错误;

3.2 HdfsSink获取Filename的优化

HdfsSink的path参数指明了日志被写到Hdfs的位置,该参数中可以引用格式化的参数,将日志写到一个动态的目录中。这方便了日志的管理。例如我们可以将日志写到category分类的目录,并且按天和按小时存放:

lc.sinks.sink_hdfs.hdfs.path = /user/hive/work/orglog.db/%{category}/dt=%Y%m%d/hour=%H

复制代码

HdfsS ink中处理每条event时,都要根据配置获取此event应该写入的Hdfs path和filename,默认的获取方法是通过正则表达式替换配置中的变量,获取真实的path和filename。因为此过程是每条event都要做的操作,耗时很长。通过我们的测试,20万条日志,这个操作要耗时6-8s左右。

由于我们目前的path和filename有固定的模式,可以通过字符串拼接获得。而后者比正则匹配快几十倍。拼接定符串的方式,20万条日志的操作只需要几百毫秒。

3.3 HdfsSink的b/m/s优化

在我们初始的设计中,所有的日志都通过一个Channel和一个HdfsSink写到Hdfs上。我们来看一看这样做有什么问题。

首先,我们来看一下HdfsSink在发送数据的逻辑:

//从Channel中取batchSize大小的events

for (txnEventCount = 0; txnEventCount < batchSize; txnEventCount++) {

//对每条日志根据category append到相应的bucketWriter上;

bucketWriter.append(event);

for (BucketWriter bucketWriter : writers) {

//然后对每一个bucketWriter调用相应的flush方法将数据flush到Hdfs上

bucketWriter.flush();

复制代码

假设我们的系统中有100个category,batchSize大小设置为20万。则每20万条数据,就需要对100个文件进行append或者flush操作。

其次,对于我们的日志来说,基本符合80/20原则。即20%的category产生了系统80%的日志量。这样对大部分日志来说,每20万条可能只包含几条日志,也需要往Hdfs上flush一次。

上述的情况会导致HdfsSink写Hdfs的效率极差。下图是单Channel的情况下每小时的发送量和写hdfs的时间趋势图。



鉴于这种实际应用场景,我们把日志进行了大小归类,分为big, middle和small三类,这样可以有效的避免小日志跟着大日志一起频繁的flush,提升效果明显。下图是分队列后big队列的每小时的发送量和写hdfs的时间趋势图。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容