Flume最重要的组件是Source、Channel和Sink,另外,Flume Agent还有一些使Flume更加灵活的组件,如拦截器,Channel选择器,Sink组和Sink选择器。本文将讨论一下拦截器的应用。
拦截器(Interceptor)是简单的插入式组件,设置在Source和Source写入数据的Channel之间,Source接收到的事件在写入到Channel之前,拦截器都可以对时间进行拦截,转换或删除这些时间。每个拦截器实例只处理同一个Source接受到的事件。
可以添加任意数量的拦截器去处理从单个Source传来的事件,Source将同一个事务中的所有事件传递给Channel处理器,进而传递给拦截器链,然后事件被传递给拦截器链的第一个拦截器,之后对事件进行转换处理,往下一个拦截器传递,依次直到最后一个拦截器返回的事件写入到Channel中。
拦截器必须在事件写入到Channel之前完成处理,因此在拦截器中进行大量的耗时处理不太合适,如果拦截器的处理非常耗时,需要相应调整响应超时时间。防止由于长时间没有响应发送事件的客户端或者Sink,而导致超时。
拦截器是需要命名的组件,每个拦截器都需要限定一个名字。拦截器的配置需要以interceptor开头、后面跟着拦截器的名称,以及配置项名称。
下面是拦截器配置示例
a1.sources = r1
a1.sinks = k1
a1.channels = c1
a1.sources.r1.interceptors = i1 i2
a1.sources.r1.interceptors.i1.type = org.apache.flume.interceptor.HostInterceptor$Builder
a1.sources.r1.interceptors.i1.preserveExisting = false
a1.sources.r1.interceptors.i1.hostHeader = hostname
a1.sources.r1.interceptors.i2.type = org.apache.flume.interceptor.TimestampInterceptor$Builder
a1.sinks.k1.filePrefix = FlumeData.%{CollectorHost}.%Y-%m-%d
a1.sinks.k1.channel = c1
Flume中最常用的拦截器是时间戳拦截器,该拦截器将时间戳插入到Flume事件的报头,附带的timeStamp是HDFS Sink用来分桶的报头。如果时间戳报头已经存在,则会替换该时间戳报头,除非preserveExisting参数设置为true。该拦截器经常用在第一层agent,用于从客户端接受数据。
参数 | 描述 |
---|---|
type | timestamp |
preserveExisting | 默认值false。如果设置为true,若时间戳报头以及存在,则不会替换该时间戳报头 |
配置示例
a1.sources = r1
a1.channels = c1
a1.sources.r1.channels = c1
a1.sources.r1.type = seq
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = timestamp
主机拦截器插入服务器的IP地址或者主机名,Agent将这些内容注入到Flume的事件报头中,事件报头中的键使用hostHeader配置,默认值为host。如果事件报头在事件中已经存在,则会替换该事件报头,除非preserveExisting参数设置为true。将useIP参数设置为false,插入的主机名会替换ip地址。
参数 | 描述 |
---|---|
type | host |
hostHeader | 默认host,事件的头,用于插入ip地址或者主机名 |
useIP | 如果设置为true,host键插入的是IP地址 |
preserveExisting | 默认值false。如果设置为true,若报头存在,则不会替换该报头 |
a1.sources = r1
a1.channels = c1
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = host
a1.sources.r1.interceptors.i1.hostHeader = hostname
静态拦截器可以简单的将固定报头的键和值插入拦截的每个事件中。
参数 | 描述 |
---|---|
type | static |
key | 默认key,报头的键 |
value | 默认value,报头键对应的值 |
preserveExisting | 默认值false。如果设置为true,若该报头已经存在,则不会替换该报头 |
a1.sources = r1
a1.channels = c1
a1.sources.r1.channels = c1
a1.sources.r1.type = seq
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = static
a1.sources.r1.interceptors.i1.key = datacenter
a1.sources.r1.interceptors.i1.value = NEW_YORK
该拦截器可以用于过滤事件,每个正则过滤器拦截将事件体转换为UTF-8的字符串,使用该字符串基于配置的正则表达式去匹配,如果匹配成功,则通过该事件或者抛弃该事件。
参数 | 描述 |
---|---|
type | regex_filter |
regex | 默认.* 正则表达式 |
excludeEvents | 默认false,如果为true,匹配上的事件会丢弃。 |
示例
a1.sources.r1.interceptors.i1.regex = (\\d):(\\d):(\\d)
a1.sources.r1.interceptors.i1.excludeEvents= true
此拦截器使用指定的正则表达式提取regex捕获组,并将匹配组追加到事件的报头。它还支持在将匹配组添加为事件报头之前对其进行格式化。
参数 | 描述 |
---|---|
type | regex_extractor |
regex | 默认.* 正则表达式 |
serializers | 空格分割的名称,对应正则匹配的捕获组 |
serializers. |
报头的键名 |
serializers. |
默认default (org.apache.flume.interceptor.RegexExtractorInterceptorPassThroughSerializer),还有org.apache.flume.interceptor.RegexExtractorInterceptorMillisSerializer,或者自定义实现org.apache.flume.interceptor.RegexExtractorInterceptorSerializer |
默认的序列化器,org.apache.flume.interceptor.RegexExtractorInterceptorPassThroughSerializer,只将匹配项映射到指定的头名称,并在regex提取值时传递该值。可以自定义序列化器实现更多功能,以任意方式格式化匹配项。自定义的类需要实现org.apache.flume.interceptor.RegexExtractorInterceptorSerializer 接口。
示例一
a1.sources.r1.interceptors.i1.regex = (\\d):(\\d):(\\d)
a1.sources.r1.interceptors.i1.serializers = s1 s2 s3
a1.sources.r1.interceptors.i1.serializers.s1.name = one
a1.sources.r1.interceptors.i1.serializers.s2.name = two
a1.sources.r1.interceptors.i1.serializers.s3.name = three
示例二
a1.sources.r1.interceptors.i1.regex = ^(?:\\n)?(\\d\\d\\d\\d-\\d\\d-\\d\\d\\s\\d\\d:\\d\\d)
a1.sources.r1.interceptors.i1.serializers = s1
a1.sources.r1.interceptors.i1.serializers.s1.type = org.apache.flume.interceptor.RegexExtractorInterceptorMillisSerializer
a1.sources.r1.interceptors.i1.serializers.s1.name = timestamp
a1.sources.r1.interceptors.i1.serializers.s1.pattern = yyyy-MM-dd HH:mm
拦截器可以为每个事件生成唯一的标识符,生成的UUID可以设置为可配置的参数,还可以为UUID生成相应的前缀。
参数 | 描述 |
---|---|
type | org.apache.flume.sink.solr.morphline.UUIDInterceptor$Builder |
headerName | 报头名称 |
preserveExisting | 默认true,如果UUID已存在,保留不覆盖。 |
prefix | 生成UUID的前缀 |
a1.sources.r1.interceptors.i1.type = org.apache.flume.sink.solr.morphline.UUIDInterceptor$Builder
a1.sources.r1.interceptors.i1.headerName = prefix-
a1.sources.r1.interceptors.i1.preserveExisting = false
拦截器是Flume中最容易编写的组件,只需要实现Interceptor接口。该接口本身非常简单,Flume本身要求所有的拦截器必须有一个实现Interceptor$Builder接口的Builder类。所有的Builde类必须有一个公共的无参构造方法。Flume使用该方法完成实例化,可以使用传递到Builder类的Context实例配置拦截器,所有需要的参数都传递到Context实例。
拦截器一般用于拦截,转换事件,通常给拦截的事件插入事件报头,这些事件用于后续的HDFS Sink(用于时间戳或者基于报头的分桶),Hbase Sink(用于行键)等。这些事件报头也经常在复杂的Channel处理器中用于将流分为多个流的分支,或者基于优先级将事件发送到不同的Sink中。
Interceptor接口
public interface Interceptor {
void initialize();
Event intercept(Event var1);
List intercept(List var1);
void close();
public interface Builder extends Configurable {
Interceptor build();
}
}
可以看到有两种处理事件的方法,第一种方法接受一个事件返回一个事件列表,第二种方法 可以接受一个事件列表并返回一个事件列表。这两个方法必须都是线程安全的,因为如果Source运行在多线程环境下,这些方法可能被多个线程调用。
整体流程:
public void initialize()运行前的初始化,一般不需要实现
综上所述,实现Interceptor
,自定义flume拦截器主要实现以下3个方法、接口
需求:实现过滤某个不需要的字段,并加密另外一个字段
# 加密 |不处理|不处理|过滤|不处理
13504245101,male,11,chifan,bei
13717457125,female,22,shuijue,shang
18451762413,male,33,dadoudou,guang
xxxxxxxxxxx,male,11,bei
xxxxxxxxxxx,female,22,shang
xxxxxxxxxxx,male,33,guang
org.apache.flume
flume-ng-core
1.6.0-cdh5.14.2
public class MyInterceptor implements Interceptor{
//需要加密字段的坐标
private final String encrypted_field_index;
//需要过滤字段的坐标
private final String filter_field_inedex;
public MyInterceptor(String encrypted_field_index,String filter_field_inedex){
this.encrypted_field_index = encrypted_field_index;
this.filter_field_inedex = encrypted_field_index;
}
@Override
public void initialize() {
//do nothing
}
@Override
public Event intercept(Event event) {
if (event == null){
return null;
}
try {
{
//每个event由header和body组成,body是数据的载体
String line = new String(event.getBody(), Charsets.UTF_8);
String[] fields = line.split(" ");
String newLine = "";
for (int i = 0; i < fields.length; i++) {
int encryptedIndex = Integer.parseInt(encrypted_field_index);
int filterIndex = Integer.parseInt(filter_field_inedex);
if (i == encryptedIndex){
newLine+=md5(fields[i] + ",");
}else if (i !=filterIndex){
newLine += fields[i] + ",";
}
}
newLine = newLine.substring(0,newLine.length() - 1);
event.setBody(newLine.getBytes(Charsets.UTF_8));
return event;
}
}catch (Exception e){
return event;
}
}
@Override
public List intercept(List events) {
return null;
}
@Override
public void close() {
//do nothing
}
/**
* 相当于自定义Interceptor的工厂类
* 在flume采集配置文件中通过制定该Builder来创建Interceptor对象
* 可以在Builder中获取、解析flume采集配置文件中的拦截器Interceptor的自定义参数:
* 指定需要加密的字段下标 指定不需要对应列的下标等
* @author
*
*/
public static class MyBuilder implements Interceptor.Builder{
//需要加密的字段下标
private String encrypt_field_index;
//需要过滤的字段下标
private String filter_field_index;
@Override
public Interceptor build() {
return new MyInterceptor(encrypt_field_index,filter_field_index);
}
@Override
public void configure(Context context) {
this.encrypt_field_index = context.getString("encrypt_field_index","");
this.filter_field_index = context.getString("filter_field_index","");
}
}
//md5加密的方法
public static String md5(String plainText) {
//定义一个字节数组
byte[] secretBytes = null;
try {
// 生成一个MD5加密计算摘要
MessageDigest md = MessageDigest.getInstance("MD5");
//对字符串进行加密
md.update(plainText.getBytes());
//获得加密后的数据
secretBytes = md.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("md5 error");
}
//将加密后的数据转换为16进制数字
String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字
// 如果生成数字未满32位,需要前面补0
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
}
# name the agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# config the source
a1.sources.r1.type = exec
a1.sources.r1.command = tail -f /flume/testData.txt
a1.sources.r1.channels = c1
a1.sources.r1.intercepotrs = i1
#MyBuilder为MyInterceptor的内部类
a1.sources.r1.interceptors.i1=type = com.interceptor.MyInterceptor$MyBuilder
# 这的字段一定要和代码中的config中的字段相等
a1.sources.r1.interceptors.i1.encrypt_field_index = 0
a1.sources.r1.interceptors.i1.filter_field_index = 3
# channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.channels = c1
a1.sinks.k1.hdfs.path = hdfs://node01:8020/Myinterceptor/%Y-%m-%d/%H%M
a1.sinks.k1.hdfs.filePrefix =events-
a1.sinks.k1.hdfs.round = true
a1.sinks.k1.hdfs.roundValue = 10
a1.sinks.k1.hdfs.roundUnit = minute
a1.sinks.k1.hdfs.rollInterval = 5
a1.sinks.k1.hdfs.rollSize = 50
a1.sinks.k1.hdfs.rollCount = 10
a1.sinks.k1.hdfs.batchSize = 100
a1.sinks.k1.hdfs.useLocalTimeStamp = true
a1.sinks.k1.hdfs.fileType = DataStream
CounterInterceptor 类的拦截方法是线程安全的,因为变量是由final修饰的,或者是Atomic原子操作。如果需要拦截该事件,则返回null即可,如果是事件列表,则必须返回一个事件列表,即使为空,也必须返回列表。
拦截器的调用是由Channel处理器来完成的,Channel处理器会首先实例化Builder类,然后调用Builder类的configure方法,该方法用于传递包含配置拦截器的Context实例。然后Channel处理器调用build方法,该方法返回拦截器。Channel处理器通过调用拦截器实例的Initialize方法初始化拦截器。
intercept(Event event)
方法intercept(List events)
方法。Interceptor
.Builder接口