基于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-size | hdfs.batchSize | 压缩格式 | 总数据大小(G) | 耗时(s) | 平均events/s | 压缩后大小(G) |
---|---|---|---|---|---|---|---|
544 | 300 | 10000 | bz2 | 9.1 | 2448 | 6833 | 1.36 |
544 | 300 | 10000 | lzo | 9.1 | 612 | 27333 | 3.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<Object>() {
@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<String, String> 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<String, List<String>> topic2EventList = new HashMap<String, List<String>>();
//从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的时间趋势图。
4 未来发展
目前,Flume日志收集系统提供了一个高可用,高可靠,可扩展的分布式服务,已经有效地支持了美团的日志数据收集工作。
后续,我们将在如下方面继续研究:
日志管理系统:图形化的展示和控制日志收集系统;
跟进社区发展:跟进Flume 1.5的进展,同时回馈社区;
基于Flume的美团日志收集系统(二)改进和优化的更多相关文章
- 基于Flume的美团日志收集系统 架构和设计 改进和优化
3种解决办法 https://tech.meituan.com/mt-log-system-arch.html 基于Flume的美团日志收集系统(一)架构和设计 - https://tech.meit ...
- 基于Flume的美团日志收集系统(一)架构和设计
美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流.美团的日志收集系统基于Flume设计和搭建而成. <基于Flume的美团日志收 ...
- 基于Flume的美团日志收集系统(一)架构和设计【转】
美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流.美团的日志收集系统基于Flume设计和搭建而成. <基于Flume的美团日志收 ...
- 转:基于Flume的美团日志收集系统(一)架构和设计
美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流.美团的日志收集系统基于Flume设计和搭建而成. <基于Flume的美团日志收 ...
- Flume + HDFS + Hive日志收集系统
最近一段时间,负责公司的产品日志埋点与收集工作,搭建了基于Flume+HDFS+Hive日志搜集系统. 一.日志搜集系统架构: 简单画了一下日志搜集系统的架构图,可以看出,flume承担了agent与 ...
- Flume -- 开源分布式日志收集系统
Flume是Cloudera提供的一个高可用的.高可靠的开源分布式海量日志收集系统,日志数据可以经过Flume流向需要存储终端目的地.这里的日志是一个统称,泛指文件.操作记录等许多数据. 一.Flum ...
- 十九,基于helm搭建EFK日志收集系统
目录 EFK日志系统 一,EFK日志系统简介: 二,EFK系统部署 1,EFK系统部署方式 2,基于Helm方式部署EFK EFK日志系统 一,EFK日志系统简介: 关于系统日志收集处理方案,其实有很 ...
- Go实现海量日志收集系统(二)
一篇文章主要是关于整体架构以及用到的软件的一些介绍,这一篇文章是对各个软件的使用介绍,当然这里主要是关于架构中我们agent的实现用到的内容 关于zookeeper+kafka 我们需要先把两者启动, ...
- Flume日志收集系统架构详解--转
2017-09-06 朱洁 大数据和云计算技术 任何一个生产系统在运行过程中都会产生大量的日志,日志往往隐藏了很多有价值的信息.在没有分析方法之前,这些日志存储一段时间后就会被清理.随着技术的发展和 ...
随机推荐
- 疯狂java讲义——多态
父类 f = new 子类(); 引用变量f,在编译时类型是父类,在运行时类型是子类类型.当这个引用变量调用子类重写父类的那个方法的时候,实际执行的是子类中重写后的那个方法.当运行的时候调用该变量的方 ...
- 结合NGUI做的手机拍照(可自定义相框)
原地址:http://www.unity蛮牛.com/thread-18220-1-1.html 在次此之前我们先要了解一下下面的我要讲的几个内容: 一.为什么要用NGUI,因为NGUI的可以做屏幕自 ...
- 使用 Swagger UI 与 Swashbuckle 创建 RESTful Web API 帮助文件
作者:Sreekanth Mothukuru 2016年2月18日 本文旨在介绍如何使用常用的 Swagger 和 Swashbuckle 框架创建描述 Restful API 的交互界面,并为 AP ...
- 如何用 Parse 和 Swift 搭建一个像 Instagram 那样的应用?
[编者按]本篇文章作者是Reinder de Vries,既是一名企业家,也是优秀的程序员,发表多篇应用程序的博客.本篇文章中,作者主要介绍了如何基于Parse特点,打造一款类似Instagram的应 ...
- POJ 2591 1338 2545 2247(数列递归衍生问题,思路挺妙)
四道题的难度: 2591<1338<2545<2247 POJ 2591 Set Definition: 这是从discuss里看来的,写的挺好,直接copy,根据我的代码稍有改动( ...
- MongoDB (五) MongoDB 数据库操作
一.MongoDB创建数据库: use 命令 MongoDB use DATABASE_NAME 用于创建数据库.该命令将创建一个新的数据库,如果它不存在,否则将返回现有的数据库. 语法: use D ...
- C# Socket 入门2(转)
现在来传一个图片看看, 改改程序, 看看服务端 图片为 140K, 1.jgp 1. 服务端 1 using System; 2 using System.Collections.Generic; ...
- 华为OJ:素数对个数
素数对个数 题目描述若两个正整数的和为素数,则这两个正整数称之为“素数伴侣”,如2和5.6和13,它们能应用于通信加密.现在密码学会请你设计一个程序,从已有的N(N为偶数)个正整数中挑选出若干对组成“ ...
- 查看服务器硬件配置信息(cpu/内存)
1.查看cpu情况: 方法一: Linux下CPU相关的参数保存在 /proc/cpuinfo 文件里 cat /proc/cpuinfo |more 方法二: 采用命令 dmesg ...
- 在telnet下操作memcache详解(操作命令详解)
这篇文章主要介绍了在telnet下操作memcache详解,telnet下的memcache操作命令详解,需要的朋友可以参考下 在定位问题.测试等时候经常需要对memcache的数据进行一些操作,但是 ...