kafka消費者知識整理

說到kafka的消費者,我們要先提到一個概念。
消費者組
消費者組的意思是:多個消費者可以屬於同一個消費者組。投遞到主題中的消息只能被消費者組中的一個消費者消費。如果我們要做消息點對點發布,就可以將多個消費者放到一個消費者組中。這樣,一條消息只會被一個消費者消費。如果要做消息的發佈訂閱,就可以將多個消費者分到不同的消費者組中,不同消費者組的消費者都可以消費這條消息。
通過消費者組,kafka可以實現點對點模型和發佈訂閱模型的任意轉換,非常方便。
每一個消費者實例一定要屬於某一個組,如果我們在實例化某一個消費者的時候,沒有指定消費者組ID,此時啓動消費者的時候就會報錯

Exception in thread "main" org.apache.kafka.common.errors.InvalidGroupIdException: The configured groupId is invalid

關於消費者和消費者組,參考<深入理解kafka>書中的圖,說明一下:
比如,我現在有一個主題,主題中有7個分區。有一個消費者訂閱了這個主題,那麼這個消費者會消費7個分區中的消息,如圖:
在這裏插入圖片描述
這個時候,又有一個消費者訂閱了這個主題,同時他們兩個屬於同一個組。此時,C0和C1會一起消費這7個分區,如圖:
在這裏插入圖片描述
如果消費者屬於2個不同的組呢,此時每個組都會訂閱所有分區的消息。但是,一條消息
只會被組中的一個消費者消費。如下圖:
在這裏插入圖片描述
由以上分析可以得出,一個消費者組的消費者數量是不能多於分區數的,如果多於分區數,就會有消費者沒有分區可以消費,如下圖:
在這裏插入圖片描述
以上邏輯,都是基於默認的分區分配策略進行分析的。可以通過修改消費者端參數partition.assignment.strategy來改變以上策略。
下面寫一個例子,來演示一下如何使用kafka的消費者訂閱一個主題,並拉取消息進行處理
消費者者API

//定義消費者的父類
public class BaseConsumerFast {
    protected static String brokerList = "192.168.11.81:9092,192.168.11.81:9093,192.168.11.81:9094";
    protected static String groupId = "group.demo";
    protected static String clientId = "consumer.client.id.demo";
    protected static String topic = "topic-partitions";

    protected static Properties initConfig(){
        Properties properties = new Properties();
        //kafka服務端地址(必填)
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokerList);
        //key的反序列化器(必填)
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        //value的反序列化器(必填)
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        //自動提交設置爲false,手動提交。默認值爲:true
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
        //groupId,消費者組標識(必填)
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,groupId);
        //clientId,kafka服務端可以使用kafka-configs.sh腳本,搭配該參數查看消費者的參數設置
        properties.put(ConsumerConfig.CLIENT_ID_CONFIG,clientId);
        return properties;
    }
}
//消費者停止消費的控制類
public class ISRunning {
    private boolean flag = true;

    public boolean get(){
        return flag;
    }

    public void set(boolean flag){
        this.flag = flag;
    }
}
//定義消費者
public class ConsumerFastStart2 extends BaseConsumerFast{
    private static ISRunning isRunning = new ISRunning();
    /**
     * 粗粒度的消息拉取
     * @param args
     */
    public static void main(String[] args) {
        KafkaConsumer<String,String> kafkaConsumer = new KafkaConsumer(initConfig());
        //訂閱一個主題
        kafkaConsumer.subscribe(Arrays.asList(topic));
        //獲取訂閱的所有主題的消息,進行消費
        while (isRunning.get()) {
        //拉取消息,超時時間爲1秒
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(1000L);
            System.out.println("本次拉取的消息數量:"+consumerRecords.count());
            System.out.println("消息集合是否爲空:"+consumerRecords.isEmpty());
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println("topic:"+consumerRecord.topic()+"---"+"partition:"+consumerRecord.partition()+"---"+"offset:"+consumerRecord.offset());
                System.out.println("key:"+consumerRecord.key()+"---"+consumerRecord.value());
                //processing record
            }
        }
    }
}

可以看到,kafka消費者訂閱消息的api是subscribe方法,subscibe方法的入參是一個List集合。一個消費者可以訂閱多個主題。如下:

 kafkaConsumer.subscribe(Arrays.asList("topic1","topic2"));

訂閱方法的重載方法如下:

//以正則表達式方式訂閱主題,同時註冊了一個rebalance的監聽器
public void subscribe(Pattern pattern, ConsumerRebalanceListener listener)
//訂閱一組主題
public void subscribe(Collection<String> topics)
//訂閱一組主題,同時註冊了一個rebalance的監聽器
public void subscribe(Collection<String> topics, ConsumerRebalanceListener listener)

我上面寫的是kafka_0.11.0.3版本的消費者客戶端,不同版本的消費者客戶端api是不同的。我們且以0.11.0.3的爲例.
這裏我發現,<深入理解kafka>這本書裏還介紹了另外一個API
public void subscribe(Pattern pattern)
這個方法,是在1.0.0版本開始出現
對應的API是assign(Collection< TopicPartition > partitions)
TopicPartition類的定義如下:

public final class TopicPartition implements Serializable {
    private int hash = 0;
    private final int partition;
    private final String topic;

    public TopicPartition(String topic, int partition) {
        this.partition = partition;
        this.topic = topic;
    }

    public int partition() {
        return this.partition;
    }

    public String topic() {
        return this.topic;
    }

可以看到,裏面一共有3個成員變量,hash變量主要和hashcode值相關。我們主要看一下partition和topic這兩個變量。topic變量就是指主題,partition變量就是指該主題的某一個分區,比如:我只訂閱topic-demo的0號分區

TopicPartition topic = new TopicPartition("topic-demo",0);
consumer.assign(Arrays.asList(topic));

那如何知道主題有哪些分區呢,kafka提供了partitionsFor( )方法。

//獲取當前topic的分區
        List<PartitionInfo> partitionInfos = kafkaConsumer.partitionsFor(topic);
        for (PartitionInfo partitionInfo : partitionInfos) {
            TopicPartition tp = new TopicPartition(topic, partitionInfo.partition());
            topicPartitions.add(tp);
        }

PartitionInfo對象的類定義如下:

public class PartitionInfo {
    private final String topic;
    private final int partition;
    private final Node leader;
    private final Node[] replicas;
    private final Node[] inSyncReplicas;

    public PartitionInfo(String topic, int partition, Node leader, Node[] replicas, Node[] inSyncReplicas) {
        this.topic = topic;
        this.partition = partition;
        this.leader = leader;
        this.replicas = replicas;
        this.inSyncReplicas = inSyncReplicas;
    }

topic,代表分區屬於哪個主題
partition,代表當前是哪個分區
leader,代表分區的leader節點信息
replicas,代表分區的AR集合列表
inSyncReplicas,代表分區的ISR集合列表
這個地方,1.0.0版本新增了一個

//OSR集合列表
private final Node[] offlineReplicas;

subscribe和assign方法的區別是:
subscribe具有消費再均衡的作用,當消費組內的消費者增加或者減少的時候,subscribe可以自動的重新分配分區,以實現消費自動均衡以及故障自動轉移。但是assign方法就不具備這個功能。這一點從API上也可以看出來,subscribe有一個可以註冊再均衡監聽器的API,但是assign方法沒有。
消息消費
消費者從kafka獲取消息採取的是拉的方式。

 ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(1000L);

poll方法中有一個參數,這個參數是一個long類型的值,單位是毫秒。代表:超時時間。返回值爲:消息集合。當超過1秒沒有拉取到消息,消費者會立即返回。
消息集合中的實際消息對象爲:

public class ConsumerRecord<K, V> {
    public static final long NO_TIMESTAMP = -1L;
    public static final int NULL_SIZE = -1;
    public static final int NULL_CHECKSUM = -1;
    private final String topic;
    private final int partition;
    private final long offset;
    private final long timestamp;
    private final TimestampType timestampType;
    private final int serializedKeySize;
    private final int serializedValueSize;
    private final Headers headers;
    private final K key;
    private final V value;
    private volatile Long checksum;
}

topic:消息來自於哪個主題
partition:消息來自於哪個主題的哪個分區
offset:消息的位移
timestamp:時間戳
timestampType:時間戳類型。一共有兩種,CreateTime和LogAppendTime,分別代表消息創建的時間戳以及消息追加到日誌中的時間戳
serializedKeySize:key序列化後的大小
serializedValueSize:value序列化後的大小
headers:消息的頭部內容
key:消息的key
value:消息的值
checksum:消息的CRC32的校驗值
以上獲取消息的方式是從主題的角度獲取所有分區的消息。我們可以將獲取消息的粒度縮小爲分區級別。我們也可以從消息集中獲取某個主題的某個分區的消息。方法爲:

List<ConsumerRecord<String, String>> records = consumerRecords.records(new TopicPartition(topic, 0));

records方法還有一個重載方法,records(topic)。按照主題維度獲取該主題的所有消息。
kafka消費者的關閉
kakfa消費者提供了wakeUp方法用來關閉消費者,同時我們也可以自己設置開關進行kafka消費者的關閉。如上面例子中的IsRunning類,就是開關類

重要的消費者參數
1 .fetch.min.bytes
poll方法,每次返回的數據量。默認爲1B,如果kafka服務端沒這麼多消息,那poll方法將阻塞等待。在生產環境中適當的增大此值,可以提高吞吐量,但是也會造成延時高
2 .fetch.max.bytes
與fetch.min.bytes相對,這個參數是指poll方法在kafka服務端所能拉取的最大消息量,默認是50MB。如果單條消息的值大於50MB,poll方法仍將返回,保證消費者可以正常工作
3 .fetch.max.wait.ms
poll方法阻塞的最長時間,如果poll方法等了這麼久,但是消息還是沒有攢夠fetch.min.ms的值,此時仍將要返回,防止poll方法無限阻塞下去。默認值是500ms
4 .max.partition.fetch.bytes
一次拉取中,一個分區最大能返回的消息量,默認值:1MB
5 .max.poll.records
一次拉取中,poll方法最多能返回的消息條數,默認爲:500條。如果消息較小,可以適當的增大此值。提升消費速度
6 .connection.max.idle.ms
指定多久之後,kafka關閉閒置的鏈接,默認爲:9分鐘
7 .exclude.internal.topics
kafka的內部主題。_consumer_offsets和_transaction_state。默認值true的情況下,只有subscribe(Collections)方法可以消費這兩個主題的消息。如果將該值設定爲false,那subscribe(Collections)和subscribe(Pattern)都可以消費內部主題。
8 .request.timeout.ms
這個參數用來設定consumer消費者客戶端連接kafka服務端的超時時間,默認值:30秒
9 .metadata.max.age.ms
強制更新kafka元數據的時間,默認值:30秒
10 .reconnect.backoff.ms
這個參數用來配置嘗試連接主機的間隔時間,默認值50(ms),防止kakfa消費者頻繁連接主機,造成資源浪費
11 .retry.backoff.ms
這個參數用來配置嘗試重新發送失敗的請求到指定的主題分區之前的等待時間,避免在某些故障情況下頻繁的重複發送,默認值100ms
12 .isolation.level
這個參數用來配置消費者的事務隔離級別。可選值有兩個,“read_uncommitted"和"read_committed”,如果設定爲read-committed,kafka消費者將只能讀取到LSO的位置。如果設定爲read-uncommitted。kafka就可以讀到HW的位置
13 .receive.buffer.bytes
這個參數用來設定接收消息緩衝區的大小,默認值爲:64KB。如果kafka的消費者和服務端跨機房的話,可以適當增大該值。如果設定爲-1的話,將會採用操作系統的值
14 .send.buffer.bytes
這個參數用來設定發送消息緩衝區的大小,默認值爲:128KB。如果設定爲-1的話,就會採用操作系統的值
關於消費者的知識非常多,先寫到這裏,下一篇介紹位移提交

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