前言:今天看到有人说kafka生产者在发送消息后,如果发生异常,异常捕获方法里拿不到消息的数据,我想了想,感觉不太对劲,所以验证了一下。
首先说下结论:kafka是不会在生产者发送消息的回调中,把发送的消息再一次返回回来的,因为这些消息我们可以自己记录,没必要浪费网络资源。
kafka-client的回调方法
kafka原生的kafka-client包中,生产者发送回调方法如下,其中RecordMetadata包含发送成功后的分区、偏移量和时间戳等信息;Exception是发送失败后的异常信息
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
System.out.println(metadata.partition() + "---" + metadata.offset());
}
});
可以发现确实回调方法里确实不包含消息数据,但是我们可以自己继承Callback类,添加消息属性,在send的时候使用我们自己写的callback类,这样就能拿到消息数据了,例子如下:
class MyCallback implements Callback {
private Object msg;
public MyCallback(Object msg) {
this.msg = msg;
}
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
}
}
producer.send(record, new MyCallback(record));
springboot中的kafka回调
那么spring提供的spring-kafka里面是怎么处理的呢,在spring中,使用kafka生产者发送消息的方法如下:
/**
* 发送消息并接收回调
*
* @param msg
*/
public void sendAndCallback(String msg) {
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send("DrewTest", msg);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onSuccess(SendResult<String, Object> result) {
System.out.println("msg OK." + result.toString());
}
@Override
public void onFailure(Throwable ex) {
System.out.println("msg send failed: " + ex.getMessage());
}
});
}
其中回调函数里的SendResult类里面包含ProducerRecord和RecordMetadata,而ProducerRecord就是spring定义的消息类,但是这里可以看到,在失败的onFailure方法中,仍然只能拿到异常Throwable类,其实spring内部已经在失败的时候对消息对象做了处理,只是没有显式返回给我们,源码如下:
protected ListenableFuture<SendResult<K, V>> doSend(final ProducerRecord<K, V> producerRecord) {
if (this.transactional) {
Assert.state(inTransaction(),
"No transaction is in process; "
+ "possible solutions: run the template operation within the scope of a "
+ "template.executeInTransaction() operation, start a transaction with @Transactional "
+ "before invoking the template method, "
+ "run in a transaction started by a listener container when consuming a record");
}
final Producer<K, V> producer = getTheProducer();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Sending: " + producerRecord);
}
final SettableListenableFuture<SendResult<K, V>> future = new SettableListenableFuture<>();
producer.send(producerRecord, buildCallback(producerRecord, producer, future));//kafka-client的发送方法
if (this.autoFlush) {
flush();
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Sent: " + producerRecord);
}
return future;
}
private Callback buildCallback(final ProducerRecord<K, V> producerRecord, final Producer<K, V> producer,
final SettableListenableFuture<SendResult<K, V>> future) {
return (metadata, exception) -> {
try {
if (exception == null) {
future.set(new SendResult<>(producerRecord, metadata));
if (KafkaTemplate.this.producerListener != null) {
KafkaTemplate.this.producerListener.onSuccess(producerRecord, metadata);
}
if (KafkaTemplate.this.logger.isTraceEnabled()) {
KafkaTemplate.this.logger.trace("Sent ok: " + producerRecord + ", metadata: " + metadata);
}
}
else {
future.setException(new KafkaProducerException(producerRecord, "Failed to send", exception));
if (KafkaTemplate.this.producerListener != null) {
KafkaTemplate.this.producerListener.onError(producerRecord, exception);//传给监听器
}
if (KafkaTemplate.this.logger.isDebugEnabled()) {
KafkaTemplate.this.logger.debug("Failed to send: " + producerRecord, exception);
}
}
}
finally {
if (!KafkaTemplate.this.transactional) {
closeProducer(producer, false);
}
}
};
}
第一个doSend方法,就是我们调用spring-kafka发送消息的方法,可以看到里面其实也是调用了kafka-client的方法,其中传入的buildCallback回调方法中,把ProducerRecord消息对象传给了监听器。所以spring在生产回调中获取消息数据的方式就跟我一开始说的类似,就是自己继承或者包装了一个Callback类,然后把消息数据当参数传进去。
那么在spring中,我们要怎么在失败的时候拿到这些数据呢,上面说到,spring在buildCallback回调方法中,把消息对象传给监听器,所以,我们要拿到这些数据,也必须自己写个监听器,在监听器中获取这些消息数据,进行失败处理。
@Service
public class MyProducerListener implements ProducerListener<String, Object> {
@Override
public void onSuccess(String topic, Integer partition, String key, Object value, RecordMetadata recordMetadata) {
System.out.println("消息发送成功");
}
@Override
public void onError(String topic, Integer partition, String key, Object value, Exception exception) {
//可对进行重发重试
System.out.println("消息发送失败");
}
}
//设置kafkaTemplate的生产者监听器
@Autowired
public MyProducerListener producerListener;
kafkaTemplate.setProducerListener(producerListener);