在大数据行业,flume作为一款常用的日志采集工具被广泛地应用到各个实时或离线的分析系统中,各种技术文章也基本能解释清楚其基础原理。但很多时候的应用需求希望能够简化其使用,我们就需要对其进行功能上的扩充,Flume的灵活性也就给了我们很大的帮助。
网络上千篇一律地介绍了flume的基本工作原理,我就简单地搬个图过来,具体的大家可以自行搜索先了解了解,当然我们也会在接下来的描述中介绍到其部分原理。
从图中可以很容易地看出,flume NG(后续简称flume)分为三大块:source, channel, sink,source负责从数据源获取数据,将数据生产到channel,channel作为一个消息队列的形式,最后由sink消费掉channel中的数据。这种形式就使得其相当地容易进行组合,用户可以将其中任意一个环节设置成其需要的模块:获取不同的数据源、选择不同的消息队列、落地到不同的存储。
从源码层来看,三者均继承自LifecycleAware, NamedComponent这两个接口。
需要实现LifecycleAware 的start、stop以及getLifecycleState三个方法,实现NamedComponent的setName和getName方法。在flume启动的时候,主函数org.apache.flume.node.Application会将配置文件或zookeeper中的参数加载到参数对象中,然后调用setName以及start方法。
在初始化过程中,配置参数的同时,会调用继承自AbstractConfigurationProvider的类,然后根据LifecycleAware的子类是Source、Channel或是Sink来调用其中的其他的方法。
看源码:
1.source
public interface Source extends LifecycleAware, NamedComponent {
/**
_ * Specifies which channel processor will handle this source's events._
_ *_
* @param channelProcessor
*/
public void setChannelProcessor(ChannelProcessor channelProcessor);
/**
_ * Returns the channel processor that will handle this source's events._
_ */_
public ChannelProcessor getChannelProcessor();
}
2.Channel
public interface Sink extends LifecycleAware, NamedComponent {
/**
* <p>Sets the channel the sink will consume from</p>
* @param channel The channel to be polled
*/
public void setChannel(Channel channel);
/**
* @return the channel associated with this sink
*/
public Channel getChannel();
/**
* <p>Requests the sink to attempt to consume data from attached channel</p>
* <p><strong>Note</strong>: This method should be consuming from the channel
* within the bounds of a Transaction. On successful delivery, the transaction
* should be committed, and on failure it should be rolled back.
* @return READY if 1 or more Events were successfully delivered, BACKOFF if
* no data could be retrieved from the channel feeding this sink
* @throws EventDeliveryException In case of any kind of failure to
* deliver data to the next hop destination.
*/
public Status process() throws EventDeliveryException;
public static enum Status {
READY, BACKOFF
}
}
3.Sink
public interface Sink extends LifecycleAware, NamedComponent {
/**
* <p>Sets the channel the sink will consume from</p>
* @param channel The channel to be polled
*/
public void setChannel(Channel channel);
/**
* @return the channel associated with this sink
*/
public Channel getChannel();
/**
* <p>Requests the sink to attempt to consume data from attached channel</p>
* <p><strong>Note</strong>: This method should be consuming from the channel
* within the bounds of a Transaction. On successful delivery, the transaction
* should be committed, and on failure it should be rolled back.
* @return READY if 1 or more Events were successfully delivered, BACKOFF if
* no data could be retrieved from the channel feeding this sink
* @throws EventDeliveryException In case of any kind of failure to
* deliver data to the next hop destination.
*/
public Status process() throws EventDeliveryException;
public static enum Status {
READY, BACKOFF
}
}
官方提供了一些可用的实现
Source
- AvroSource 基于Socket协议和json方式序列化的服务,用户可以配置可信的ip或者hostname来决定客户端发送的数据是否接收,安全机制上,还能设置ssl的验证,常用于多地机房的数据中转。
- ExecSource
- MultiportSyslogTCPSource
- NetcatSource
- NetcatUdpSource
- SequenceGeneratorSource
- SpoolDirectorySource 常用的对目录文件数据的输入
- StressSource
- SyslogTcpSource
- SyslogUDPSource
- ThriftSource
- HTTPSource
Channel
- FileChannel 最常使用的方式,对于数据量比较大的系统,内存通道方式很容易产生OOM,虽然内存方式效率极高,但是仅适用于数据量小,或者落地效率极高的场景。
- JdbcChannel
- SpillableMemoryChannel
- MemoryChannel
- PseudoTxnMemoryChannel
Sink
- HDFSEventSink
- HiveSink 该方式必须是Hive的桶表,并且开启了事务(Orc列存储方式),该方式会调用HiveEndpoint来写数据,我接触过的系统大多使用spark做数据处理,而该方式的hive表通过jdbc或者是orc的方式均不能正常加载数据到spark(目前spark2.2的orc和hive1.2的orc文件不兼容),所以直接利用HDFS更佳。
- IRCSink
- KafkaSink 实时系统常用的,通常情况下,会开一个Thrift的source,或者根据业务的协议自定义一个基于socket的服务的source,通过interceptor将上报的字节码数据转换成需要的json数据,然后存放到kafka中,再到线上利用实时系统做一些数据分发或者实时分析的业务。后续一般也就是从kafka读一份存到数据仓库,然后另外几条线都是实时的分析任务。
- AvroSink 该方式相当于开启了一个Avro的客户端,将数据发送至Avro服务。一般是配合source端的Avro将数据传递到另外的机房的数据仓库。
- LoggerSink
- NullSink
- RollingFileSink
- ThriftSink
然后大家常用到的一些第三方的实现
Source
- org.keedio.flume.source.SQLSource 这是一个jdbc数据源的source,被大家广泛使用。该source会调用hibernate的功能块获取jdbc数据源的数据,然后转换成csv格式的数据(使用的opencsv),经常会出现一些奇怪的换行符,所以我们在实际使用中替换成json的array形式表示一行数据,在落地前转换为需要的数据结构。
实际的需求
对于定义好的业务,以现有的对配置文件的形式来指定配置最为稳定,或者以统一的zookeeper来管理配置文件。但是对于复杂业务环境,往往官方的东西不足以支撑,所以需要自定义一些特性。
常见的使用方式:
多个数据源的数据结构相同,此时可以通过多个source将数据汇总到一个channel中,最后由多个sink并行地写入到存储中。
一个数据源需要复制多份,用于不同的系统业务,此时由一个source分发到多个channel中,每个channel的数据都是一个相同的副本;再由不同的sink输出到不同业务的存储中。
由此可以看出,如果想要产生多个副本,一个数据源可以通过多个channel来实现对副本的增加,一个channel始终对应一份副本,sink数量增加相当于提高了数据写入到存储的并行度。多数据源汇总其实意义不大,因为大多数落地后也就汇总了。
我们常见的问题:
1. 数据源与数据仓库不在同一机房,且数据源只能通过内网访问,系统需要在页面端实现一套业务一个同一配置,数据源采集与落地仓库的采集服务需要自动在可用端口段分配端口用于avro的交换方式,原则上分配了两个采集,需要人员在两台机器上配置不同的参数,维护的时候需要先停止输入端的服务,再停止落地端的服务,相当麻烦。如果由统一的调度自动获取可用端口,分配给输出源的采集服务,自动生成数据桥接的配置,然后由服务分发给两端的服务,将极大简化任务的调度。
2. 很多时候由于系统资源不足,不足以同时启动多个采集任务,需要分时间段或根据数据采集情况随时启停某些采集任务。单一的定时任务控制不一定能满足判断启停条件,某些时候停止标识需要配合监控到的一定时间段内的数据流量来触发。
3. 多个flume任务的管理相对分散,不易于管理(flume的主函数始终是Application,查询agent需要根据java执行的命令来判断,相对复杂)。
鉴于常见的使用方式以及存在的一些问题,采用统一的服务对flume的agent进程进行管理的需求就比较迫切。我根据我们常用的服务简化了一层轻量级的服务,用于对flume任务的控制,命名为Flume RESTful Driver,以下简称driver。
Driver采用spring boot作为http rest服务框架。提供了启动、监控以及停止功能的rest请求接口。使用Spring ApplicationContext(StaticApplicationContext)以bean形式寄存每一个用户定义的agent。我们合理地使用了bean的单例模式,保证同一个agent只存在一个副本,这样在每一次操作任务的时候保证通过agentName就能获取到正确的任务对象,并且获取其配置信息以及counter中的监控信息,以json方式展示。
Driver基本原理
1.提交任务,此时需要用户传递requestbody,包含agentName,source(包括其对应的一个interceptor),channels,sinks的信息。服务接收到json对象后,根据agentName创建spring bean对象,再根据json对象初始化为flume的配置参数,然后传递给bean,最后调用bean的方法创建flume的agent,此时agent被启动。
2.查询指定的agent信息,服务接收到agentName之后,向ApplicationContext获取对象,然后获取其中保存的参数值,再由jmx对象中抽取到flume中对应的counter信息。最后将信息整合后返回给用户。
3.查询agent列表,服务接收到请求后,从ApplicationContext将所有bean name以json形式返回给用户。
4.停止任务,用户指定需要停止的agentName,服务从ApplicationContext获取到对应的bean,然后调用对象中的stop方法,会立即调用Application的stop方法,停止jmx监控,然后将各个LifecycleAware停止。最终再移除ApplicationContext中对应的bean对象。
项目地址:
https://github.com/matrixzoo/flume-restful-driver
安全性的问题
Spring boot本身提供了大量的成熟的组件,目前项目没有增加任何安全验证,后续会在项目中更新。实际的业务中,通常会使用spring-boot-starter-security模块,在配置文件中配置一个可用的用户密码作为一种简单的安全验证。项目中大多数情况下直接使用硬件防火墙对ip做限制了,这一层其实也可以不加。这个需要具体看实际的业务情况。
driver项目的一些计划
我们在大多数的项目中都采用了一个统一的调度管理界面,但是定制化程度较高,不适用于普遍情况,未来会扩展加入一个统一调度的服务,但是要权衡大多数的使用场景,而且最好是轻量级的,易于做二次开发的。
目前flume提供的组件只能说适应了大部分情况下数据从传统数据库到大数据平台的,但其架构不失为一个良好的etl工具,后期会拓展一些我认为常用的自定义的source以及sink。
同时也希望有志同道合的朋友加入到该项目的开发中,拓展一些轻量级的功能。该项目以轻量级、易拓展为原则。
Sherard Lee
2018.08.29