Kafka生产者拦截器

一、简介 

kafka生产者拦截器主要用于在消息发送前对消息内容进行定制化修改,以便满足相应的业务需求,也可用于在消息发送后获取消息的发送状态,所在分区和偏移量等信息。同事,用户可以在生产者中指定多个拦截器形成一个拦截器链,生产者会根据指定顺序先后调用。

Kafka生产者拦截器的访问流程如下:

这里的拦截器为两个拦截器组成的一个拦截器链。第一个拦截器为时间拦截器,作用是在消息发送之前修改消息的内容,在消息最前面加入当前时间戳;第二个拦截器为消息发送状态拦截器,作用是统计消息发送成功和失败的数量。

 

二、操作步骤

1、创建Java项目

在Eclipse或者IDEA中新建Maven项目kafka_interceptor_dome,然后在项目的pom.xml中加入kafka客户端依赖库,内容如下:

    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>2.0.0</version>
    </dependency>

2、时间拦截器

项目中新建拦截器类TimeInterceptor.java,并实现Kafka客户端API的生产者接口org.apache.kafka.clients.producer.ProducerInterceptor,然后添加接口中需要实现的方法,分别介绍如下:

(1)configure(Map<String, ?> configs)

该方法在初始化数据的时候被调用,用于获取生产者的配置信息

(2)onSend(ProducerRecord<K, V>)

该方法在消息被序列化之前调用,并传入要发送的消息记录。用户可以在该方法中对消息记录进行任意的修改,包括消息的key和value以及要发送的主题和分区等。

在Kafka API操作中,生产者使用KafkaProducer对象的send()方法发送消息,send()方法的源码如下:

    @Override
    public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
        // 对消息记录进行修改
        ProducerRecord<K, V> interceptedRecord = this.interceptors.onSend(record);
        return doSend(interceptedRecord, callback);
    }

从上述源码可以看出,在生产者发送消息之前,会先调用拦截器的onSend()方法,并传入消息记录record。onSend()方法返回一条新的消息记录interceptedRecord。最终将新消息记录interceptedRecord发送给了Kafka服务器。

(3)onAcknowledgement(RecordMetadata metadata, Exception exception)

该方法在发送到服务器的记录已被确认或者记录发送失败时调用(在生产者回调逻辑触发之前),可以在metadata对象中获取消息的主题、分区和偏移量等信息,在exception对象中获取消息的异常信息。

    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        int partition = metadata.partition();
        String topic = metadata.topic();
        long offset = metadata.offset();
        String str = metadata.toString();
        long timestamp = metadata.timestamp();
    }

(4)close()

该方法用于关闭拦截器并释放资源。当生产者关闭时将调用该方法。

拦截器类TimeInterceptor.java的完整代码如下:

package kafka.demo.interceptor;

import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

/**
 * 时间拦截器,发送消息之前,在消息内容前面加入时间戳
 */
public class TimeInterceptor implements ProducerInterceptor<String, String> {
    /**
     * 获取生产者配置信息
     */
    @Override
    public void configure(Map<String, ?> configs) {
        System.out.println(configs.get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG));
    }

    /**
     * 该方法在消息发送之前调用
     * 对原消息记录进行修改,在消息内容最前边添加时间戳
     * @param record    生产者发送的消息记录,并自动传入
     * @return 修改后的消息记录
     */
    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
        System.out.println("TimeInterceptor-------onSend方法被调用");
        // 创建一个新的record,把时间戳写到消息体的最前面
        ProducerRecord<String, String> proRecord = new ProducerRecord<String, String>(
                record.topic(), record.key(), System.currentTimeMillis() + "," + record.value().toString());
        return proRecord;
    }

    /**
     * 该方法在消息发送完毕后调用
     * 当发送到服务器的记录已被确认,或者记录发送失败时,将调用次方法
     */
    @Override
    public void onAcknowledgement(RecordMetadata recordMetadata, Exception exception) {
        System.out.println("TimeInterceptor-------onAcknowledgement方法被调用");
    }

    /**
     * 当拦截器关闭时调用该方法
     */
    @Override
    public void close() {
        System.out.println("TimeInterceptor-------close方法被调用");
    }
}

3、状态拦截器

项目中新建拦截器类CounterInterceptor.java,并实现Kafka客户端API的生产者接口org.apache.kafka.clients.producer.ProducerInterceptor。类CounterInterceptor的构造与时间戳拦截器相同,只是业务逻辑不通。

拦截器类CounterInterceptor.java的完整代码如下:

package kafka.demo.interceptor;

import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

/**
 * 消息发送状态统计拦截器
 * 统计发送成功和失败的消息数,并在生产者关闭时打印这两个消息数
 */
public class CounterInterceptor implements ProducerInterceptor<String, String> {
    // 发送成功的消息数量
    private int successCounter = 0;
    // 发送失败的消息数量
    private int errCounter = 0;

    /**
     * 获取生产者配置信息
     */
    @Override
    public void configure(Map<String, ?> map) {
        System.out.println(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG);
    }

    /**
     * 该方法在消息发送前调用
     * 修改发送的消息记录,此处不做处理
     */
    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
        System.out.println("CounterInterceptor-------onSend方法被调用");
        return record;
    }

    /**
     * 该方法在消息发送完毕后调用
     * 当发送到服务器的记录已被确认,或者记录发送失败时,将调用此方法
     */
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        System.out.println("CounterInterceptor-------onAcknowledgement方法被调用");
        // 统计成功和失败的次数
        if (exception == null) {
            successCounter ++;
        }else{
            errCounter ++;
        }
    }

    /**
     * 当生产者关闭时调用该方法,可以在此将结果进行持久化保存
     */
    @Override
    public void close() {
        System.out.println("CounterInterceptor-------close方法被调用");
        // 打印统计结果
        System.out.println("发送成功的消息数量: " + successCounter);
        System.out.println("发送失败的消息数量: " + errCounter);
    }
}

4、创建生产者

项目中新建生产者类MyProducer.java,向名称为mytopic的主题循环发送5条消息,完整代码如下:

package kafka.demo.interceptor;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 生产者类
 */
public class MyProducer {
    public static void main(String[] args) {
        // 1、设置配置属性
        Properties props = new Properties();
        // 设置生产者Broker服务器连接地址
        props.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "hadoop01:9092, hadoop02:9092, hadoop03:9092");
        // 设置序列化key程序类
        props.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
                StringSerializer.class.getName());
        // 设置序列化value程序类,可以是Integer,也可以是String
        props.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
                StringSerializer.class.getName());

        // 2、设置拦截器链
        List<String> interceptors = new ArrayList<String>();
        // 添加拦截器TimeInterceptor,需要指定拦截器的全路径
        interceptors.add("kafka/demo/interceptor/TimeInterceptor.java");
        // 添加拦截器CounterInterceptor
        interceptors.add("kafka/demo/interceptor/CounterInterceptor.java");
        // 将拦截器加入配置属性中
        props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);

        // 3、发送消息
        Producer<String, String> producer = new KafkaProducer<String, String>(props);
        // 循环发送5条消息
        for (int i = 0; i < 5; i++) {
            // 发送消息,此方式只负责发送消息,不关系是否发送成功
            // 第一个参数:主题名称
            // 第二个参数:消息的value值,即消息内容
            producer.send(new ProducerRecord<String, String>("mytopic", "hello kafka"+i));
        }

        // 4、关闭生产者,释放资源
        // 调用该方法后将触发拦截器的close()方法
        producer.close();
    }
}

上述代码与普通生产者程序不同的是,在发送消息之前向生产者配置属性中加入了两个拦截器类:TimeInterceptor和CounterInterceptor,生产者在发送消息之前和消息发送完毕之后,无论发送是否成,都会按顺序依次调用这两个拦截器中的相应方法。

项目kafka_interceptor_demo的完整结构如下图所示:

5、运行程序

(1)创建主题

在kafka集群中执行以下命令,创建名为mytopic的主题,分区数为2,每个分区的副本数为2:

$ bin/kafka-topics.sh \
  --create \
  --zookeeper hadoop01:9092,hadoop02:9092,hadoop03:9092 \
  --replication 2 \
  --partitions 2 \
  --topic mytopic

(2)启动消费者

在kafka集群中执行以下命令,启动一个消费者,监听主题mytopic的消息:

$ bin/kafka-console-consumer.sh \
  --bootstrap-server hadoop01:9092,hadoop02:9092,hadoop03:9092 \
  --topic mytopic

(3)运行生产者程序

在IDEA中运行生产者程序myProducer.java,观察kafka消费者端和IDEA控制台的输出信息。

kafka消费者端的输出信息如下:

1604306867915,hello kafka 0
1604306867925,hello kafka 4
1604306867920,hello kafka 2
1604306867922,hello kafka 3
1604306867915,hello kafka 1

可以看到,成功输出了5条消息,且在消息内容前面加入了时间戳。

IDEA控制台的部分输出信息如下:

TimeInterceptor-------onSend方法被调用
CounterInterceptor-------onSend方法被调用
TimeInterceptor-------onSend方法被调用
CounterInterceptor-------onSend方法被调用
TimeInterceptor-------onSend方法被调用
CounterInterceptor-------onSend方法被调用
TimeInterceptor-------onSend方法被调用
CounterInterceptor-------onSend方法被调用
TimeInterceptor-------onSend方法被调用
CounterInterceptor-------onSend方法被调用
TimeInterceptor-------onAcknowledgement方法被调用
CounterInterceptor-------onAcknowledgement方法被调用
TimeInterceptor-------close方法被调用
CounterInterceptor-------close方法被调用
发送成功的消息数量: 5
发送失败的消息数量: 0

由上述输出信息结合拦截器的调用顺序可以总结出:在每条消息发送之前,两个拦截器会依次调用onSen()方法;在每条消息发送之后,两个拦截器会依次调用onAcknowledgement()方法;在生产者关闭时,两个拦截器会依次调用close()方法。

至此,kafka生产者拦截器完成。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kafka生产者拦截可以在消息被发送到Kafka集群之前对消息进行定制化修改,例如增加一些额外的信息、对消息进行压缩、加密等操作,从而满足一些特定的需求。常见的拦截有:压缩拦截、加密拦截、性能监控拦截等。 在实现自定义的生产者拦截时,需要实现org.apache.kafka.clients.producer.ProducerInterceptor接口,并重写onSend()和onAcknowledgement()方法。其中,onSend()方法在消息被序列化之前调用,可以在此方法中对消息进行修改;onAcknowledgement()方法在消息被确认接收后调用,可以在此方法中对成功发送的消息进行统计。 以增加时间戳为例,以下是一个简单的生产者拦截实现: ```java public class TimestampInterceptor implements ProducerInterceptor<String, String> { @Override public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) { String modifiedValue = System.currentTimeMillis() + "," + record.value(); return new ProducerRecord<>(record.topic(), record.partition(), record.timestamp(), record.key(), modifiedValue, record.headers()); } @Override public void onAcknowledgement(RecordMetadata metadata, Exception exception) { // do nothing } @Override public void close() { // do nothing } @Override public void configure(Map<String, ?> configs) { // do nothing } } ``` 在上述代码中,onSend()方法会在消息发送前将当前时间戳加在消息值的前面,并返回一个新的ProducerRecord对象。在configure()方法中可以进行一些初始化操作。调用producer.addInterceptor()方法将拦截添加到生产者中即可。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值