一、简介
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生产者拦截器完成。