前言:今天看到有人說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);