Kafka學習(一)——初識與入門

初識Kafka

Kafka 起初是 由 LinkedIn 公司採用 Scala 語言開發的一個多分區、多副本且基於 ZooKeeper 協調的分佈式消息系統,現已被捐獻給 Apache 基金會。

基本概念

一個典型的 Kafka 體系架構包括若干 Producer、若干 Broker、若干 Consumer,以及一個 ZooKeeper 集羣,如下圖所示。其中 ZooKeeper 是 Kafka 用來負責集羣元數據的管理、控制器的選舉等操作的。Producer 將消息發送到 Broker,Broker 負責將收到的消息存儲到磁盤中,而 Consumer 負責從 Broker 訂閱並消費消息。
在這裏插入圖片描述

Producer

生產者,也就是發送消息的一方。生產者負責創建消息,然後將其投遞到 Kafka 中。

Consumer

消費者,也就是接收消息的一方。消費者連接到 Kafka 上並接收消息,進而進行相應的業務邏輯處理。

Broker

服務代理節點。對於 Kafka 而言,Broker 可以簡單地看作一個獨立的 Kafka 服務節點或 Kafka 服務實例。大多數情況下也可以將 Broker 看作一臺 Kafka 服務器,前提是這臺服務器上只部署了一個 Kafka 實例。一個或多個 Broker 組成了一個 Kafka 集羣。一般而言,我們更習慣使用首字母小寫的 broker 來表示服務代理節點。

主題(Topic)與分區(Partition)

在 Kafka 中還有兩個特別重要的概念—主題(Topic)與分區(Partition)。Kafka 中的消息以主題爲單位進行歸類,生產者負責將消息發送到特定的主題(發送到 Kafka 集羣中的每一條消息都要指定一個主題),而消費者負責訂閱主題並進行消費。

主題是一個邏輯上的概念,它還可以細分爲多個分區,一個分區只屬於單個主題,很多時候也會把分區稱爲主題分區(Topic-Partition)。同一主題下的不同分區包含的消息是不同的,分區在存儲層面可以看作一個可追加的日誌(Log)文件,消息在被追加到分區日誌文件的時候都會分配一個特定的偏移量(offset)。

offset 是消息在分區中的唯一標識,Kafka 通過它來保證消息在分區內的順序性,不過 offset 並不跨越分區,也就是說,Kafka 保證的是分區有序而不是主題有序。

在這裏插入圖片描述

多副本

Kafka 爲分區引入了多副本(Replica)機制,通過增加副本數量可以提升容災能力。

同一分區的不同副本中保存的是相同的消息(在同一時刻,副本之間並非完全一樣),副本之間是“一主多從”的關係,其中 leader 副本負責處理讀寫請求,follower 副本只負責與 leader 副本的消息同步。副本處於不同的 broker 中,當 leader 副本出現故障時,從 follower 副本中重新選舉新的 leader 副本對外提供服務。Kafka 通過多副本機制實現了故障的自動轉移,當 Kafka 集羣中某個 broker 失效時仍然能保證服務可用。

在這裏插入圖片描述
如上圖所示,Kafka 集羣中有4個 broker,某個主題中有3個分區,且副本因子(即副本個數)也爲3,如此每個分區便有1個 leader 副本和2個 follower 副本。生產者和消費者只與 leader 副本進行交互,而 follower 副本只負責消息的同步,很多時候 follower 副本中的消息相對 leader 副本而言會有一定的滯後。

Kafka 消費端也具備一定的容災能力。Consumer 使用拉(Pull)模式從服務端拉取消息,並且保存消費的具體位置,當消費者宕機後恢復上線時可以根據之前保存的消費位置重新拉取需要的消息進行消費,這樣就不會造成消息丟失。

分區中的所有副本統稱爲 AR(Assigned Replicas)。所有與 leader 副本保持一定程度同步的副本(包括 leader 副本在內)組成ISR(In-Sync Replicas),ISR 集合是 AR 集合中的一個子集。消息會先發送到 leader 副本,然後 follower 副本才能從 leader 副本中拉取消息進行同步,同步期間內 follower 副本相對於 leader 副本而言會有一定程度的滯後。

前面所說的“一定程度的同步”是指可忍受的滯後範圍,這個範圍可以通過參數進行配置。與 leader 副本同步滯後過多的副本(不包括 leader 副本)組成 OSR(Out-of-Sync Replicas),由此可見,AR=ISR+OSR。在正常情況下,所有的 follower 副本都應該與 leader 副本保持一定程度的同步,即 AR=ISR,OSR 集合爲空。

leader 副本負責維護和跟蹤 ISR 集合中所有 follower 副本的滯後狀態,當 follower 副本落後太多或失效時,leader 副本會把它從 ISR 集合中剔除。如果 OSR 集合中有 follower 副本“追上”了 leader 副本,那麼 leader 副本會把它從 OSR 集合轉移至 ISR 集合。默認情況下,當 leader 副本發生故障時,只有在 ISR 集合中的副本纔有資格被選舉爲新的 leader,而在 OSR 集合中的副本則沒有任何機會(不過這個原則也可以通過修改相應的參數配置來改變)。

ISR 與 HW 和 LEO 也有緊密的關係。HW 是 High Watermark 的縮寫,俗稱高水位,它標識了一個特定的消息偏移量(offset),消費者只能拉取到這個 offset 之前的消息。

在這裏插入圖片描述
如上圖所示,它代表一個日誌文件,這個日誌文件中有9條消息,第一條消息的 offset(LogStartOffset)爲0,最後一條消息的 offset 爲8,offset 爲9的消息用虛線框表示,代表下一條待寫入的消息。日誌文件的 HW 爲6,表示消費者只能拉取到 offset 在0至5之間的消息,而 offset 爲6的消息對消費者而言是不可見的。

LEO 是 Log End Offset 的縮寫,它標識當前日誌文件中下一條待寫入消息的 offset,上圖中 offset 爲9的位置即爲當前日誌文件的 LEO,LEO 的大小相當於當前日誌分區中最後一條消息的 offset 值加1。分區 ISR 集合中的每個副本都會維護自身的 LEO,而 ISR 集合中最小的 LEO 即爲分區的 HW,對消費者而言只能消費 HW 之前的消息。

Kafka 的複製機制既不是完全的同步複製,也不是單純的異步複製。事實上,同步複製要求所有能工作的 follower 副本都複製完,這條消息纔會被確認爲已成功提交,這種複製方式極大地影響了性能。而在異步複製方式下,follower 副本異步地從 leader 副本中複製數據,數據只要被 leader 副本寫入就被認爲已經成功提交。在這種情況下,如果 follower 副本都還沒有複製完而落後於 leader 副本,突然 leader 副本宕機,則會造成數據丟失。Kafka 使用的這種 ISR 的方式則有效地權衡了數據可靠性和性能之間的關係。

kafka 簡單demo

docker安裝kafka和zk
  1. 下載鏡像
    這裏使用了wurstmeister/kafka和wurstmeister/zookeeper這兩個版本的鏡像
    docker pull wurstmeister/zookeeper
    docker pull wurstmeister/kafka
    在命令中運行docker images驗證兩個鏡像已經安裝完畢

  2. 啓動

  • 啓動zookeeper容器

    docker run -d --name zookeeper -p 2181:2181 -t wurstmeister/zookeeper
    
  • 啓動kafka容器

    docker run -d --name kafka --publish 9092:9092 --link zookeeper --env KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 --env KAFKA_ADVERTISED_HOST_NAME=192.168.59.101 --env KAFKA_ADVERTISED_PORT=9092 --volume /etc/localtime:/etc/localtime wurstmeister/kafka:latest
    

    192.168.59.101 改爲宿主機器的IP地址,如果不這麼設置,可能會導致在別的機器上訪問不到kafka。

簡單demo

生產者

public class ProducerFastStart {
    public static final String brokerList = "localhost:9092";
    public static final String topic = "topic-demo";

    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put("key.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("bootstrap.servers", brokerList);


        KafkaProducer<String, String> producer =
                new KafkaProducer<>(properties);
        ProducerRecord<String, String> record =
                new ProducerRecord<>(topic, "hello, Kafka!");
        try {
            producer.send(record);
        } catch (Exception e) {
            e.printStackTrace();
        }
        producer.close();
    }
}

消費者

public class ProducerFastStart {
    public static final String brokerList = "localhost:9092";
    public static final String topic = "topic-demo";

    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put("key.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("bootstrap.servers", brokerList);


        KafkaProducer<String, String> producer =
                new KafkaProducer<>(properties);
        ProducerRecord<String, String> record =
                new ProducerRecord<>(topic, "hello, Kafka!");
        try {
            producer.send(record);
        } catch (Exception e) {
            e.printStackTrace();
        }
        producer.close();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章