Kafka學習三-消費者

目錄

 

消費者和消費組

消費者羣組和分區再均衡

消費者的相關配置

消費者如何提交偏移量

KafkaConsumer API 提供的偏移量的提交方式

再均衡監聽器

從特定偏移量處開始處理記錄

如何優雅的退出


消費者和消費組

Kafk的消費者從屬於消費羣組,一個羣組裏面的消費者訂閱的是同一個主題。每個消費者接受主題的一部分分區消息。關係如下: 

消費者羣組和分區再均衡

(1)什麼是分區再均衡?
分區的所有權從一個消費者轉移到另一個消費者,叫做分區再均衡。
(2)什麼情況下會導致分區再均衡?
a.一個新的消費者加入羣組時,它讀取的是原本其他消費者讀取的消息
b.當一個消費者關閉或者發生崩潰時,會離開羣組,此時原本又它讀取的分區將有羣組中其他的消費者讀取
c.在主題發生變化時,比如添加了新的分區
(3)發生分區再均衡的影響?
再均衡期間,消費者無法讀取消息,造成整個羣組一小段時間的不可用。並且當分區被從新分配給另一個消費者時,消費者當前的讀取狀態丟失。
(4)消費者通過向被指派爲羣組協調器的broker發送心跳來維持他們和羣組的從屬關係以及他們對分區的所有權關係。只有消費者在正常時間間隔內發送了心跳,就被認爲是活躍的。如果消費者停止發送心跳的時間足夠長,會話就會過期,羣組協調器會認爲該消費者已經死亡,觸發一次再均衡。
(5)如何進行分配分區?
當有消費者加入到羣組,會先向羣組協調器發送一個JoinGroup的請求,第一個加入羣組的消費者會成功”羣主“。羣主會從協調器獲取到羣組的成員信息(包括所有最近發送過心跳的消費者)並負責給每一個消費者分配分區。

消費者的相關配置

(1)fetch.min.bytes:指定了消費者從服務器獲取記錄的最小字節數。broker在收到消費者的數據請求時,如果可用數據量小於該值,那麼broker會等到有足夠多的數據量時再返回給消費者。
(2)fetch.max.wait.ms:用於指定broker的等待時間。默認500ms。如果麼有足夠的數據流入kafka,消費者獲取最小數據量的要求無法滿足,最終達到500ms的延遲,會將此時topic小於fetch.min.bytes的數據量返回給消費者。
(3)max.partiton.fetch.bytes:指定服務器從每個分區中返回給消費者的最大字節數。默認值1M,也就是KafkaConsumer.poll()方法從每個分區裏返回的記錄最多不能超過該值。在實際分配消費者內存時,需要多分配一些,需要防止有的消費者掛掉,其他消費者需要處理更多的分區。
(4)session.timeout.ms:指定消費者在被認爲死亡之前可以與服務器斷開連接的時間,默認是3s。也就是3s之內沒有發送心跳檢測,就會被認爲已死亡,協調器會再均衡。該參數與heartbeat.interval.ms相關。heartbeat.interval.ms指定了發送心跳的頻率。所以heartbeat.interval.ms要比session.timeout.ms小。一般是session.timeout.ms的三分之一。
(5)auto.offset.reset:消費者在讀取一個沒有偏移量的分區或者偏移量無效如何處理。
a.latest:偏移量無效的時候,消費者讀取最新的記錄
b.earliest:偏移量無效的時候,消費者會從起始位置讀取分區記錄
(6)enable.auto.commit:消費者是否自動提交偏移量,默認true。一般爲了避免出現重複數據,我們會自行進行偏移量的提交。
(7)partition.assignment.strategy:設置分區分配策略。
a.Range:該策略會把主題的若干連續的分區分配給消費者。

 b.RoundRobin:該策略會把主題的所有分區逐個分配給消費者 
(8)client.id:標識從客戶端發送過來的消息
(9)max.poll.records:控制單次調用call()方法能夠返回的記錄數據

消費者如何提交偏移量

消費者會向一個特殊主題:consumeroffset發送消息,消息包括每個分區的偏移量。當有新的消費者加入羣組或者消費者崩潰,會發生再均衡。再均衡之後每個消費者可能分配到新的分區,爲了能夠繼續之前的工作,消費者讀取每個分區最後提交的偏移量,從該該偏移量處進行讀取。 

那麼如果提交的偏移量不正確,就會引發兩種情況:(1)提交的偏移量小於客戶端最後處理的消息的偏移量,此時消息會被重複處理;(2)提交的偏移量大於客戶端最後處理的消息偏移量,此時消息就會有丟失。 

KafkaConsumer API 提供的偏移量的提交方式

(1)自動提交:設置enable.auto.commit=true,但是在發生再均衡的時候極易丟失數據。 (2)提交當前偏移量:設置enable.auto.commit=false,讓應用程序自行提交偏移量。可以使用commitSync()或者commitASync()進行提交。 

    Properties properties = new Properties();
    properties.put("bootstrap.servers","broker1:port");
    properties.put("key.serializer","xxxxx");
    properties.put("value.serializer","xxxx");

    KafkaConsumer<String,String> consumer = new KafkaConsumer<String, String>(properties);
    while (true){
        Duration duration = Duration.of(100, ChronoUnit.MILLIS);
        ConsumerRecords<String, String> records = consumer.poll(duration);

        for (ConsumerRecord<String, String> record : records) {
            System.out.println("topic:"+record.topic()+",partition:"+record.partition()+",offset:"+record.offset());
        }
        try {
            //同步提交
            consumer.commitSync();
            //異步提交
            consumer.commitAsync();
        }catch (CommitFailedException e){
            System.out.println("commit failed:"+e);
        }
    }

同步提交:會一直嘗試提交直至成功,如果提交失敗,我們也是能把日誌記錄下來
異步提交:提交最後一個偏移量之後,繼續做其他的事情。如果提交失敗,不會進行重試,原因:在收到服務器相應的時候,可能有一個更大的偏移量已經提交成功。
同步+異步結合: 

    try {
            //異步提交
            consumer.commitAsync();
        }catch (CommitFailedException e){
            System.out.println("commit failed:"+e);
        }finally {
            try{
                //同步提交
                consumer.commitSync(); 
            }finally {
                consumer.close();
            }
        }

如果一切正常,我們使用異步提交的方式。如果直接關閉了消費者,就沒有下一次提交了,使用同步提交,一直進行重試。 (3)提交特定的偏移量:
場景:我們通過poll()方法獲取到大批數據,我們並不想在數據都處理完了,才返回一個偏移量。我們想在處理的中間就返回偏移量,那麼不管是同步的方式還是異步的方式都不滿足我們的需求。消費者API中提供了 consumer.commitSync()和consumer.commitAsync()方法時傳入希望提交的分區以及相關偏移量 

    KafkaConsumer<String,String> consumer = new KafkaConsumer<String, String>(properties);
    Map<TopicPartition,OffsetAndMetadata> currentOffSets = new HashMap<>();
    int count =0;
    while (true){
        Duration duration = Duration.of(100, ChronoUnit.MILLIS);
        ConsumerRecords<String, String> records = consumer.poll(duration);

        for (ConsumerRecord<String, String> record : records) {
            System.out.println("topic:"+record.topic()+",partition:"+record.partition()+",offset:"+record.offset());
            currentOffSets.put(new TopicPartition(record.topic(),record.partition()),new OffsetAndMetadata(record.offset()+1));
            if (count%100==0){
                consumer.commitSync(currentOffSets);
            }
            count++;
        }
    }

再均衡監聽器

消費者在退出和進行分區再均衡之前,會做一些清理工作,我們可以在分區之前做一些操作,或者在分區之後做一些操作。可以在調用subscribe()方法的時候傳入一個ConsumerRebalanceListener實例,該實例有如下兩個方法:
(1)void onPartitionsRevoked(Collection partitions)該方法會在再均衡開始之前和消費者停止讀取消息之後被調用。
(2)void onPartitionsAssigned(Collection partitions)該方法會在重新分配分區之後的消費者開始讀取消息之前被調用。

從特定偏移量處開始處理記錄

我們使用poll()方法目前都是從各個分區的最新偏移量開始處理數據,我們也可以從特定的偏移量處進行獲取。
(1)void seekToBeginning(Collection partitions):從分區的開始位置獲取
(2) void seekToEnd(Collection partitions):從分區的末尾開始獲取
(3) void seek(TopicPartition partition, OffsetAndMetadata offsetAndMetadata) 重某個分區的特定位置進行獲取。這裏我們可以在我們處理數據的時間,保存本次處理的offset到本地數據庫,那麼下次獲取就可以使用這個方法,從而減少消息丟失。

如何優雅的退出

我們消費者都是在一個無線循環中進行消息輪詢,可以使用consumer.wakeup()退出poll(),並拋出WakeUpException異常,我們不需要處理該異常。consumer.wakeup()是消費者唯一一個可以從其他線程裏安全調用的方法。同時在退出線程之前,我們要調用consumer.close()方法,該方法會提交任何還沒有提交的東西,並告知協調器觸發再均衡,不需要等待會話超時。

某些場景下,可能出現一個消費者從一個主題的所有分區獲取特定的分區讀取數據,此時不在需要消費者羣組和再均衡,值需要把主題和分區分配給消費者,消費者提交偏移量就可以。

    List<PartitionInfo> topic = consumer.partitionsFor("topic");
    List<TopicPartition> partitions = Lists.newArrayList();
    if (topic!=null){
        for (PartitionInfo partitionInfo : topic) {
            TopicPartition partition  =  new TopicPartition(partitionInfo.topic(),partitionInfo.partition());
            partitions.add(partition);
        }
    }
    consumer.assign(partitions);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章