转自:http://blog.163.com/guaiguai_family/blog/static/20078414520138100562883/

Flume 是 Cloudera 公司开源出来的一套日志收集系统,早期版本依赖 ZooKeeper,现在的 FumeNG 去掉了这个依赖,我没用过之前的版本,想来失去整个日志收集系统的全局视图是挺可惜的,但 FlumeNG 上手以及使用挺简单,搭配监测系统也能用的不赖,有利有弊了:-)

下图展示了一种常见的 Flume 使用场景,服务器上发送事件给本地的 Flume agent 或者让本地 Flume agent 去 tail -f 日志文件,日志被发送给同一个数据中心里的多个下游 Flume agent,这些下游 Flume agent 将数据写到 HDFS,同时在本地磁盘留一个短期副本以供调试。

Flume 的配置文件挺易懂的, 官方文档 有很详细的描述,从结构上讲分成两部分,声明一个 Flume agent 会运行哪些 source、channel、sink,然后是配置各个 source、channel、sink 的属性以及互相的连接关系。

  • source:日志来源,可以调用外部命令比如 tail -f,也可以监听端口,接收 avro、thrift、文本行格式的日志。source 和 channel 是多对多的关系,source 往 channel 里写数据可以是replicating(默认)或者 multiplexing方式,比如上图里 log collector 里的 source 就是复制了两份日志写到两个 channel 里。
  • channel:其实个人觉得叫 queue 更合适,免得跟 sink 的用途混淆。channel 用来做日志缓存以及日志分发,粗略来说channel 和 sink 是一对多的关系,channel 传数据到 sink 可以有 default, failover 和 load_balance三种方式,文档里这个地方既叫 sink processor 又叫 sink group,个人觉得 sink group 理解起来更容易,channel 实际是发送数据到 sink group,所以是 channel 和 sink group 一对一(这大概就是为什么sink group 又叫 sink processor,指如何把事件从 channel 里转到 sinks 这个处理过程),sink group 和 sink 是一对多。default 方式下一个 sink group 只允许有一个 sink;failover 指总是先写优先级高的 sink,失败的话写优先级次高的 sink;load_balance 就容易理解了,轮流写 sink。
  • sink:处理日志写出,注意一个 sink 只能写一个地方,比如本地文件或者某单个远程主机的网络端口,并不是 sink 来做 load balance,所以上图中针对每个 log collector,log emitter 那里都要各配置一个 sink。Flume 标配的 sink 挺多,local fs, hdfs, hbase, solr, elasticsearch, avro, thrift 等,难能可贵的是 hdfs 和 hbase sink 都支持 Kerberos 认证,真不愧是 Cloudera 家做的东西,跟 Hadoop 集成就是好。

Flume agent 的一个进程里可以包含多个 source、channel、sink,这些元素之间组成的 flow 可以互相没关系,比如一套 source-channel-sink 收集 access.log,一套 source-channel-sink 收集 error.log,两者没有数据交互。同一台机器上也可以运行多个 flume agent 进程。注意同一个 agent 进程里 memory channel 里的 event 是共享的,但是 Flume 在估算内存消耗时不考虑共享这个事情。

Flume agent 进程会每隔三十秒检测配置文件,如果修改了会重新载入,所以虽然没有 ZooKeeper 集中管理配置信息,但利用 Puppet/Chef + Nagios/Ganglia 之类帮忙也不是太大问题。

Flume 没有像 Scribe 那样直接支持 category,而是允许给 event 添加 header,在 multiplexing channel selector 里可以按照 event header 映射到不同 channel,这样就可以在整个 flow 的末端把日志切分开来。如果使用 hdfs sink 的话,hdfs 文件名可以插入 event header 的值,所以不必用 multiplexing channel selector 即可达到按 category 切分日志的效果。

Flume 的设计还是挺灵活挺简单的,我小测试了下,稳定性不错,但是性能不怎么样(可能我测试不规范),尤其是用 file channel 的时候,Flume 把事件缓存在 JVM 里,这个设计没有 Kafka 高明以及高效。另一个担心是它没有像 Kafka 那样把 replication 作为一个核心设计,需要使用者去 event flow 的各个环节显式配置,比如每个 log collector 加一个 memory channel 和一个 avro sink 写到另一个 log collector 去,这个过程没有 ZooKeeper 的帮助,实际是没有实用价值的。如果项目时间允许,我觉得在 Kafka 基础上构建 sink 是个更高效、方便且可靠的日志收集方案,如同 LinkedIn 的 data pipeline 架构那样。

下面是一段 Perl 脚本,用于生成 Flume 配置,不直接手工配置的原因是很多地方的 source、channel、sink 配置是基本一样的,手工维护有点累。下面那行 tail 脚本有点长,显示不完整,应该是

tail -F -n 0--pid `ps -o ppid= \$\$` $log_file | sed -e \"s/^/host=`hostname --fqdn` category=$category:/\"

话说 Scribe、Flume 这些二货为啥不直接提供 tail -f 的功能。。。。

#!/usr/bin/perl
#
# Emitter:
# Server -> access.log -> tail -F -> Flume exec source(access) ->
# Flume file channel(c1) -> Flume Avro Sinks with load balancing sink processor(g1: s1 s2 s3)
#
# Collector:
# Flume Avro source with replicating channel selects(source1) ->
# Flume memory channel(file1) -> Flume file roll sink(file1)
# Flume memory channel(hdfs1) -> Flume HDFS sink(hdfs2)
# Flume memory channel(hdfs2) -> Flume HDFS sink(hdfs2), in another data center
# Flume memory channel(hbase1) -> Flume HBase sink(hbase1), the standard HBase sink uses
# hbase-site.xml to get server address, so can't use two HBase sinks
# except starting another Flume agent process. use strict;
use warnings;
useGetopt::Long; my%g_log_files =(
"access"=>[ qw(/tmp/access.log )],
);
my@g_collector_hosts= qw( collector1 collector2 collector3 );
my $g_emitter_avro_port =3000;
my $g_emitter_thrift_port =3001;
my $g_emitter_nc_port =3002;
my $g_collector_avro_port =3000;
my $g_flume_work_dir ="/tmp/flume";
my $g_data_dir ="/tmp/log-data";
my%g_hdfs_paths =(
"hdfs1"=>"hdfs://namenode1:8020/user/gg/data",
"hdfs2"=>"hdfs://namenode2:8020/user/gg/data",
);
my $g_emitter_conf ="emitter.properties";
my $g_collector_conf ="collector.properties";
my $g_overwrite_conf =0; GetOptions("force!"=> \$g_overwrite_conf); generate_emitter_config();
generate_collector_config(); exit(0); #######################################
sub generate_emitter_config {
my $conf ="";
my $sources = join(" ", sort(keys %g_log_files));
my $sinks = join(" ", map {"s$_"}(1..@g_collector_hosts)); $conf .=<<EOF;
emitter.sources = $sources avro1 thrift1 nc1
emitter.channels = c1
emitter.sinks = $sinks
emitter.sinkgroups = g1 EOF formy $category ( sort keys %g_log_files){
my $log_files = $g_log_files{$category}; formy $log_file (@$log_files){
$conf .=<<EOF;
emitter.sources.$category.channels = c1
emitter.sources.$category.type =exec
emitter.sources.$category.command = tail -F -n 0--pid `ps -o ppid= \$\$` $log_file | sed -e \"s/^/host=`hostname --fqdn` category=$category:/\"
emitter.sources.$category.shell = /bin/sh -c
emitter.sources.$category.restartThrottle =5000
emitter.sources.$category.restart =true
emitter.sources.$category.logStdErr =true
emitter.sources.$category.interceptors = i1 i2 i3
emitter.sources.$category.interceptors.i1.type = timestamp
emitter.sources.$category.interceptors.i2.type = host
emitter.sources.$category.interceptors.i2.useIP =false
emitter.sources.$category.interceptors.i3.type =static
emitter.sources.$category.interceptors.i3.key = category
emitter.sources.$category.interceptors.i3.value = $category EOF
}
} $conf .=<<EOF;
emitter.sources.avro1.channels = c1
emitter.sources.avro1.type = avro
emitter.sources.avro1.bind = localhost
emitter.sources.avro1.port = $g_emitter_avro_port
emitter.sources.avro1.interceptors = i1 i2 i3
emitter.sources.avro1.interceptors.i1.type = timestamp
emitter.sources.avro1.interceptors.i2.type = host
emitter.sources.avro1.interceptors.i2.useIP =false
emitter.sources.avro1.interceptors.i3.type =static
emitter.sources.avro1.interceptors.i3.key = category
emitter.sources.avro1.interceptors.i3.value =default emitter.sources.thrift1.channels = c1
emitter.sources.thrift1.type = thrift
emitter.sources.thrift1.bind = localhost
emitter.sources.thrift1.port = $g_emitter_thrift_port
emitter.sources.thrift1.interceptors = i1 i2 i3
emitter.sources.thrift1.interceptors.i1.type = timestamp
emitter.sources.thrift1.interceptors.i2.type = host
emitter.sources.thrift1.interceptors.i2.useIP =false
emitter.sources.thrift1.interceptors.i3.type =static
emitter.sources.thrift1.interceptors.i3.key = category
emitter.sources.thrift1.interceptors.i3.value =default emitter.sources.nc1.channels = c1
emitter.sources.nc1.type = netcat
emitter.sources.nc1.bind = localhost
emitter.sources.nc1.port = $g_emitter_nc_port
emitter.sources.nc1.max-line-length =20480
emitter.sources.nc1.interceptors = i1 i2 i3
emitter.sources.nc1.interceptors.i1.type = timestamp
emitter.sources.nc1.interceptors.i2.type = host
emitter.sources.nc1.interceptors.i2.useIP =false
emitter.sources.nc1.interceptors.i3.type =static
emitter.sources.nc1.interceptors.i3.key = category
emitter.sources.nc1.interceptors.i3.value =default emitter.channels.c1.type = file
emitter.channels.c1.checkpointDir = $g_flume_work_dir/emitter-c1/checkpoint
#emitter.channels.c1.useDualCheckpoints = true
#emitter.channels.c1.backupCheckpointDir = $g_flume_work_dir/emitter-c1/checkpointBackup
emitter.channels.c1.dataDirs = $g_flume_work_dir/emitter-c1/data EOF my $i =0;
my $port = $g_collector_avro_port;
my $onebox = is_one_box();
formy $host ( sort @g_collector_hosts){
++$i;
$port +=1000if $onebox; $conf .=<<EOF;
emitter.sinks.s$i.channel = c1
emitter.sinks.s$i.type = avro
emitter.sinks.s$i.hostname = $host
emitter.sinks.s$i.port = $port
emitter.sinks.s$i.batch-size =100
#emitter.sinks.s$i.reset-connection-interval = 600
emitter.sinks.s$i.compression-type = deflate EOF
} $conf .=<<EOF; emitter.sinkgroups.g1.sinks = $sinks
emitter.sinkgroups.g1.processor.type = load_balance
emitter.sinkgroups.g1.processor.backoff =true
emitter.sinkgroups.g1.processor.selector = round_robin EOF $conf =~ s/^+//mg; die"$g_emitter_conf already exists!\n"if! $g_overwrite_conf &&-e $g_emitter_conf;
open my $fh,">", $g_emitter_conf ordie"Can't write $g_emitter_conf: $!\n";
print $fh $conf;
close $fh;
} sub generate_collector_config {
my $conf ="";
my@sinks= qw(file1 hdfs1 hdfs2 hbase1);
my $sinks = join(" ",@sinks); my $port = $g_collector_avro_port;
my $onebox = is_one_box();
$port +=1000if $onebox; $conf .=<<EOF;
collector.sources = source1
collector.channels = $sinks
collector.sinks = $sinks collector.sources.source1.channels = $sinks
collector.sources.source1.type = avro
collector.sources.source1.bind =0.0.0.0
collector.sources.source1.port = $port
collector.sources.source1.compression-type = deflate
collector.sources.source1.interceptors = i1 i2 i3 i4 collector.sources.source1.interceptors.i1.type = timestamp
collector.sources.source1.interceptors.i1.preserveExisting =true collector.sources.source1.interceptors.i2.type = host
collector.sources.source1.interceptors.i2.preserveExisting =true
collector.sources.source1.interceptors.i2.useIP =false collector.sources.source1.interceptors.i3.type =static
collector.sources.source1.interceptors.i3.preserveExisting =true
collector.sources.source1.interceptors.i3.key = category
collector.sources.source1.interceptors.i3.value =default collector.sources.source1.interceptors.i4.type = host
collector.sources.source1.interceptors.i4.preserveExisting =false
collector.sources.source1.interceptors.i4.useIP =false
collector.sources.source1.interceptors.i4.hostHeader = collector EOF formy $sink (@sinks){
$conf .=<<EOF;
collector.channels.$sink.type = memory
collector.channels.$sink.capacity =10000
collector.channels.$sink.transactionCapacity =100
collector.channels.$sink.byteCapacityBufferPercentage =20
collector.channels.$sink.byteCapacity =0 EOF
} $conf .=<<EOF; collector.sinks.file1.channel = file1
collector.sinks.file1.type = file_roll
collector.sinks.file1.sink.directory = $g_data_dir/collector-$port-file1
collector.sinks.file1.sink.rollInterval =3600
collector.sinks.file1.batchSize =100
collector.sinks.file1.sink.serializer = text
collector.sinks.file1.sink.serializer.appendNewline =true
#collector.sinks.file1.sink.serializer = avro_event
#collector.sinks.file1.sink.serializer.syncIntervalBytes = 2048000
#collector.sinks.file1.sink.serializer.compressionCodec = snappy collector.sinks.hdfs1.channel = hdfs1
collector.sinks.hdfs1.type = hdfs
collector.sinks.hdfs1.hdfs.path = $g_hdfs_paths{hdfs1}/%{category}/%Y%m%d/%H
collector.sinks.hdfs1.hdfs.filePrefix =%{collector}-$port
collector.sinks.hdfs1.hdfs.rollInterval =600
collector.sinks.hdfs1.hdfs.rollSize =0
collector.sinks.hdfs1.hdfs.rollCount =0
collector.sinks.hdfs1.hdfs.idleTimeout =0
collector.sinks.hdfs1.hdfs.batchSize =100
collector.sinks.hdfs1.hdfs.codeC = snappy
collector.sinks.hdfs1.hdfs.fileType =SequenceFile
#collector.sinks.hdfs1.serializer = text
#collector.sinks.hdfs1.serializer.appendNewline = true
collector.sinks.hdfs1.serializer = avro_event
collector.sinks.hdfs1.serializer.syncIntervalBytes =2048000
collector.sinks.hdfs1.serializer.compressionCodec =null
#collector.sinks.hdfs2.serializer.compressionCodec = snappy collector.sinks.hdfs2.channel = hdfs2
collector.sinks.hdfs2.type = hdfs
collector.sinks.hdfs2.hdfs.path = $g_hdfs_paths{hdfs2}/%{category}/%Y%m%d/%H
collector.sinks.hdfs2.hdfs.filePrefix =%{collector}-$port
collector.sinks.hdfs2.hdfs.rollInterval =600
collector.sinks.hdfs2.hdfs.rollSize =0
collector.sinks.hdfs2.hdfs.rollCount =0
collector.sinks.hdfs2.hdfs.idleTimeout =0
collector.sinks.hdfs2.hdfs.batchSize =100
collector.sinks.hdfs2.hdfs.codeC = snappy
collector.sinks.hdfs2.hdfs.fileType =SequenceFile
#collector.sinks.hdfs2.serializer = text
#collector.sinks.hdfs2.serializer.appendNewline = true
collector.sinks.hdfs2.serializer = avro_event
collector.sinks.hdfs2.serializer.syncIntervalBytes =2048000
collector.sinks.hdfs2.serializer.compressionCodec =null
#collector.sinks.hdfs2.serializer.compressionCodec = snappy collector.sinks.hbase1.channel = hbase1
collector.sinks.hbase1.type = hbase
collector.sinks.hbase1.table = log
collector.sinks.hbase1.columnFamily = log EOF $conf =~ s/^+//mg; die"$g_collector_conf already exists!\n"if! $g_overwrite_conf &&-e $g_collector_conf;
open my $fh,">", $g_collector_conf ordie"Can't write $g_collector_conf: $!\n";
print $fh $conf;
close $fh;
} sub is_one_box {
my%h = map { $_ =>1}@g_collector_hosts;
return keys %h <@g_collector_hosts;
}

Apache Flume 简介的更多相关文章

  1. Apache Flume简介及安装部署

    概述 Flume 是 Cloudera 提供的一个高可用的,高可靠的,分布式的海量日志采集.聚合和传输的软件. Flume 的核心是把数据从数据源(source)收集过来,再将收集到的数据送到指定的目 ...

  2. Apache Flume日志收集系统简介

    Apache Flume是一个分布式.可靠.可用的系统,用于从大量不同的源有效地收集.聚合.移动大量日志数据进行集中式数据存储. Flume简介 Flume的核心是Agent,Agent中包含Sour ...

  3. Apache Flume 1.7.0 各个模块简介

    Flume简介 Apache Flume是一个分布式.可靠.高可用的日志收集系统,支持各种各样的数据来源,如http,log文件,jms,监听端口数据等等,能将这些数据源的海量日志数据进行高效收集.聚 ...

  4. Flume简介与使用(二)——Thrift Source采集数据

    Flume简介与使用(二)——Thrift Source采集数据 继上一篇安装Flume后,本篇将介绍如何使用Thrift Source采集数据. Thrift是Google开发的用于跨语言RPC通信 ...

  5. Apache Flume 安装文档、日志收集

    简介: 官网 http://flume.apache.org 文档 https://flume.apache.org/FlumeUserGuide.html hadoop 生态系统中,flume 的职 ...

  6. Flume 简介及基本使用

    一.Flume简介 Apache Flume是一个分布式,高可用的数据收集系统.它可以从不同的数据源收集数据,经过聚合后发送到存储系统中,通常用于日志数据的收集.Flume 分为 NG 和 OG (1 ...

  7. 入门大数据---Flume 简介及基本使用

    一.Flume简介 Apache Flume 是一个分布式,高可用的数据收集系统.它可以从不同的数据源收集数据,经过聚合后发送到存储系统中,通常用于日志数据的收集.Flume 分为 NG 和 OG ( ...

  8. Apache Flume 1.7.0 发布,日志服务器

    Apache Flume 1.7.0 发布了,Flume 是一个分布式.可靠和高可用的服务,用于收集.聚合以及移动大量日志数据,使用一个简单灵活的架构,就流数据模型.这是一个可靠.容错的服务. 本次更 ...

  9. org.apache.flume.FlumeException: NettyAvroRpcClient { host: xxx.xxx.xxx.xxx, port: 41100 }: RPC

    2014-12-19 01:05:42,141 (lifecycleSupervisor-1-1) [WARN - org.apache.flume.sink.AbstractRpcSink.star ...

随机推荐

  1. Ubuntu Linux 分区简易教程

    关于Linux系统下的“分区”问题,对于新手来说一直是很头疼的.我来简单写一下,它的“分区”方法,规则. 声明:我为了让没有接触过Linux系统的人,理解更加简单.所以在言语表述上不是很规范,专业.我 ...

  2. 非Page类使用session(Httpcontext.session和page.session区别)

    ASP.NET中Session高级使用技巧 在开发Aspx .NET软件时,有时需要把常用的东西封装到一个非PAGE类中,文章介绍在非Page类中使用Session的方法. 一.PAGE参数法: 1. ...

  3. 第一篇、Swift_搭建UITabBarController + 4UINavigationController主框架

    import UIKit class MainViewController: UITabBarController { override func viewDidLoad() { super.view ...

  4. (转)实战Memcached缓存系统(2)Memcached Java API基础之MemcachedClient

    1. 构造函数 public MemcachedClient(InetSocketAddress[] ia) throws IOException; public MemcachedClient(Li ...

  5. 函数 resize和reserve的区别

    reserve是容器预留空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素.加入新的元素时,要调用push_back()/insert()函数. resize是改变容 ...

  6. HTML5之拖放

    - Draggable 标签  文件拖放 99年IE5开始,05后所有浏览器支持(除了opera) <li id=be draggable=true ondragstart="star ...

  7. 【转】Android SDK Manager 更新方法

    在Android SDK Manager Setting 窗口设置HTTP Proxy server和HTTP Proxy Port这个2个参数,分别设置为: HTTP Proxy server:mi ...

  8. C++ 对数组sizeof 和对数组元素sizeof

    这一段程序 下面这段程序很有看点://arr1 is an array of intsint *source=arr1;size_t sz=sizeof(arr1)/sizeof(*arr1);//n ...

  9. Hibernate Cascade & Inverse

    Cascade - 修改实体表 Inverse - 修改中间表 http://www.cnblogs.com/amboyna/archive/2008/02/18/1072260.html 1.到底在 ...

  10. html实现层叠加

    <div id="canvasesdiv" style="position:relative; width:400px; height:300px"> ...