kafka學習系列:消息發送確認機制,避免程序退出導致數據丟失的情況

場景

在使用spring-kafka進行功能開發的時候,思考過這樣一個問題:假如使用信號量的方式(不瞭解的,可以點擊我的這篇文章《Java學習系列:使用SignalHandler來處理Linux信號量,控制程序結束的步驟》 進行了解)來終止程序,雖然我們使用了kafkaTemplate.send方法發送了,但是假如程序在發送過程就關閉了,是否就會造成數據丟失?即我們調用了kafkaTemplate.send方法發送了數據,認爲數據已經發送了;但是程序關閉的時候,導致數據未發送成功,進而導致了數據丟失情況的發生。

環境

軟件 版本
JDK 8
Kafka 2.0.1
spring-boot 2.1.8.RELEASE

正文

spring-kafka發送消息的介紹

spring-kafka提供了幾種消息處理的機制,具體的方法如下:

ListenableFuture<SendResult<K, V>> send(String topic, V data);

ListenableFuture<SendResult<K, V>> send(String topic, K key, V data);

ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, V data);

ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key, V data);

ListenableFuture<SendResult<K, V>> send(ProducerRecord<K, V> record);

ListenableFuture<SendResult<K, V>> send(Message<?> message);

這些是從類org.springframework.kafka.core.KafkaOperations裏面截取出來的,而KafkaTemplate是繼承了KafkaOperations,並實現了裏面的方法。如果細心的人可以發現,其實每個方法基本都會返回一個ListenableFuture對象。通過查看KafkaTemplate的源碼,我們可以看到裏面的多個send方法都會指向protected ListenableFuture<SendResult<K, V>> doSend(final ProducerRecord<K, V> producerRecord),下面貼一下這個方法的內部實現:

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));
	if (this.autoFlush) {
		flush();
	}
	if (this.logger.isTraceEnabled()) {
		this.logger.trace("Sent: " + producerRecord);
	}
	return future;
}

從上面的源碼,我們可以知道,處理之後,是返回了SettableListenableFuture對象。那麼,這個對象是做什麼用的呢?我們可以看一下里面的類圖:
在這裏插入圖片描述
從上面的類圖,我們可以發現,其實都是繼承了Futrue接口。說到這裏,就得說一下Future是什麼東西了。Future接口是在Java 5被引入,設計初衷是提供一種異步計算,返回一個執行運算結果的引用,當運算結束之後,這個引用將會被返回給調用方。這樣就可以將耗時時間比較久的運算先隔離開,自己去做其他運算,等處理好,再來接收結果。所以,我們在使用kafkaTemplate.send方法的時候,其實是會將數據放到一個隊列裏面,等待數據累積到一定階段或者一定的時間,就會將數據發送出去。這樣做是爲了減少數據傳輸的次數,加快發送效率。具體的代碼可以查看org.apache.kafka.clients.producer.internals.ProducerBatch。而這個過程耗費的實際是不確定的,所以沒有特殊要求的話,我們一般是不做同步阻塞等待消息結果返回,而是使用回調函數的方式,讓spring-kafka發送消息之後,主動調用回調函數,告知我們數據已經發送成功了。那麼,該怎麼做?這就是接下來的重點了。

消息發送確認機制

我們一般使用兩種方式來對消息發送的結果進行處理,一種是同步阻塞等待結果返回,一種是異步等待結果返回。接下來爲大家說明兩種情況。

一、同步阻塞

發送完,立即調用Future等待返回,就是同步阻塞。請看代碼:

try {
    kafkaTemplate.send(topic, partition,
                        String.valueOf(Math.round(Math.random() * 100)),
                        message).get(100, TimeUnit.SECONDS);
    handleSuccess(data);
}
catch (ExecutionException e) {
    handleFailure(data, record, e.getCause());
}
catch (TimeoutException | InterruptedException e) {
    handleFailure(data, record, e);
}

二、異步等待結果返回

發送完,獲取對應的ListenableFuture對象,並添加對應的回調函數,等待消息發送完畢之後,調用該回調函數方法。具體代碼如下:

ListenableFuture<SendResult<String,String>> future =  kafkaTemplate.send(topic, partition,
                        String.valueOf(Math.round(Math.random() * 100)),
                        message);
future.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {
    @Override
    public void onSuccess(SendResult<String, String> result) {
        log.trace("發送消息成功,發送主題爲:",topic);
    }

    @Override
    public void onFailure(Throwable ex) {
        log.error("發送消息失敗,消息主題爲 {},異常消息爲 :{}",topic,ex);
        handleFailureData(topic, message);
    }
});

而實際項目使用中,我是使用第二種方式,這樣既不會耽誤消息的發送,也不會錯過對應的消息結果,比單一的同步阻塞方法要好多了。

總結

通過研究spring-kafka的文檔和對應的源碼,實現了消息發送確認的機制:添加對應的回調函數,等待消息發送完畢之後,調用回調函數進行記錄。從源碼當中,也學到了不少的東西和比較好的設計理念。

參考鏈接

spring-kafka-docs

隨緣求贊

如果我的文章對大家產生了幫忙,可以在文章底部點個贊或者收藏;
如果有好的討論,可以留言;
如果想繼續查看我以後的文章,可以左上角點擊關注
拜拜

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章