如何系統的瞭解Kafka

哥不是小蘿莉發表於2021-02-28

1.概述

在大資料的浪潮下,時時刻刻都會產生大量的資料。比如社交媒體、部落格、電子商務等等,這些資料會以不同的型別儲存在不同的平臺裡面。為了執行ETL(提取、轉換、載入)操作,需要一個訊息中介軟體系統,該系統應該是非同步和低耦合的,即來自各種儲存系統(如HDFS、Cassandra、RDBMS等)的資料可以同時轉存在一個地方,而所有這些資料來源都是彼此獨立的。解決這個問題的方法之一是Kafka,它是一個開源的分散式訊息處理平臺。

2.內容

2.1 專業術語

  • Message:它基本上是一個鍵值對,在值部分包含有用的資料和記錄;
  • Topic:對於多租戶,可以建立多個主題,這只是釋出和訂閱訊息的名稱;
  • Partition:對於多執行緒任務,可以在一個Topic中,建立多個分割槽,提升生產者和消費者的效能;
  • Offset:訊息以類似於提交日誌的順序形式儲存,並且從0開始為每個訊息提供順序ID(每個分割槽偏移量從0開始);
  • Broker:Kafka叢集由多個服務節點組成,這些服務節點只是叢集中託管Zookeeper維護的無狀態伺服器的節點,英文這裡沒有主從概念,所以所有的Broker都是同級別的(每個Partition上會有Leader和Follower);
  • Consumer:用於消費Topic的應用程式;
  • ConsumerGroup:消費不同Topic所使用的相同GroupID;
  • Producer:用於生產Topic的應用程式。

2.2 Kafka為何需要Zookeeper

Zookeeper是一個分散式叢集管理系統,它是一個為分散式應用提供一致性服務的軟體,提供的功能包含:配置維護、域名服務、分散式服務等。它目標就是封裝好複雜易出錯的關鍵服務,將簡單易用的介面和效能高效、功能穩定的提供提供給使用者。而在Kafka中,它提供了以下功能:

  • 控制器選舉:對於特定主題,分割槽中的所有讀寫操作都是通過複製副本的資料來完成的,每當Leader當機,Zookeeper就會選舉出新的Leader來提供服務;
  • 配置Topic:與某個Topic相關的後設資料,即某個特定Topic是否位於Broker中,有多少個Partition等儲存在Zookeeper中,並在生產訊息時持續同步;
  • ACL:Topic的許可權控制均在Zookeeper中進行維護。

2.3 Kafka有哪些特性?

Kafka的一些關鍵特性,使得它更加受到喜愛,針對傳統訊息系統的不同:

  • 高吞吐量:吞吐量表示每秒可以處理的訊息數(訊息速率)。由於我們可以將Topic分佈到不同的Broker上,因此我們可以實現每條數以千次的讀寫操作;
  • 分散式:分散式系統是一個被分割成多臺執行的機器的系統,所有這些機器在一個叢集中協同工作,在終端使用者看來是一個單一的節點。Kafka是分散式的,因為它儲存、讀取和寫入多個節點上的資料,這些節點被稱為Broker,它與Zookeeper一起共同建立了一個稱為Kafka叢集的生態系統;
  • 永續性:訊息佇列完全儲存在磁碟上,而不是儲存在記憶體中,同一資料的多個副本(ISR)可以跨不同的節點儲存。因此,不存在由於故障轉移場景而導致資料丟失的可能性,並使其具有永續性;
  • 可伸縮性:任何系統都可以水平或垂直伸縮,縱向可伸縮性意味著向相同的節點新增更多的資源,如CPU、記憶體,並且會產生很高的操作成本。水平可伸縮性可以通過簡單的在叢集中新增幾個節點來實現,這增加了容量需求。Kafka水平擴充套件意味著當我們的容量用完時,我們可以在叢集中新增一個新的節點。

2.4 Producer如何寫資料?

生產者首先獲取Topic中的後設資料,以便知道需要使用訊息更新哪個Broker。後設資料也儲存在Broker中,並與Zookeeper保持連續同步。因此,若有多個生產者都希望連線到Zookeeper來訪問後設資料,會導致效能降低。當生產者獲取了Topic和後設資料資訊,它就會在Leader所在的Broker節點的日誌中寫入訊息,而之後Follower(ISR)會將其複製進行同步。

 

 

 在寫入操作可以是同步的,即僅當Follower還在其日誌中同步訊息時,或者非同步,即只有Leader更新資訊訊息,狀態發生給生產者。磁碟上的訊息可以保留特定的持續時間,在此期限後,將自動清除舊訊息,並且不再可供使用。預設情況下,設定為7天。可以通過三種策略將訊息寫到Topic。

  1. send(key,value,topic,partition):專門提供需要進行寫操作的分割槽。不建議使用該方式,因為它可能會導致分割槽大小不均衡;
  2. send(key,value,topic):在這裡,預設的HashPartitioner用於確定要寫入訊息的分割槽,方式查詢key的Hash並進行取模,該Topic的分割槽,也可以編寫我們自己定義的分割槽程式;
  3. send(null,value,topic):在這種情況下,訊息以迴圈方式儲存在所有分割槽中。

Java生產者示例程式碼如下:

public class JProducer extends Thread {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "127.0.0.1:9092");
        props.put("acks", "1");
        props.put("retries", 0);
        props.put("batch.size", 16384);
        props.put("linger.ms", 1);
        props.put("buffer.memory", 33554432);
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        Producer<String, String> producer = new KafkaProducer<>(props);
        long counter = 1L;
        while (true) {
            String json = "{\"id\":" + (counter++) + ",\"date\":\"" + new Date().toString() + "\"}";
            String k = "key" + counter;
            producer.send(new ProducerRecord<String, String>("test01", k, json), (recordMetadata, e) -> {
                if (e == null) {
                    System.out.println(recordMetadata.topic() + "-" + recordMetadata.partition() + "-" + recordMetadata.offset());
                } else {
                    e.printStackTrace();
                }
            });
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // producer.close();
    }

}

2.5 消費者如何訂閱訊息?

由於Kafka的速度非常快並且可以獲取實時訊息,因此單個消費者肯定會在Topic中讀取很大一部分訊息時出現延遲。為了克服這類問題,可以建立一個消費者組,該消費者組由多個具有相同GroupID的消費者組成。每個使用者都連線有一個唯一的分割槽,該分割槽所在所有使用者之間平均分配。將分割槽分配給特定使用者是消費者組協調器的責任,協調器由Broker被提名擔任該角色。為了管理活躍的消費者,消費者組中的所有成員會將它們的心跳傳送到組協調器。

關於消費分割槽與消費執行緒的對應關係,理論上消費執行緒數應該小於等於分割槽數。之前是有這樣一種觀點,一個消費執行緒對應一個分割槽,當消費執行緒等於分割槽數是最大化執行緒的利用率。直接使用KafkaConsumer Client例項,這樣使用確實沒有什麼問題。但是,如果我們有富裕的CPU,其實還可以使用大於分割槽數的執行緒,來提升消費能力,這就需要我們對KafkaConsumer Client例項進行改造,實現消費策略預計算,利用額外的CPU開啟更多的執行緒,來實現消費任務分片。

在0.10.x以後的版本中,Kafka底層架構發生了變化,將消費者的資訊由Zookeeper儲存遷移到Topic(__consumer_offsets)中進行儲存。消費的偏移量Key(groupid, topic, partition)以及Value(Offset, ...)

 

 

 Java消費者示例程式碼如下:

public class JConsumer extends Thread {

    private String groupId;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1; i++) {
            JConsumerSsl jc = new JConsumerSsl("jgroup" + i);
            jc.start();
        }
    }

    public JConsumerSsl(String groupId) {
        this.groupId = groupId;
    }

    @Override
    public void run() {
        consumer(this.groupId);
    }

    public static void consumer(String groupId) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "127.0.0.1:9092");
        props.put("group.id", groupId);
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("test01"));
        boolean flag = true;
        while (flag) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records)
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            try {
                // sleep(60 * 1000L);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        consumer.close();
    }

}

2.6 Kafka為何如此之快?

由於Kafka遵循了一定的策略,這也是它設計的一部分,以使得它效能更好、更快。

  • 沒有隨機磁碟訪問:它使用稱為不可變佇列的順序資料結構,其中讀寫操作始終為恆定時間O(1)。它在末尾附加訊息,並從頭開始或者從特定偏移量讀取;
  • 順序IO:現代作業系統將其大不部分可用的記憶體分配給磁碟快取,並且更快的用於儲存和檢索順序資料;
  • 零拷貝:由於根本沒有修改資料,因此將磁碟中的資料不必要的載入到應用程式記憶體中,因此,它沒有將其載入到應用程式中,而是通過Socket位元組,緩衝區以及網路從context快取區傳送了相同的資料;
  • 訊息批處理:為了避免多次網路互動,將多個訊息分組在一起;
  • 訊息壓縮:在通過網路傳輸訊息之前,使用gzip、snappy等壓縮演算法對訊息進行壓縮,然後在Consumer中使用API將其解壓。

2.7 資料如何儲存在Broker上?

在開啟Kafka伺服器之前,Broker中的所有訊息都儲存在配置檔案中的配置的日誌目錄中,在該目錄內,可以找到包含特定Topic分割槽的資料夾,其格式topic_name-partition_number,例如topic1-0。另外,__consumer_offsets這個Topic也儲存在同一日誌目錄中。

 

 

在特定Topic的分割槽目錄中,可以找到Kafka的Segment檔案xxx.log,索引檔案xxx.index和時間索引xxx.timeindex。當達到舊的Segment大小或者時間限制時,會在建立新的Segment檔案時將屬於該分割槽的所有資料寫入活躍的Segment中。索引將每個偏移量對映到其訊息在日誌中的位置,由於偏移量時順序的,因此將二進位制搜尋應用於在特定偏移量的日誌檔案中查詢資料索引。

2.8 日誌壓縮

  • 任何保持在日誌頭部以內的使用者都將看到所寫的每條訊息,這些訊息將具有順序偏移量。可以使用Topic的min.compaction.lag.ms屬性來保證訊息在被壓縮之前必須經過的最短時間。也就是說,它為每個訊息在(未壓縮)頭部停留的時間提供了一個下限。可以使用Topic的max.compaction.lag.ms屬性來保證從編寫訊息到訊息符合壓縮條件之間的最大延時
  • 訊息始終保持順序,壓縮永遠不會重新排序訊息,只是刪除一些而已
  • 訊息的偏移量永遠不會改變,它是日誌中位置的永久識別符號
  • 從日誌開始的任何使用者將至少看到所有記錄的最終狀態,按記錄的順序寫入。另外,如果使用者在比Topic的log.cleaner.delete.retention.ms短的時間內到達日誌的頭部,則會看到已刪除記錄的所有delete標記。保留時間預設是24小時。

詳情分析可閱讀《Kafka日誌壓縮剖析》。

3.總結

以上就是筆者給大家簡要的彙總了Kafka的各個知識點,包含常見的術語、Consumer & Producer的使用方式、儲存流程等

另外,筆者開源的一款Kafka監控關係系統Kafka-Eagle,喜歡的同學可以Star一下,進行關注。

Kafka Eagle原始碼地址:https://github.com/smartloli/kafka-eagle

4.結束語

這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大資料探勘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裡點選購買連結購買博主的書進行學習,在此感謝大家的支援。關注下面公眾號,根據提示,可免費獲取書籍的教學視訊。 

 

相關文章