Flume 自定义拦截器、自定义不同主题、分区

 背景介绍、业务场景:

有的时候希望通过Flume将读取的文件再细分存储,

比如:source的数据按照业务类型分开存储,具体一点比如类似:将source中web、wap、media等的内容分开存储;

比如:过滤或修改一些数据。

比如在收集数据的event的hander中加入处理的时间戳,agent的主机或者IP,固定的key-valueg。

就可以考虑使用interceptors。

        Flume中的拦截器(interceptor),用户Source读取events发送到Sink的时候,在events header中加入一些有用的信息,或者对events的内容进行过滤,完成初步的数据清洗。interceptors 是 source 的拦截器 。主要的作用就是对于一个source 可以指定一个或者多个interceptors 按先后的顺序对数据进行处理 。

Flume有各种自带的拦截器,如:

Timestamp 拦截器:在Event Header中添加时间戳。
Host 拦截器:在Event Header中添加agent运行机器的Host或IP。
Static 拦截器:在Event Header中添加自定义静态属性。
Remove Header拦截器:可移除Event Header中指定属性。
UUID拦截器:在Event Header中添加全局唯一UUID。
Search and Replace拦截器:基于正则搜索和替换字符串等。
Regex Filtering拦截器:基于正则过滤或反向过滤Event。
Regex Extractor拦截器:基于正则在Event Header添加指定的Key,并将匹配到的内容作为对应的Value

通过使用不同的拦截器,实现不同的功能。但是以上的这些拦截器,不能改变原有日志数据的内容或者对日志信息添加一定的处理逻辑,当一条日志信息有几十个甚至上百个字段的时候,在传统的Flume处理下,收集到的日志还是会有对应这么多的字段,也不能对你想要的字段进行对应的处理。
          根据实际业务的需求,为了更好的满足数据在应用层的处理,通过自定义Flume拦截器,过滤掉不需要的字段,并对指定字段加密处理,将源数据进行预处理。减少了数据的传输量,降低了存储的开销。

一、自定义拦截器类型必须是:类全名$内部类名,其实就是内部类名称
  如:zhouls.bigdata.MySearchAndReplaceInterceptor$Builder

二、为什么这样写
  至于为什么这样写:是因为Interceptor接口还有一个 公共的内部接口(Builder) ,所以自定义拦截器 要是实现 Builder接口,也就是实现一个内部类(该内部类的主要作用是:获取flume-conf.properties 自定义的 参数,并将参数传递给 自定义拦截器)

内容包括:

1. 定义一个类CustomParameterInterceptor实现Interceptor接口。

2.  在CustomParameterInterceptor类中定义变量,这些变量是需要到 Flume的配置文件中进行配置使用的。每一行字段间的分隔符(fields_separator)、通过分隔符分隔后,所需要列字段的下标(indexs)、多个下标使用的分隔符(indexs_separator)、多个下标使用的分隔符(indexs_separator)。

3. 添加CustomParameterInterceptor的有参构造方法。并对相应的变量进行处理。将配置文件中传过来的unicode编码进行转换为字符串。

4. 写具体的要处理的逻辑intercept()方法,一个是单个处理的,一个是批量处理。

5. 接口中定义了一个内部接口Builder,在configure方法中,进行一些参数配置。并给出,在flume的conf中没配置一些参数时,给出其默认值。通过其builder方法,返回一个CustomParameterInterceptor对象。

6. 定义一个静态类,类中封装MD5加密方法

7.  通过以上步骤,自定义拦截器的代码开发已完成,然后打包成jar, 放到Flume的根目录下的lib中

flume自定义拦截器 - 简书

//只保留两个接口的数据
package deng.yb.flume_ng_Interceptor;
public class MyInterceptor implements Interceptor {
    /**
     * epp接口-request
     */
    private final String EPP_REQUEST = "POST /api/sky_server_data_app/track/user_time HTTP/1.1";
    /**
     * app接口-request
     */
    private final String APP_REQUEST = "POST /api/sky_server_data_app/code/app_code HTTP/1.1";
    public void close() {
    }
    public void initialize() {
    }
    public Event intercept(Event event) {
        String body = new String(event.getBody(), Charsets.UTF_8);
        if (body.indexOf(EPP_REQUEST) > -1 || body.indexOf(APP_REQUEST) > -1) {
            event.setBody(body.toString().getBytes());
            return event;
        }
        return null;
    }
    public List<Event> intercept(List<Event> events) {
        List<Event> intercepted = new ArrayList<>(events.size());
        for (Event event : events) {
            Event interceptedEvent = intercept(event);
            if (interceptedEvent != null) {
                intercepted.add(interceptedEvent);
            }
        }
        return intercepted;
    }
    public static class Builder implements Interceptor.Builder {
        public void configure(Context arg0) {
            // TODO Auto-generated method stub
        }
        public Interceptor build() {
            return new MyInterceptor();
        }
    }
}
epplog.sources.r1.interceptors=i1
epplog.sources.r1.interceptors.i1.type= deng.yb.flume_ng_Interceptor.MyInterceptor$Builder
  • 把自定义程序打好jar包放进$FLUME_HOME/lib文件夹下
  • 启动:bin/flume-ng agent -c conf -f conf/spool-interceptor-hdfs.conf -name a1 -Dflume.root.logger=DEBUG,console
  • 这样flume到kafka的数据就是帅选的信息后的,避免了大量没用信息到kafka导致IO问题

kafka均衡负载

  • 需要把消息均匀分布在不同brokers上,避免单台broker节点压力过大

 解决方案:

  • 向flume添加拦截器,会为每个event的head添加一个随机唯一的key,我们需要向header中写上随机的key,然后数据才会真正的向kafka分区进行随机发布
  • flume的agent添加和修改以下配置
epplog.sources.r1.interceptors=i1 i2
epplog.sources.r1.interceptors.i1.type= deng.yb.flume_ng_Interceptor.MyInterceptor$Builder

epplog.sources.r1.interceptors.i2.type=org.apache.flume.sink.solr.morphline.UUIDInterceptor$Builder
epplog.sources.r1.interceptors.i2.headerName=key
epplog.sources.r1.interceptors.i2.preserveExisting=false

 创建topic

#分区数需要根据brokers的数量决定,最好是brokers的整数倍
kafka-topics --create  --zookeeper bi-slave1:2181,bi-slave2:2181,bi-master:2181 
--replication-factor 1 --partitions 3 --topic epplog1
  • 修改flume的sink的topic,重启flume

  • 看到消息

     

  • 可以看到,消息自动uuid和帅选后的信息

  • 查看不同brokers该topic的分区
    1分区

    2分区

    3分区

  • 分区名格式为 topic-分区索引,索引从0开始算

  • 能看到,消息已经相对均匀分布在3个分区,也就是三台机器上面,从而达到kafka负载均衡

链接:https://www.jianshu.com/p/7b60f9f01d44

总结
kafka sink会自动从header中获取key的值,并以此值最为key发送到broker中。

kafka的分区规则是:

如果key为空,则随机发送到各个分区中。
key不为空,则根据Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions,类似于hash(keyBytes)对分区数取摸,来发送到对应的分区。

二、Kafka数据分类发送到不同topic,自定义拦截器

        依据Kafka Sink的配置在消息头中携带了topic字段的话,该消息就会被发送到topic字段对应的topic去。那么在flume接收到消息之后,可以通过拦截器为topic加上header,即可将其进行分类。

Flume拦截器如下:

public class JudgeTestStringInterceptor implements Interceptor {
    // 声明一个存放事件的List
    private List<Event> allEvents;
    public void initialize () {
        // 初始化
        allEvents = new ArrayList<Event>();
    }
    /**
     * 单个事件拦截
     * @param event
     * @return
     */
    public Event intercept (Event event) {
        // 1、获取事件中的头信息
        Map<String, String> headers = event.getHeaders();
        // 2、获取事件中的body信息
        String body = new String(event.getBody());
        // 3、根据body中是否有“test”来决定添加怎样的头信息
        // 有的话添加<topic, first>没有则添加<topic, second>
        if (body.contains("test")) {
            headers.put("topic", "first");
        } else {
            headers.put("topic", "second");
        }
        return event;
        // 如果返回null则认为该事件无用,将会被过滤
    }
    /**
     * 批量事件拦截
     * @param list
     * @return
     */
    public List<Event> intercept (List<Event> list) {
        // 1、清空集合
        allEvents.clear();
        // 2、遍历event
        for (Event event : list) {
            // 3、给每个事件添加头信息
            allEvents.add(intercept(event));
        }
        return allEvents;
    }
    public void close () {
    }
    // 定义一个Builder对象
    public static class Builder implements Interceptor.Builder {
        public Interceptor build () {
            return new JudgeTestStringInterceptor();
        }
        public void configure (Context context) {
        }
    }
}

配置文件type-kafka.conf如下:

# define
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# source
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444

# interceptor
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = com.starnet.interceptor.JudgeTestStringInterceptor$Builder

# sink
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.bootstrap.servers = hadoop113:9092,hadoop114:9092,hadoop115:9092
a1.sinks.k1.kafka.topic = first
a1.sinks.k1.kafka.flumeBatchSize = 20
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.kafka.producer.linger.ms = 1

# channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# bind
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

启动flume,两个消费者以及nc之后结果如下:

[bd@hadoop113 ~]$ nc localhost 44444
test
hello
word

[bd@hadoop113 ~]$ kafka-console-consumer.sh --zookeeper hadoop113:2181 --topic first
test

[bd@hadoop113 ~]$ kafka-console-consumer.sh --zookeeper hadoop113:2181 --topic second
hello
word

方法2:增加自定义分区类

        在自定义分区类的时候,如果和lib目录下的Kafka的版本和你自定义Kafka分区类时使用的API版本不一致,就会出现找不到对应类的异常。那么如何自定义对应版本的分区类呢?
最简单的方法就是找到你的安装的版本的Kafka的lib目录下的类似于kafka_2.10-0.10.2.1.jar的jar包,并将此jar包作为工程的lib引入,Maven工程直接添加对应版本的依赖即可,先以最新版本的Kafka版本(0.10.2.1)为例,对应Maven工程需要添加的依赖就是:

之后,实现你的jar包下的对应的Partition接口即可(推荐继承DefaultPartitioner,以免少实现了某些方法):

public class SimplePartitioner extends DefaultPartitioner {

    public SimplePartitioner() {
    }
    @Override
    public void configure(Map<String, ?> configs) {
    }

    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {

        String stringKey = String.valueOf(key);
        String[] keys = stringKey.split("\\.");
        System.out.println(Integer.valueOf(keys[3]) % 2);
        return Integer.valueOf(keys[3]) % 2;
    }

    @Override
    public void close() {
    }
}

        然后需要将你自定义的分区类的jar包以及所依赖的Kafka版本的jar包(不同版本的可能依赖的个数也不一样,最新版需要依赖两个jar包:kafka-clients-0.10.2.1.jar和kafka_2.10-0.10.2.1.jar),放到flume的安装目录的lib文件夹下,并将其他版本的Kafka的jar包删除。
最后需要提醒的是一定要注意版本一致性。你用什么版本的kafka就需要将对应版本的jar包放到对应目录下,否则有时会找不到。
# sink 1 配置
kafka_key.sinks.sink1.type = org.apache.flume.sink.kafka.KafkaSink
kafka_key.sinks.sink1.brokerList = bigdata-001:9092,bigdata-002:9092,bigdata-003:9092
kafka_key.sinks.sink1.topic = test_key
kafka_key.sinks.sink1.channel = channel1
kafka_key.sinks.sink1.batch-size = 100
kafka_key.sinks.sink1.requiredAcks = -1
# kafka_key.sinks.sink1.kafka.partitioner.class = com.lxw1234.flume17.SimplePartitioner
 

自定义分区实现kafka有序(示例代码)

1)Source中使用拦截器

kafka_key.sources.sources1.interceptors = i1
kafka_key.sources.sources1.interceptors.i1.type = com.bigdata.flume.MyInterceptor$Builder

kafka_key.sources.sources1.interceptors = i1
kafka_key.sources.sources1.interceptors.i1.type = regex_extractor
kafka_key.sources.sources1.interceptors.i1.regex = .*?\|(.*?)\|.*
kafka_key.sources.sources1.interceptors.i1.serializers = s1
kafka_key.sources.sources1.interceptors.i1.serializers.s1.name = key

2)sink中使用自定义的partitioner类:
kafka_key.sinks.sink1.kafka.partitioner.class = com.lxw1234.flume17.SimplePartitioner

自定义的分区类:
public class BehaviorInterceptor implements Interceptor
{
    @Override
    public void initialize() {

    }

    @Override
    public Event intercept(Event event) {
        //如果event为空过滤掉
        if(event == null || event.getBody() == null || event.getBody().length == 0){
            return null;
        }

        long userId = 0;

        //解析日志
        try{
            UserBehaviorRequestModel model = JSONUtil.json2Object(new String(event.getBody()),UserBehaviorRequestModel.class);
            userId = model.getUserId();
        }catch (Exception e){
            e.printStackTrace();
        }

        if(userId == 0){
            return null;
        }
        //将userId赋值给key
        event.getHeaders().put("key",userId+"");

        return event;
    }

    @Override
    public List<Event> intercept(List<Event> events) {

        List<Event> out = Lists.newArrayList();
        for (Event event : events) {
            Event outEvent = intercept(event);
            //event 为空过滤掉
            if (outEvent != null) { out.add(outEvent); }
        }
        return out;
    }

    @Override
    public void close() {

    }
    //程序入口
    public static class BehaviorBuilder implements Interceptor.Builder {

        @Override
        public Interceptor build() {
            return new BehaviorInterceptor();
        }

        @Override
        public void configure(Context context) {

        }
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四月天03

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值