Kafka Topic 中明明有可拉取的訊息,為什麼 poll 不到

青石路發表於2024-08-28

開心一刻

今天小學女同學給我發訊息
她:你現在是畢業了嗎
我:嗯,今年剛畢業
她給我發了一張照片,懷裡抱著一隻大橘貓
她:我的眯眯長這麼大了,好看嗎
我:你把貓挪開點,它擋住了,我看不到
她:你是 sb 嗎,滾
我解釋道:你說的是貓呀
可訊息剛發出,就出現了紅色感嘆號,並提示:訊息已發出,但被對方拒收了

你也沒說貓叫眯眯呀

kafka搭建

出於簡單考慮,基於 docker 搭建一個 kafka 節點;因為一些原因,國內的 Docker Hub 映象加速器都不可用了,目前比較靠譜的做法是搭建個人映象倉庫,可參考:Docker無法拉取映象解決辦法,我已經試過了,是可行的,但還是想補充幾點

  1. sync-image-example.yml 只需要修改最後的映象複製,其他內容不需要改

    sync-image-example_改動點

    支援一次配置多個映象的複製

  2. 映象複製

    docker 映象複製命令的格式

    skopeo copy docker://docker.io/名稱空間/映象名:TAG docker://阿里雲映象地址/名稱空間/映象名:TAG

    我們以 kafka 為例,去 Docker Hub 一搜,好傢伙,搜出來上萬個

    上萬個kafka映象

    我們將搜尋條件精確化一些,搜 wurstmeister/kafka

    wurstmeister_kafka

    點進去,它在 Docker Hub 的地址是:

    https://hub.docker.com/r/wurstmeister/kafka

    那它的 docker 地址就是

    docker://docker.io/wurstmeister/kafka

    其他的映象用類似的方式去找,所以最終的複製命令類似如下:

    skopeo copy docker://docker.io/wurstmeister/kafka:latest docker://registry.cn-hangzhou.aliyuncs.com/qingshilu/wurstmeister_kafka:latest

    如果一切順利,那麼在我們的阿里雲個人映象倉庫就能看到我們複製的映象了

    阿里雲個人映象
  3. 如何 pull

    在個人倉庫點映象名,會看到 操作指南

    如何pull

    我們只關注前兩步,就可以將映象 pull 下來

    映象過濾_wurstmeister

映象獲取到之後,就可以搭建 kafka 了;因為依賴 zookeeper,我們先啟動它

docker run -d --name zookeeper-test -p 2181:2181 \
--env ZOO_MY_ID=1 \
-v zookeeper_vol:/data \
-v zookeeper_vol:/datalog \
-v zookeeper_vol:/logs \
registry.cn-hangzhou.aliyuncs.com/qingshilu/wurstmeister_zookeeper

然後啟動 kafka

docker run -d --name kafka-test -p 9092:9092 \
--env KAFKA_ZOOKEEPER_CONNECT=192.168.2.118:2181 \
--env KAFKA_ADVERTISED_HOST_NAME=192.168.2.118 \
--env KAFKA_ADVERTISED_PORT=9092  \
--env KAFKA_LOG_DIRS=/kafka/logs \
-v kafka_vol:/kafka  \
registry.cn-hangzhou.aliyuncs.com/qingshilu/wurstmeister_kafka

不出意外的話,都啟動成功

kafka啟動成功

如果出意外了,大家也別慌,用 docker log 去檢視日誌,然後找對應的解決方案

# 1.先找到啟動失敗的容器id
docker ps -a
# 2.用 docker log 檢視容器啟動日誌
docker log 容器id

如果需要開啟 kafkaSASL 認證,可參考:Docker-Compose搭建帶SASL使用者密碼驗證的Kafka 來搭建

Kafka Tool

詳情可檢視:kafka視覺化客戶端工具(Kafka Tool)的基本使用

kafka_tool連線成功

建立 Topic:test-topic,併傳送一條訊息

建立test_topic併傳送一條訊息

此時 test-topic 中有 1 條訊息

消費者 poll

程式碼很簡單

/**
 * @author: 青石路
 */
public class MsgConsumer {

    private static final Logger LOGGER = LoggerFactory.getLogger(MsgConsumer.class);

    public static void main(String[] args) {
        Properties props = new Properties();
        props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.2.118:9092");
        props.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        props.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "test_group");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
        // 如果在kafka中找不到當前消費者的偏移量,則設定為最舊的
        props.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String,String>(props);
        // 訂閱主題
        consumer.subscribe(Collections.singleton("test-topic"));
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
        LOGGER.info("records count = {}", records.count());
        records.forEach(record -> LOGGER.info("{} - {} - {}", record.offset(), record.key(), record.value()));
        // consumer.commitAsync();
        consumer.close();
    }
}

我們執行下,輸出日誌如下

poll沒拉取到訊息_日誌

竟然 poll 不到訊息,為什麼呀?

思考考

我們調整下程式碼,迴圈 poll

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
    LOGGER.info("records count = {}", records.count());
    records.forEach(record -> LOGGER.info("{} - {} - {}", record.offset(), record.key(), record.value()));
}

我們再執行下,輸出日誌如下

while迴圈後日志

消費者 poll 的過程中會先判斷當前消費者是否在 消費者組 中,如果不在,會先加入消費者組,在加入過程中,ConsumerCoordinator 會對這個消費者組 Rebalance,整個過程中該消費者組內的所有消費者都不能工作,而 poll 又配置了超時時間(100 毫秒),如果在超時時間內,當前消費者還未正常加入消費者組中,那麼 poll 肯定是拉取不到資料的;根據日誌可以看出,第 3 次 poll 的時候,消費者已經正常加入消費者組中,那麼就能 poll 到資料了

很多小夥伴可能可能會有這樣的疑問

平時在專案中使用的時候,從來沒有感受到這樣的問題,為什麼呢

原因有以下幾點

  1. poll 的超時時間設定比較長,超時時間內消費者能夠正常加入到消費者組中
  2. 消費者隨專案的啟動建立,存活週期與專案一致,那麼只有前幾次 poll 的時候,可能會因為消費者未加入到消費者組中而拉取不到資料,而一旦消費者成功加入到消費者組之後,那麼只要 Topic 中有資料,poll 肯定能拉取到資料;從整個次數佔比來看,poll 拉取不到資料的異常情況(Topic 中有可拉取的資料,但 poll 不到)佔比非常小,小到可以忽略不計了

所以你們感受不到這樣;但如果某些場景下,比如 DataX 從 kafka 讀資料

異源資料同步 → DataX 為什麼要支援 kafka?

消費者要不斷新建,那麼 poll 不到資料的異常情況的佔比就會上來了,那就需要透過一些機制來降低其所造成的的影響了,比如說重試機制

總結

  1. 示例程式碼:kafka-demo
  2. 如果大家平時用 docker 比較多,推薦透過搭建個人映象倉庫來解決映象拉取超時的問題
  3. kakfa 消費者 poll 的時候,消費者如果不在消費者組中,會先加入消費者組,那麼超時時間內可能 poll 不到資料,可以透過增大超時時間,或者重試機制來降低 poll 不到資料的異常次數(Topic 中沒有可拉取的資料而 poll 不到的情況不算異常情況)

相關文章