kafka生產者回調的使用

前言:今天看到有人說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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章