04-系统内置拦截器使用&自定义拦截器

本文详细介绍了Apache Flume中的拦截器机制,包括TimestampInterceptor、HostInterceptor和StaticInterceptor的用法,并通过实例展示了如何配置时间戳、主机名和静态头拦截器。此外,还探讨了Channel选择器的类型,如replicating和multiplexing,以及它们在事件路由中的作用。最后,给出了自定义拦截器的实现步骤和配置示例,该拦截器根据事件体内容动态路由到不同的HDFS路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系统内置拦截器使用

在Flume运行过程中,Flume有能力在运行阶段修改/删除Event,这是通过拦截器((Interceptors)来实现的。拦截器有下面几个特点:

  • 拦截器需要实现org.apache.flume.interceptor.Interceptor接口。
  • 拦截器可以修改或删除事件基于开发者在选择器中选择的任何条件。
  • 拦截器采用了责任链模式,多个拦截器可以按指定顺序拦截。
  • 一个拦截器返回的事件列表被传递给链中的下一个拦截器。
  • 如果一个拦截器需要删除事件,它只需要在返回的事件集中不包含要删除的事件即可。

常用拦截器:

  1. Timestamp Interceptor :时间戳拦截器,将当前时间戳((毫秒)加入到events header中,key名字为: timestamp,值为当前时间戳。用的不是很多
  2. Host Interceptor:主机名拦截器。将运行Flume agent的主机名或者IP地址加入到events header中,key名字为: host (也可自定义)
  3. Static Interceptor:静态拦截器,用于在events header中加入一组静态的key和value。

案例演示

通过时间拦截器,数据源为SyslogTcp,传送的通道模式是FileChannel,最后输出的目的地为HDFS

配置方案

[root@tianqinglong01 flumeconf]# vi ts.conf
a1.sources = r1
a1.channels = c1
a1.sinks = s1

a1.sources.r1.type=syslogtcp
a1.sources.r1.host=tianqingglong01
a1.sources.r1.port=6666
a1.sources.r1.interceptors=i1 i2 i3
a1.sources.r1.interceptors.i1.type=timestamp
a1.sources.r1.interceptors.i1.preserveExisting=false
a1.sources.r1.interceptors.i2.type=host
a1.sources.r1.interceptors.i2.preserveExisting=false
a1.sources.r1.interceptors.i2.useIP=true
a1.sources.r1.interceptors.i2.hostHeader=hostname
a1.sources.r1.interceptors.i3.type=static
a1.sources.r1.interceptors.i3.preserveExisting=false
a1.sources.r1.interceptors.i3.key=hn
a1.sources.r1.interceptors.i3.value=tianqinglong01

a1.channels.c1.type=memory

a1.sinks.s1.type=hdfs
a1.sinks.s1.hdfs.path=hdfs://tianqinglong01:8020/flume/%Y/%m/%d/%H%M
a1.sinks.s1.hdfs.filePrefix=%{hostname}
a1.sinks.s1.hdfs.fileSuffix=.log
ai.sinks.s1.hdfs.inuseSuffix=.tmp
a1.sinks.s1.hdfs.rollInterval=60
a1.sinks.s1.hdfs.rollSize=1024
a1.sinks.s1.hdfs.rollCount=10
a1.sinks.s1.hdfs.idleTimeout=0
a1.sinks.s1.hdfs.batchSize=100
a1.sinks.s1.hdfs.fileType=DataStream
a1.sinks.s1.hdfs.writeFormat=Text
a1.sinks.s1.hdfs.round=true
a1.sinks.s1.hdfs.roundvalue=1
ai.sinks.s1.hdfs.roundUnit=second
ai.sinks.s1.hdfs.useLocalTimeStamp=true

a1.sources.r1.channels=c1
a1.sinks.s1.channel=c1

启动agent的服务

[root@tianqinglong flumeconf]# flume-ng agent -c ../conf -f ./ts.conf -n a1 -Dflume.root.logger=INFO,console

测试

[root@tianqinglong ~]# echo "hello world hello interceptor" | nc tianqinglong01 6666

# nc需要安装
yum install -y nmap-ncat

选择器的使用

说明

Flume中的Channel选择器作用于source阶段,是决定Source接受的特定事件写入到哪个Channe的组件,他们告诉Channel处理器,然后由其将事件写入到Channel。

Agent中各个组件的交互

由于Flume不是两阶段提交,事件被写入到一个Channel,然后事件在写入下一个Channel之前提交,如果写入一个Channel出现异常,那么之前已经写入到其他Channel的相同事件不能被回滚。当这样的异常发生时,Channel处理器抛出ChanelException异常,事务失败,如果Source试图再次写入相同的事件(大多数情况下,会再次写入,只有Syslog,Exec等Source不能重试,因为没有办法生成相同的数据),重复的事件将写入到Channel中,而先前的提交是成功的,这样在Flume中就发生了重复。

Channel选择器的配置是通过Channel处理器完成的,Channel选择器可以指定一组Channel是必须的,另一组的可选的。

Flume分类两种选择器,如果Source配置中没有指定选择器,那么会自动使用复制Channel选择器.

  • replicating:该选择器复制每个事件到通过Source的Channels参数指定的所有Channel中。
  • multiplexing:是一种专门用于动态路由事件的Channel选择器,通过选择事件应该写入到哪个Channel,基于一个特定的事件头的值进行路由

案例演示: replicating selector

配置方案

[root@tianqinglong01 flumeconf]# vi rep.conf
a1.sources = r1
a1.channels = c1 c2
a1.sinks = s1 s2

a1.sources.r1.type=syslogtcp
a1.sources.r1.host = tianqinglong01
a1.sources.r1.port = 6666
a1.sources.r1.selector.type=replicating

a1.channels.c1.type=memory

a1.channels.c2.type=memory

a1.sinks.s1.type=hdfs
a1.sinks.s1.hdfs.path=hdfs://tianqinglong01:8020/flume/%Y/%m/%d/rep
a1.sinks.s1.hdfs.filePrefix=s1sink
a1.sinks.s1.hdfs.fileSuffix=.log
a1.sinks.s1.hdfs.inuseSuffix=.tmp
a1.sinks.s1.hdfs.rollInterval=60
a1.sinks.s1.hdfs.rollSize=1024
a1.sinks.s1.hdfs.rollCount=10
a1.sinks.s1.hdfs.idleTimeout=0
a1.sinks.s1.hdfs.batchSize=100
a1.sinks.s1.hdfs.fileType=DataStream
a1.sinks.s1.hdfs.writeFormat=Text
a1.sinks.s1.hdfs.round=true
a1.sinks.s1.hdfs.roundvalue=1
a1.sinks.s1.hdfs.roundUnit=second
a1.sinks.s1.hdfs.useLocalTimeStamp=true

a1.sinks.s2.type=hdfs
a1.sinks.s2.hdfs.path=hdfs://tianqinglong01:8020/flume/%Y/%m/%d/rep
a1.sinks.s2.hdfs.filePrefix=s2sink
a1.sinks.s2.hdfs.fileSuffix=.log
a1.sinks.s2.hdfs.inUseSuffix=.tmp
a1.sinks.s2.hdfs.rollInterval=60
a1.sinks.s2.hdfs.rollSize=1024
a1.sinks.s2.hdfs.rollCount=10
a1.sinks.s2.hdfs.idleTimeout=0
a1.sinks.s2.hdfs.batchSize=100
ai.sinks.s2.hdfs.fileType=Datastream
a1.sinks.s2.hdfs.writeFormat=Text
a1.sinks.s2.hdfs.round=true
a1.sinks.s2.hdfs.roundValue=1
a1.sinks.s2.hdfs.roundunit=second
a1.sinks. s2.hdfs.useLocalTimeStamp=true

a1.sources.r1.channels=c1 c2
a1.sinks.s1.channel=c1
a1.sinks.s2.channel=c2

启动agent的服务

[root@tianqinglong flumeconf]# flume-ng agent -c ../conf -f ./rep.conf -n a1 -Dflume.root.logger=INFO,console

测试

[root@tianqinglong ~]# echo "hello world hello interceptor" | nc tianqinglong01 6666

# nc需要安装
yum install -y nmap-ncat

案例演示: Multiplexing selector

配置方案

[root@tianqinglong01 flumeconf]# vi mul.conf
a1.sources = r1
a1.channels = c1 c2
a1.sinks = s1 s2

a1.sources.r1.type=syslogtcp
a1.sources.r1.host = tianqinglong01
a1.sources.r1.port = 6666
a1.sources.r1.selector.type=replicating
a1.sources.r1.selector.type=multiplexing
a1.sources.r1.selector.header = state
a1.sources.r1.selector.mapping.USER = c1
a1.sources.r1.selector.mapping.ORDER = c2
a1.sources.r1.selector.default = c1


a1.channels.c1.type=memory

a1.channels.c2.type=memory

a1.sinks.s1.type=hdfs
a1.sinks.s1.hdfs.path=hdfs://tianqinglong01:8020/flume/%Y/%m/%d/mul
a1.sinks.s1.hdfs.filePrefix=s1sink
a1.sinks.s1.hdfs.fileSuffix=.log
a1.sinks.s1.hdfs.inuseSuffix=.tmp
a1.sinks.s1.hdfs.rollInterval=60
a1.sinks.s1.hdfs.rollSize=1024
a1.sinks.s1.hdfs.rollCount=10
a1.sinks.s1.hdfs.idleTimeout=0
a1.sinks.s1.hdfs.batchSize=100
a1.sinks.s1.hdfs.fileType=DataStream
a1.sinks.s1.hdfs.writeFormat=Text
a1.sinks.s1.hdfs.round=true
a1.sinks.s1.hdfs.roundvalue=1
a1.sinks.s1.hdfs.roundUnit=second
a1.sinks.s1.hdfs.useLocalTimeStamp=true

a1.sinks.s2.type=hdfs
a1.sinks.s2.hdfs.path=hdfs://tianqinglong01:8020/flume/%Y/%m/%d/mul
a1.sinks.s2.hdfs.filePrefix=s2sink
a1.sinks.s2.hdfs.fileSuffix=.log
a1.sinks.s2.hdfs.inUseSuffix=.tmp
a1.sinks.s2.hdfs.rollInterval=60
a1.sinks.s2.hdfs.rollSize=1024
a1.sinks.s2.hdfs.rollCount=10
a1.sinks.s2.hdfs.idleTimeout=0
a1.sinks.s2.hdfs.batchSize=100
ai.sinks.s2.hdfs.fileType=Datastream
a1.sinks.s2.hdfs.writeFormat=Text
a1.sinks.s2.hdfs.round=true
a1.sinks.s2.hdfs.roundValue=1
a1.sinks.s2.hdfs.roundunit=second
a1.sinks. s2.hdfs.useLocalTimeStamp=true

a1.sources.r1.channels=c1 c2
a1.sinks.s1.channel=c1
a1.sinks.s2.channel=c2

启动agent的服务

[root@tianqinglong flumeconf]# flume-ng agent -c ../conf -f ./mul.conf -n a1 -Dflume.root.logger=INFO,console

测试

[root@tianqinglong ~]# curl -X POST -d '[{"headers":{"state":"ORDER"},"body":"this is my multiplex to c2"]' http://tianqinglong01:6666
[root@tianqinglong ~]# curl -X POST -d '[{"headers":{"state":"ORDER"},"body":"this is my content"]' http://tianqinglong01:6666

自定义拦截器

需求

为了提高Flume的扩展性,用户可以自己定义一个拦载器

将event的body中的数据,以数字开头的,存储为 hdfs://tianqinglong01:8020/flume/number.log s1
将event的body中的数据,以字母开头的,存储为 hdfs://tianqinglong01:8020/flume/character.log s2
将event的body中的数据,其他的开头的,乎储为hdfs: //tianqinglong01:8020/flume/other.log s3

pom.xml

<dependency>
	<groupId>org.apache.flume</groupId>
    <artifactId>flume-ng-core</artifactId>
	<version>1.8.0</version>
</dependency>

代码

package com.qf;

import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;

import java.util.List;

public class MyIntercepter implements Interceptor {
    private static final String LOCATION_KEY="location";
    private static final String LOCATION_NUMBER="number";
    private static final String LOCATION_CHARACTER="character";
    private static final String LOCATION_OTHER="other";


    public Event intercept(Event event) {
        byte[] body = event.getBody();
        if(body[0]>='0'&&body[0]<='9'){
            event.getHeaders().put(LOCATION_KEY,LOCATION_NUMBER);
        }
        else if(body[0]>='a'&&body[0]<='z' ||body[0]>='A'&&body[0]<='Z'){
            event.getHeaders().put(LOCATION_KEY,LOCATION_CHARACTER);

        }else {
            event.getHeaders().put(LOCATION_KEY,LOCATION_OTHER);

        }
        return event;
    }

    public List<Event> intercept(List<Event> events) {
        for (Event event: events
             ) {
            intercept(event);
        }
        return events;
    }

    public void initialize() {

    }
    public void close() {

    }
    public static class MyBuilder implements Builder{

        public Interceptor build() {
            return new MyIntercepter();
        }

        public void configure(Context context) {

        }
    }
}

打包上传

使用maven将拦截器打包,然后把此包和依赖的fastjson一起上传到flume lib 目录下

编写方案

a1.sources=r1
al.channels=c1 c2 c3
a1.sinks=s1 s2 s3
a1.sources.r1.channels=c1 c2 c3
a1.sinks.s1.channel=c1
a1.sinks.s2.channel=c2
a1.sinks.s3.channel=c3
#设置source的属性
a1.sources.ri.type=syslogtcp
a1.sources.r1.host=tianqinglong01
a1.sources.r1.port=12345
#设置拦截器
a1.sources.r1.interceptors=i1
a1.sources.r1.interceptors.i1.type=com.qf.flume. MyInterceptor$MyBuilder
#设置选择器的属性
a1.sources.r1.selector.type=multiplexing
a1.sources.r1.selector.header=location
a1.sources.r1.selector.mapping.number=c1
a1.sources.r1.selector.mapping.character=c2
a1.sources.ri.selector.mapping.other=c3
#设置channel的属性
a1.channels.c1.type=memory
a1.channels.c1.capacity=1000
a1.channels.c2.type=memory
a1.channels.c2.capacity=1000
a1.channels.c3.type=memory
a1.channels.c3.capacity=1000
#设置sink的属性
a1.sinks.s1.type=hdfs
a1.sinks.s1.hdfs.path=hdfs: //tianqinglong01:8020/flume/customInterceptor/s1/%Y-%m-%d-%H
a1.sinks.s1.hdfs.useLocalTimeStamp=true
a1.sinks.s1.hdfs.filePrefix=regex
a1.sinks.s1.hdfs.rollInterval=0
a1.sinks.s1.hdfs.rollSize=102400
a1.sinks.s1.hdfs.rollCount=30
a1.sinks.s1.hdfs.fileType=DataStream
a1.sinks.s1.hdfs.writeFormat=Text

a1.sinks.s1.type=hdfs
a1.sinks.s1.hdfs.path=hdfs: //tianqinglong01:8020/flume/customInterceptor/s1/%Y-%m-%d-%H
a1.sinks.s2.hdfs.useLocalTimeStamp=true
a1.sinks.s2.hdfs.filePrefix=regex
a1.sinks.s2.hdfs.rollInterval=0
a1.sinks.s2.hdfs.rollSize=102400
a1.sinks.s2.hdfs.rollCount=30
a1.sinks.s2.hdfs.fileType=DataStream
a1.sinks.s2.hdfs.writeFormat=Text

a1.sinks.s3.type=hdfs
a1.sinks.s3.hdfs.path=hdfs: //tianqinglong01:8020/flume/customInterceptor/s1/%Y-%m-%d-%H
a1.sinks.s3.hdfs.useLocalTimeStamp=true
a1.sinks.s3.hdfs.filePrefix=regex
a1.sinks.s3.hdfs.rollInterval=0
a1.sinks.s3.hdfs.rollSize=102400
a1.sinks.s3.hdfs.rollCount=30
a1.sinks.s3.hdfs.fileType=DataStream
a1.sinks.s3.hdfs.writeFormat=Text

启动agent

[root@tianqinglong01 flumeconf]# flume-ng agent -c ../conf/ -f ./mytest.conf -n -a1 -Dflume.root.logger=INFO,console

测试

echo "hello world" | nc  tianqinglong01 12345
echo "123 hello world" | nc  tianqinglong01 12345
echo ".123 hello world" | nc  tianqinglong01 12345
&lt;think&gt;嗯,用户问的是MyBatis-Plus的拦截器为什么拦截不到内置的getById方法。首先,我需要回想一下MyBatis和MyBatis-Plus的拦截机制有什么不同。MyBatis的拦截器通常是基于Executor接口的,而MyBatis-Plus可能用了自己的实现或者扩展了某些方法。 用户之前已经了解过如何在Spring Boot中配置MyBatis的SQL拦截器,现在遇到的是MyBatis-Plus特有的问题。可能他们按照之前的做法配置了拦截器,但是发现像getById这样的内置方法没有被拦截到。这时候需要分析MyBatis-Plus的工作机制。 首先,MyBatis-Plus的BaseMapper中的方法,比如selectById,实际上可能会调用MyBatis的底层方法,但可能使用了不同的接口或者代理方式。例如,getById可能最终会调用到MyBatis的selectOne或者selectList方法,这取决于具体的实现。如果拦截器只拦截了Executor的update和query方法,可能需要检查这些方法是否被正确覆盖。 另外,MyBatis-Plus可能对Executor进行了包装或者使用自定义的组件,比如自己的SqlSession,这可能导致传统的拦截点失效。这时候需要找到正确的拦截点,可能不是Executor,而是其他接口,比如StatementHandler或者ParameterHandler。 还有可能是拦截器的@Signature配置不正确,比如方法名或参数类型不匹配。比如,getById方法可能对应的底层MyBatis方法是query,但参数类型可能有变化,或者方法名不同,需要具体查看MyBatis-Plus的源码。 另外,用户可能需要确认拦截器是否正确注册到了MyBatis的配置中。在MyBatis-Plus中,可能需要通过特定的配置类或者使用MyBatis-Plus的配置方式来添加拦截器,而不是传统的MyBatis配置方式。例如,使用MybatisPlusInterceptor这个类来添加插件,而不是直接使用addInterceptor方法。 还可能存在多个拦截器的执行顺序问题,或者拦截器被其他拦截器覆盖了。需要检查是否有其他拦截器影响了当前拦截器的执行。 最后,建议用户通过调试或者日志来确认拦截器是否被加载,以及具体的拦截点是否被触发。可能需要查看MyBatis-Plus生成的SQL语句对应的MappedStatement,确认其ID是否正确,以及拦截器是否针对了正确的方法和参数类型。&lt;/think&gt;在MyBatis-Plus中拦截不到内置的`getById`方法,通常是由于以下原因及解决方案: --- ### 一、核心原因分析 1. **方法调用链路差异** MyBatis-Plus的`getById`方法底层实际调用的是`selectById`,而该方法是通过`SqlSessionTemplate`执行的,可能绕过了标准MyBatis拦截点 2. **拦截器签名不匹配** MyBatis-Plus的CRUD方法可能使用`SELECT_ONE`或`SELECT_LIST`类型的SQL命令,需要调整拦截的目标方法 3. **自动生成的SQL差异** 内置方法可能使用`SqlMethod`生成的SQL,与自定义SQL的执行路径不同 --- ### 二、正确配置方式 #### 1. 使用MyBatis-Plus原生拦截器接口 ```java @Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new InnerInterceptor() { @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { System.out.println(&quot;拦截到查询操作:&quot; + ms.getId()); } }); return interceptor; } } ``` #### 2. 精确指定拦截方法(传统MyBatis方式) ```java @Intercepts({ @Signature(type = Executor.class, method = &quot;query&quot;, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class CustomInterceptor implements Interceptor { // 实现逻辑 } ``` --- ### 三、验证步骤 1. 打印被拦截的SQL ID: ```java System.out.println(&quot;拦截SQL ID: &quot; + ms.getId()); ``` 2. 观察控制台输出,确认是否出现类似`com.baomidou.mybatisplus.core.mapper.BaseMapper.selectById`的语句 --- ### 四、特殊场景解决方案 如果仍无法拦截,可尝试以下方式: ```java // 在拦截器添加对StatementHandler的拦截 @Intercepts({ @Signature(type = StatementHandler.class, method = &quot;prepare&quot;, args = {Connection.class, Integer.class}) }) public class StatementInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = handler.getBoundSql(); System.out.println(&quot;最终执行SQL: &quot; + boundSql.getSql()); return invocation.proceed(); } } ``` --- ### 五、原理示意图 ``` MyBatis-Plus CRUD方法 &darr; SqlSessionTemplate &darr; MyBatis-Plus执行器(自动生成SQL) &darr; DefaultSqlSession &darr; Executor (需在此层拦截) ``` --- ### 六、注意事项 1. 优先使用MyBatis-Plus提供的`InnerInterceptor`体系 2. 避免与分页插件等内置拦截器冲突 3. 通过`@MapperScan`确保Mapper接口被正确扫描 4. 检查是否有多个拦截器相互覆盖的情况 通过上述配置可以成功拦截到`getById`等内置方法的SQL执行过程,实际开发中建议结合具体需求选择拦截层级。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值