萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

前言

Kafka 是一款分布式消息發(fā)布和訂閱系統(tǒng),具有高性能、高吞吐量的特點(diǎn)而被廣泛應(yīng)用與大數(shù)據(jù)傳輸場(chǎng)景。它是由 LinkedIn 公司開發(fā),使用 Scala 語言編寫,之后成為 Apache 基金會(huì)的一個(gè)頂級(jí)項(xiàng)目。kafka 提供了類似 JMS 的特性,但是在設(shè)計(jì)和實(shí)現(xiàn)上是完全不同的,而且他也不是 JMS 規(guī)范的實(shí)現(xiàn)。

Kafka簡(jiǎn)介

kafka產(chǎn)生背景

kafka 作為一個(gè)消息系統(tǒng),早起設(shè)計(jì)的目的是用作 LinkedIn 的活動(dòng)流(Activity Stream)和運(yùn)營(yíng)數(shù)據(jù)處理管道(Pipeline)。活動(dòng)流數(shù)據(jù)是所有的網(wǎng)站對(duì)用戶的使用情況做分析的時(shí)候要用到的最常規(guī)的部分,活動(dòng)數(shù)據(jù)包括頁面的訪問量(PV)、被查看內(nèi)容方面的信息以及搜索內(nèi)容。這種數(shù)據(jù)通常的處理方式是先把各種活動(dòng)以日志的形式寫入某種文件,然后周期性的對(duì)這些文件進(jìn)行統(tǒng)計(jì)分析。運(yùn)營(yíng)數(shù)據(jù)指的是服務(wù)器的性能數(shù)據(jù)(CPU、IO 使用率、請(qǐng)求時(shí)間、服務(wù)日志等)。

Kafka應(yīng)用場(chǎng)景

由于 kafka 具有更好的吞吐量、內(nèi)置分區(qū)、冗余及容錯(cuò)性的優(yōu)點(diǎn)(kafka 每秒可以處理幾十萬消息),讓 kafka 成為了一個(gè)很好的大規(guī)模消息處理應(yīng)用的解決方案。

日志收集:日志收集方面,有很多比較優(yōu)秀的產(chǎn)品,比如 Apache Flume,很多公司使用kafka 代理日志聚合。

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

`

kafka架構(gòu)

一個(gè)典型的 kafka 集群包含若干 Producer(可以是應(yīng)用節(jié)點(diǎn)產(chǎn)生的消息,也可以是通過Flume 收集日志產(chǎn)生的事件),若干個(gè) Broker(kafka 支持水平擴(kuò)展)、若干個(gè) Consumer Group,以及一個(gè) zookeeper 集群。kafka 通過 zookeeper 管理集群配置及服務(wù)協(xié)同。

Producer 使用 push 模式將消息發(fā)布到 broker,consumer 通過監(jiān)聽使用 pull 模式從broker 訂閱并消費(fèi)消息。多個(gè) broker 協(xié)同工作,producer 和 consumer 部署在各個(gè)業(yè)務(wù)邏輯中。三者通過zookeeper 管理協(xié)調(diào)請(qǐng)求和轉(zhuǎn)發(fā)。這樣就組成了一個(gè)高性能的分布式消息發(fā)布和訂閱系統(tǒng)。圖上有一個(gè)細(xì)節(jié)是和其他 mq 中間件不同的點(diǎn),producer 發(fā)送消息到 broker的過程是 push,而 consumer 從 broker 消費(fèi)消息的過程是 pull,主動(dòng)去拉數(shù)據(jù)。而不是 broker 把數(shù)據(jù)主動(dòng)發(fā)送給 consumer

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

名詞解釋:

Topic:Kafka將消息分門別類,每一類的消息稱之為一個(gè)主題(Topic)。

Producer:發(fā)布消息的對(duì)象稱之為主題生產(chǎn)者(Kafka topic producer)

Consumer:訂閱消息并處理發(fā)布的消息的對(duì)象稱之為主題消費(fèi)者(consumers)

Broker:已發(fā)布的消息保存在一組服務(wù)器中,稱之為Kafka集群。集群中的每一個(gè)服務(wù)器都是一個(gè)代理(Broker)。 消費(fèi)者可以訂閱一個(gè)或多個(gè)主題(topic),并從Broker拉數(shù)據(jù),從而消費(fèi)這些已發(fā)布的消息。

Topic和Log:Topic是發(fā)布的消息的類別名,一個(gè)topic可以有零個(gè),一個(gè)或多個(gè)消費(fèi)者訂閱該主題的消息。對(duì)于每個(gè)topic,Kafka集群都會(huì)維護(hù)一個(gè)分區(qū)log,就像下圖中所示:

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

每一個(gè)分區(qū)都是一個(gè)順序的、不可變的消息隊(duì)列, 并且可以持續(xù)的添加。分區(qū)中的消息都被分了一個(gè)序列號(hào),稱之為偏移量(offset),在每個(gè)分區(qū)中此偏移量都是唯一的。

Kafka集群保持所有的消息,直到它們過期(無論消息是否被消費(fèi))。可以看到這種設(shè)計(jì)對(duì)消費(fèi)者來說操作自如,一個(gè)消費(fèi)者的操作不會(huì)影響其它消費(fèi)者對(duì)此log的處理。

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

分布式:Log的分區(qū)被分布到集群中的多個(gè)服務(wù)器上。每個(gè)服務(wù)器處理它分到的分區(qū)。 根據(jù)配置每個(gè)分區(qū)還可以復(fù)制到其它服務(wù)器作為備份容錯(cuò)。 每個(gè)分區(qū)有一個(gè)leader,零或多個(gè)follower。Leader處理此分區(qū)的所有的讀寫請(qǐng)求,而follower被動(dòng)的復(fù)制數(shù)據(jù)。 這樣可以平衡負(fù)載,避免所有的請(qǐng)求都只讓一臺(tái)或者某幾臺(tái)服務(wù)器處理。

生產(chǎn)者:生產(chǎn)者往某個(gè)Topic上發(fā)布消息。生產(chǎn)者也負(fù)責(zé)選擇發(fā)布到Topic上的哪一個(gè)分區(qū)。最簡(jiǎn)單的方式從分區(qū)列表中輪流選擇。也可以根據(jù)某種算法依照權(quán)重選擇分區(qū)。開發(fā)者負(fù)責(zé)如何選擇分區(qū)的算法。

消費(fèi)者:通常來講,消息模型可以分為兩種, 隊(duì)列和發(fā)布-訂閱式。 隊(duì)列的處理方式是 一組消費(fèi)者從服務(wù)器讀取消息,一條消息只有其中的一個(gè)消費(fèi)者來處理。在發(fā)布-訂閱模型中,消息被廣播給所有的消費(fèi)者,接收到消息的消費(fèi)者都可以處理此消息。Kafka為這兩種模型提供了單一的消費(fèi)者抽象模型: 消費(fèi)者組 (consumer group)。 消費(fèi)者用一個(gè)消費(fèi)者組名標(biāo)記自己。 一個(gè)發(fā)布在Topic上消息被分發(fā)給此消費(fèi)者組中的一個(gè)消費(fèi)者。 每個(gè)組包含數(shù)目不等的消費(fèi)者, 一個(gè)組內(nèi)多個(gè)消費(fèi)者可以用來擴(kuò)展性能和容錯(cuò)。正如下圖所示:

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

2個(gè)kafka集群托管4個(gè)分區(qū)(P0-P3),2個(gè)消費(fèi)者組,消費(fèi)組A有2個(gè)消費(fèi)者實(shí)例,消費(fèi)組B有4個(gè)。

Docker搭建kafka

下載以下三個(gè)鏡像

docker pull wurstmeister/zookeeper
docker pull wurstmeister/kafka
docker pull sheepkiller/kafka-manager

kafka-manager是kafka的可視化管理工具

啟動(dòng)容器

docker run -d --name zookeeper --publish 2181:2181 \--volume /etc/localtime:/etc/localtime \--restart=always \wurstmeister/zookeeper
docker run -d --name kafka --publish 9082:9092 \--link zookeeper:zookeeper \--env KAFKA_BROKER_ID=100 \--env HOST_IP=127.0.0.1 \--env KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 \--env KAFKA_ADVERTISED_HOST_NAME=192.168.1.108 \--env KAFKA_ADVERTISED_PORT=9082 \--restart=always \--volume /etc/localtime:/etc/localtime \wurstmeister/kafka
docker run -d --name kafka-manager \--link zookeeper:zookeeper \--link kafka:kafka -p 9001:9000 \--restart=always \--env ZK_HOSTS=zookeeper:2181 \sheepkiller/kafka-manager

訪問

http://127.0.0.1:9001

添加Cluster

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

查看界面

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

搭建完畢,頁面其他功能自己摸索下

Kafka快速加入門

//以下Spring Boot應(yīng)用程序?qū)⑷齻€(gè)消息發(fā)送到一個(gè)主題,接收它們,然后停止:
@SpringBootApplication
public class Application implements CommandLineRunner {

    public static Logger logger = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args).close();
    }

    @Autowired
    private KafkaTemplate<String, String> template;

    private final CountDownLatch latch = new CountDownLatch(3);

    @Override
    public void run(String... args) throws Exception {
        this.template.send("myTopic", "foo1");
        this.template.send("myTopic", "foo2");
        this.template.send("myTopic", "foo3");
        latch.await(60, TimeUnit.SECONDS);
        logger.info("All received");
    }

    @KafkaListener(topics = "myTopic")
    public void listen(ConsumerRecord<?, ?> cr) throws Exception {
        logger.info(cr.toString());
        latch.countDown();
    }

}

Kafka進(jìn)階

通信原理

消息是 kafka 中最基本的數(shù)據(jù)單元,在 kafka 中,一條消息由 key、 value 兩部分構(gòu)成,在發(fā)送一條消息時(shí),我們可以指定這個(gè) key,那么 producer 會(huì)根據(jù) key 和 partition 機(jī)制來判斷當(dāng)前這條消息應(yīng)該發(fā)送并存儲(chǔ)到哪個(gè) partition 中。我們可以根據(jù)需要進(jìn)行擴(kuò)展 producer 的 partition 機(jī)制。

消息默認(rèn)的分發(fā)機(jī)制

默認(rèn)情況下,kafka 采用的是 hash 取模的分區(qū)算法。如果Key 為 null,則會(huì)隨機(jī)分配一個(gè)分區(qū)。這個(gè)隨機(jī)是在這個(gè)參數(shù)”metadata.max.age.ms”的時(shí)間范圍內(nèi)隨機(jī)選擇一個(gè)。對(duì)于這個(gè)時(shí)間段內(nèi),如果 key 為 null,則只會(huì)發(fā)送到唯一的分區(qū)。這個(gè)值值哦默認(rèn)情況下是 10 分鐘更新一次。

關(guān)于 Metadata ,這個(gè)之前沒講過,簡(jiǎn)單理解就是T opic/Partition 和 broker 的映射關(guān)系,每一個(gè) topic 的每一個(gè) partition,需要知道對(duì)應(yīng)的 broker 列表是什么, leader是誰、 follower 是誰。這些信息都是存儲(chǔ)在 Metadata 這個(gè)類里面。

消費(fèi)端如何消費(fèi)指定的分區(qū)

//通過下面的代碼,就可以消費(fèi)指定該 topic 下的 0 號(hào)分區(qū)。其他分區(qū)的數(shù)據(jù)就無法接收
//消費(fèi)指定分區(qū)的時(shí)候,不需要再訂閱
//kafkaConsumer.subscribe(Collections.singletonList(topic));
//消費(fèi)指定的分區(qū)
TopicPartition topicPartition=new 
TopicPartition(topic,0);
kafkaConsumer.assign(Arrays.asList(topicPartit
ion));

消費(fèi)原理

在實(shí)際生產(chǎn)過程中,每個(gè) topic 都會(huì)有多個(gè) partitions,多個(gè) partitions 的好處在于,一方面能夠?qū)?broker 上的數(shù)據(jù)進(jìn)行分片有效減少了消息的容量從而提升 io 性能。另外一方面,為了提高消費(fèi)端的消費(fèi)能力,一般會(huì)通過多個(gè)consumer 去消費(fèi)同一個(gè) topic ,也就是消費(fèi)端的負(fù)載均衡機(jī)制,也就是我們接下來要了解的,在多個(gè)partition 以及多個(gè) consumer 的情況下,消費(fèi)者是如何消費(fèi)消息的同時(shí),在上一節(jié)課,我們講了, kafka 存在 consumer group的 概 念 , 也 就是 group.id 一樣 的 consumer ,這些consumer 屬于一個(gè) consumer group,組內(nèi)的所有消費(fèi)者協(xié)調(diào)在一起來消費(fèi)訂閱主題的所有分區(qū)。當(dāng)然每一個(gè)分區(qū)只能由同一個(gè)消費(fèi)組內(nèi)的 consumer 來消費(fèi),那么同一個(gè)consumer group 里面的 consumer 是怎么去分配該消費(fèi)哪個(gè)分區(qū)里的數(shù)據(jù)的呢?如下圖所示, 3 個(gè)分區(qū), 3 個(gè)消費(fèi)者,那么哪個(gè)消費(fèi)者消分哪個(gè)分區(qū)?

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

分區(qū)分配策略

在 kafka 中,存在兩種分區(qū)分配策略,一種是 Range(默認(rèn))、另 一 中 另 一 中 還 是 RoundRobin ( 輪 詢 )。 通過partition.assignment.strategy 這個(gè)參數(shù)來設(shè)置。

Range strategy(范圍分區(qū))

Range 策略是對(duì)每個(gè)主題而言的,首先對(duì)同一個(gè)主題里面的分區(qū)按照序號(hào)進(jìn)行排序,并對(duì)消費(fèi)者按照字母順序進(jìn)行排序。假設(shè)我們有 10 個(gè)分區(qū),3 個(gè)消費(fèi)者,排完序的分區(qū)將會(huì)是 0, 1, 2, 3, 4, 5, 6, 7, 8, 9;消費(fèi)者線程排完序?qū)?huì)是C1-0, C2-0, C3-0。然后將 partitions 的個(gè)數(shù)除于消費(fèi)者線程的總數(shù)來決定每個(gè)消費(fèi)者線程消費(fèi)幾個(gè)分區(qū)。如果除不盡,那么前面幾個(gè)消費(fèi)者線程將會(huì)多消費(fèi)一個(gè)分區(qū)。在我們的例子里面,我們有 10 個(gè)分區(qū),3 個(gè)消費(fèi)者線程, 10 / 3 = 3,而且除不盡,那么消費(fèi)者線程 C1-0 將會(huì)多消費(fèi)一個(gè)分區(qū),所以最后分區(qū)分配的結(jié)果看起來是這樣的:

  • C1-0 將消費(fèi) 0, 1, 2, 3 分區(qū)
  • C2-0 將消費(fèi) 4, 5, 6 分區(qū)
  • C3-0 將消費(fèi) 7, 8, 9 分區(qū)

假如我們有 11 個(gè)分區(qū),那么最后分區(qū)分配的結(jié)果看起來是這樣的:

  • C1-0 將消費(fèi) 0, 1, 2, 3 分區(qū)
  • C2-0 將消費(fèi) 4, 5, 6, 7 分區(qū)
  • C3-0 將消費(fèi) 8, 9, 10 分區(qū)

假如我們有 2 個(gè)主題(T1 和 T2),分別有 10 個(gè)分區(qū),那么最后分區(qū)分配的結(jié)果看起來是這樣的:

  • C1-0 將消費(fèi) T1 主題的 0, 1, 2, 3 分區(qū)以及 T2 主題的 0, 1, 2, 3 分區(qū)
  • C2-0 將消費(fèi) T1 主題的 4, 5, 6 分區(qū)以及 T2 主題的 4, 5, 6 分區(qū)
  • C3-0 將消費(fèi) T1 主題的 7, 8, 9 分區(qū)以及 T2 主題的 7, 8, 9 分區(qū)

可以看出,C1-0 消費(fèi)者線程比其他消費(fèi)者線程多消費(fèi)了 2 個(gè)分區(qū),這就是 Range strategy 的一個(gè)很明顯的弊端

RoundRobin strategy(輪詢分區(qū))

輪詢分區(qū)策略是把所有 partition 和所有 consumer 線程都列出來,然后按照 hashcode 進(jìn)行排序。最后通過輪詢算法分配 partition 給消費(fèi)線程。如果所有 consumer 實(shí)例的訂閱是相同的,那么 partition 會(huì)均勻分布。

在我們的例子里面,假如按照 hashCode 排序完的 topicpartitions 組依次為 T1-5, T1-3, T1-0, T1-8, T1-2, T1-1, T1-4, T1-7, T1-6, T1-9,我們的消費(fèi)者線程排序?yàn)?C1-0, C1-1, C2-0, C2-1,最后分區(qū)分配的結(jié)果為:

  • C1-0 將消費(fèi) T1-5, T1-2, T1-6 分區(qū);
  • C1-1 將消費(fèi) T1-3, T1-1, T1-9 分區(qū);
  • C2-0 將消費(fèi) T1-0, T1-4 分區(qū);
  • C2-1 將消費(fèi) T1-8, T1-7 分區(qū);

使用輪詢分區(qū)策略必須滿足兩個(gè)條件

  1. 每個(gè)主題的消費(fèi)者實(shí)例具有相同數(shù)量的流
  2. 每個(gè)消費(fèi)者訂閱的主題必須是相同的

什么時(shí)候會(huì)觸發(fā)這個(gè)策略呢?

當(dāng)出現(xiàn)以下幾種情況時(shí),kafka 會(huì)進(jìn)行一次分區(qū)分配操作,

也就是 kafka consumer 的 rebalance

  1. 同一個(gè) consumer group 內(nèi)新增了消費(fèi)者
  2. 消費(fèi)者離開當(dāng)前所屬的 consumer group,比如主動(dòng)停機(jī)或者宕機(jī)
  3. topic 新增了分區(qū)(也就是分區(qū)數(shù)量發(fā)生了變化)
    kafka consuemr 的 rebalance 機(jī)制規(guī)定了一個(gè) consumergroup 下的所有 consumer 如何達(dá)成一致來分配訂閱 topic的每個(gè)分區(qū)。而具體如何執(zhí)行分區(qū)策略,就是前面提到過的兩種內(nèi)置的分區(qū)策略。而 kafka 對(duì)于分配策略這塊,提供了可插拔的實(shí)現(xiàn)方式, 也就是說,除了這兩種之外,我們還可以創(chuàng)建自己的分配機(jī)制。

什么時(shí)候會(huì)觸發(fā)這個(gè)策略呢?

當(dāng)出現(xiàn)以下幾種情況時(shí),kafka 會(huì)進(jìn)行一次分區(qū)分配操作,也就是 kafka consumer 的 rebalance

  1. 同一個(gè) consumer group 內(nèi)新增了消費(fèi)者
  2. 消費(fèi)者離開當(dāng)前所屬的 consumer group,比如主動(dòng)停機(jī)或者宕機(jī)
  3. topic 新增了分區(qū)(也就是分區(qū)數(shù)量發(fā)生了變化)kafka consuemr 的 rebalance 機(jī)制規(guī)定了一個(gè) consumergroup 下的所有 consumer 如何達(dá)成一致來分配訂閱 topic的每個(gè)分區(qū)。而具體如何執(zhí)行分區(qū)策略,就是前面提到過的兩種內(nèi)置的分區(qū)策略。而 kafka 對(duì)于分配策略這塊,提供了可插拔的實(shí)現(xiàn)方式, 也就是說,除了這兩種之外,我們還可以創(chuàng)建自己的分配機(jī)制。

誰來執(zhí)行 Rebalance 以及管理 consumer 的 group 呢?

Kafka 提供了一個(gè)角色: coordinator 來執(zhí)行對(duì)于 consumer group 的管理,Kafka 提供了一個(gè)角色:coordinator 來執(zhí)行對(duì)于 consumer group 的管理,當(dāng) consumer group 的第一個(gè) consumer 啟動(dòng)的時(shí)候,它會(huì)去和 kafka server 確定誰是它們組的 coordinator。之后該 group 內(nèi)的所有成員都會(huì)和該 coordinator 進(jìn)行協(xié)調(diào)通信

如何確定 coordinator

consumer group 如何確定自己的 coordinator 是誰呢, 消費(fèi) 者 向 kafka 集 群 中 的 任 意 一 個(gè) broker 發(fā) 送 一 個(gè)GroupCoordinatorRequest 請(qǐng)求,服務(wù)端會(huì)返回一個(gè)負(fù)載最 小 的 broker 節(jié) 點(diǎn) 的 id , 并 將 該 broker 設(shè) 置 為coordinator

JoinGroup 的過程

在 rebalance 之前,需要保證 coordinator 是已經(jīng)確定好了的,整個(gè) rebalance 的過程分為兩個(gè)步驟,Join 和 Syncjoin: 表示加入到 consumer group 中,在這一步中,所有的成員都會(huì)向 coordinator 發(fā)送 joinGroup 的請(qǐng)求。一旦所有成員都發(fā)送了 joinGroup 請(qǐng)求,那么 coordinator 會(huì)選擇一個(gè) consumer 擔(dān)任 leader 角色,并把組成員信息和訂閱信息發(fā)送消費(fèi)者

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

protocol_metadata: 序列化后的消費(fèi)者的訂閱信息

leader_id: 消費(fèi)組中的消費(fèi)者,coordinator 會(huì)選擇一個(gè)座位 leader,對(duì)應(yīng)的就是 member_id

member_metadata 對(duì)應(yīng)消費(fèi)者的訂閱信息

members:consumer group 中全部的消費(fèi)者的訂閱信息

generation_id: 年代信息,類似于之前講解 zookeeper 的時(shí)候的 epoch 是一樣的,對(duì)于每一輪 rebalance

generation_id 都會(huì)遞增。主要用來保護(hù) consumer group。隔離無效的 offset 提交。也就是上一輪的 consumer 成員無法提交 offset 到新的 consumer group 中。

Synchronizing Group State 階段

完成分區(qū)分配之后,就進(jìn)入了 Synchronizing Group State階段,主要邏輯是向 GroupCoordinator 發(fā)送SyncGroupRequest 請(qǐng)求,并且處理 SyncGroupResponse響應(yīng),簡(jiǎn)單來說,就是 leader 將消費(fèi)者對(duì)應(yīng)的 partition 分配方案同步給 consumer group 中的所有 consumer

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

每個(gè)消費(fèi)者都會(huì)向 coordinator 發(fā)送 syncgroup 請(qǐng)求,不過只有 leader 節(jié)點(diǎn)會(huì)發(fā)送分配方案,其他消費(fèi)者只是打打醬油而已。當(dāng) leader 把方案發(fā)給 coordinator 以后,coordinator 會(huì)把結(jié)果設(shè)置到 SyncGroupResponse 中。這樣所有成員都知道自己應(yīng)該消費(fèi)哪個(gè)分區(qū)。

? consumer group 的分區(qū)分配方案是在客戶端執(zhí)行的!Kafka 將這個(gè)權(quán)利下放給客戶端主要是因?yàn)檫@樣做可以有更好的靈活性

如何保存消費(fèi)端的消費(fèi)位置

什么是 offset

前面在講partition 的時(shí)候,提到過 offset, 每個(gè) topic可以劃分多個(gè)分區(qū)(每個(gè) Topic 至少有一個(gè)分區(qū)),同一topic 下的不同分區(qū)包含的消息是不同的。每個(gè)消息在被添加到分區(qū)時(shí),都會(huì)被分配一個(gè) offset(稱之為偏移量),它是消息在此分區(qū)中的唯一編號(hào), kafka 通過 offset 保證消息在分區(qū)內(nèi)的順序, offset 的順序不跨分區(qū),即 kafka 只保證在同一個(gè)分區(qū)內(nèi)的消息是有序的; 對(duì)于應(yīng)用層的消費(fèi)來說,每次消費(fèi)一個(gè)消息并且提交以后,會(huì)保存當(dāng)前消費(fèi)到的最近的一個(gè) offset。那么 offset 保存在哪里?

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

offset 在哪里維護(hù)?

在 kafka 中,提供了一個(gè)_consumer_offsets的一個(gè)topic ,把 offset 信息寫入到這個(gè)topic中。

_consumer_offsets——按保存了每個(gè) consumer group某一時(shí)刻提交的 offset 信息。 consumer_offsets 默認(rèn)有50 個(gè)分區(qū)。

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

消息的存儲(chǔ)原理

消息的保存路徑

消息發(fā)送端發(fā)送消息到 broker 上以后,消息是如何持久化的呢?那么接下來去分析下消息的存儲(chǔ)

首先我們需要了解的是, kafka 是使用日志文件的方式來保存生產(chǎn)者和發(fā)送者的消息,每條消息都有一個(gè) offset 值來表示它在分區(qū)中的偏移量。 Kafka 中存儲(chǔ)的一般都是海量的消息數(shù)據(jù),為了避免日志文件過大,Log 并不是直接對(duì)應(yīng)在一個(gè)磁盤上的日志文件,而是對(duì)應(yīng)磁盤上的一個(gè)目錄,這個(gè)目錄的明明規(guī)則是<topic_name>_<partition_id>比如創(chuàng)建一個(gè)名為 firstTopic 的 topic,其中有 3 個(gè) partition,那么在 kafka 的數(shù)據(jù)目錄(/tmp/kafka-log)中就有 3 個(gè)目錄,firstTopic-0~3

多個(gè)分區(qū)在集群中的分配

如果我們對(duì)于一個(gè) topic,在集群中創(chuàng)建多個(gè) partition,那么 partition 是如何分布的呢?

1.將所有 N Broker 和待分配的 i 個(gè) Partition 排序

2.將第 i 個(gè) Partition 分配到第(i mod n)個(gè) Broker 上

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

了解到這里的時(shí)候,大家再結(jié)合前面講的消息分發(fā)策略,就應(yīng)該能明白消息發(fā)送到 broker 上,消息會(huì)保存到哪個(gè)分區(qū)中,并且消費(fèi)端應(yīng)該消費(fèi)哪些分區(qū)的數(shù)據(jù)了。

消息寫入的性能

我們現(xiàn)在大部分企業(yè)仍然用的是機(jī)械結(jié)構(gòu)的磁盤,如果把消息以隨機(jī)的方式寫入到磁盤,那么磁盤首先要做的就是尋址,也就是定位到數(shù)據(jù)所在的物理地址,在磁盤上就要找到對(duì)應(yīng)的柱面、磁頭以及對(duì)應(yīng)的扇區(qū);這個(gè)過程相對(duì)內(nèi)存來說會(huì)消耗大量時(shí)間,為了規(guī)避隨機(jī)讀寫帶來的時(shí)間消耗, kafka 采用順序?qū)懙姆绞酱鎯?chǔ)數(shù)據(jù)。

頁緩存

順序?qū)懭胧荎afka高吞吐量的一個(gè)原因,當(dāng)然即使采用的是磁盤的順序?qū)懭耄敲匆彩菦]有辦法和內(nèi)存相比的。因?yàn)闉榱嗽僖淮翁岣逰akfa的吞吐量,Kafka采用了Memory Mapped Files

(后面簡(jiǎn)稱mmap)也被翻譯成內(nèi)存映射文件 ,它的工作原理是直接利用操作系統(tǒng)的page cache 來實(shí)現(xiàn)文件到物理內(nèi)存的直接映射,完成映射之后你對(duì)物理內(nèi)存的操作會(huì)被同步到硬盤上(操作系統(tǒng)在適當(dāng)?shù)臅r(shí)候)。

操作系統(tǒng)本身有一層緩存,叫做page cache,是在內(nèi)存里的緩存,我們也可以稱之為os cache,意思就是操作系統(tǒng)自己管理的緩存。你在寫入磁盤文件的時(shí)候,可以直接寫入這個(gè)os cache里,也就是僅僅寫入內(nèi)存中,接下來由操作系統(tǒng)自己決定什么時(shí)候把os cache里的數(shù)據(jù)真的刷入磁

盤文件中(每5秒檢查一次是否需要將頁緩存數(shù)據(jù)同步到磁盤文件)。僅僅這一個(gè)步驟,就可以將磁盤文件寫性能提升很多了,因?yàn)槠鋵?shí)這里相當(dāng)于是在寫內(nèi)存,不是在寫磁盤.

零拷貝

消息從發(fā)送到落地保存,broker 維護(hù)的消息日志本身就是文件目錄,每個(gè)文件都是二進(jìn)制保存,生產(chǎn)者和消費(fèi)者使用相同的格式來處理。在消費(fèi)者獲取消息時(shí),服務(wù)器先從硬盤讀取數(shù)據(jù)到內(nèi)存,然后把內(nèi)存中的數(shù)據(jù)原封不動(dòng)的通過 socket 發(fā)送給消費(fèi)者。雖然這個(gè)操作描述起來很簡(jiǎn)單,但實(shí)際上經(jīng)歷了很多步驟。

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

? 操作系統(tǒng)將數(shù)據(jù)從磁盤讀入到內(nèi)核空間的頁緩存

? 應(yīng)用程序?qū)?shù)據(jù)從內(nèi)核空間讀入到用戶空間緩存中

? 應(yīng)用程序?qū)?shù)據(jù)寫回到內(nèi)核空間到 socket 緩存中

? 操作系統(tǒng)將數(shù)據(jù)從 socket 緩沖區(qū)復(fù)制到網(wǎng)卡緩沖區(qū),以便將數(shù)據(jù)經(jīng)網(wǎng)絡(luò)發(fā)出

這個(gè)過程涉及到 4 次上下文切換以及 4 次數(shù)據(jù)復(fù)制,并且有兩次復(fù)制操作是由 CPU 完成。但是這個(gè)過程中,數(shù)據(jù)完全沒有進(jìn)行變化,僅僅是從磁盤復(fù)制到網(wǎng)卡緩沖區(qū)。

通過“ 零拷貝 ”技術(shù),可以去掉這些沒必要的數(shù)據(jù)復(fù)制操作,同時(shí)也會(huì)減少上下文切換次數(shù)。現(xiàn)代的 unix 操作系統(tǒng)提供一個(gè)優(yōu)化的代碼路徑,用于將數(shù)據(jù)從頁緩存?zhèn)鬏數(shù)?socket;在 Linux 中,是通過 sendfile 系統(tǒng)調(diào)用來完成的。Java 提供了訪問這個(gè)系統(tǒng)調(diào)用的方法: FileChannel.transferTo API

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

使用 sendfile,只需要一次拷貝就行,允許操作系統(tǒng)將數(shù)據(jù)直接從頁緩存發(fā)送到網(wǎng)絡(luò)上。所以在這個(gè)優(yōu)化的路徑中,只有最后一步將數(shù)據(jù)拷貝到網(wǎng)卡緩存中是需要的

消息的文件存儲(chǔ)機(jī)制

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

前面我們知道了一個(gè) topic 的多個(gè) partition 在物理磁盤上的保存路徑,那么我們?cè)賮矸治鋈罩镜拇鎯?chǔ)方式。通過如下命令找到對(duì)應(yīng) partition 下的日志內(nèi)容

[root@localhost ~]# ls /tmp/kafka-logs/firstTopic-1/00000000000000000000.index 00000000000000000000.log  00000000000000000000.timeindex   leader-epochcheckpoint

kafka 是通過分段的方式將 Log 分為多個(gè) LogSegment,LogSegment 是一個(gè)邏輯上的概念,一個(gè) LogSegment 對(duì)應(yīng)磁盤上的一個(gè)日志文件和一個(gè)索引文件,其中日志文件是用來記錄消息的。索引文件是用來保存消息的索引。那么這個(gè) LogSegment 是什么呢?

LogSegment

假設(shè) kafka 以 partition 為最小存儲(chǔ)單位,那么我們可以想象當(dāng) kafka producer 不斷發(fā)送消息,必然會(huì)引起 partition文件的無線擴(kuò)張,這樣對(duì)于消息文件的維護(hù)以及被消費(fèi)的消息的清理帶來非常大的挑戰(zhàn),所以 kafka 以 segment 為單位又把 partition 進(jìn)行細(xì)分。每個(gè) partition 相當(dāng)于一個(gè)巨型文件被平均分配到多個(gè)大小相等的 segment 數(shù)據(jù)文件中(每個(gè) segment 文件中的消息不一定相等),這種特性方便已經(jīng)被消費(fèi)的消息的清理,提高磁盤的利用率。

? log.segment.bytes=107370 ( 設(shè)置分段大小 ), 默認(rèn)是1gb,我們把這個(gè)值調(diào)小以后,可以看到日志分段的效果

? 抽取其中 3 個(gè)分段來進(jìn)行分析

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

segment file 由 2 大部分組成,分別為 index file 和 data file,此 2 個(gè)文件一一對(duì)應(yīng),成對(duì)出現(xiàn),后綴".index"和“.log”分別表示為 segment 索引文件、數(shù)據(jù)文件.segment 文件命名規(guī)則:partion 全局的第一個(gè) segment從 0 開始,后續(xù)每個(gè) segment 文件名為上一個(gè) segment文件最后一條消息的 offset 值進(jìn)行遞增。數(shù)值最大為 64 位long 大小,20 位數(shù)字字符長(zhǎng)度,沒有數(shù)字用 0 填。

segment 中 index 和 log 的對(duì)應(yīng)關(guān)系

從所有分段中,找一個(gè)分段進(jìn)行分析為了提高查找消息的性能,為每一個(gè)日志文件添加 2 個(gè)索引索引文件: OffsetIndex 和 TimeIndex,分別對(duì)應(yīng) .index以及 .timeindex, TimeIndex 索引文件格式:它是映射時(shí)間戳和相對(duì)offset

查 看 索 引 內(nèi) 容 : sh  kafka-run-class.sh 
kafka.tools.DumpLogSegments  --files  /tmp/kafkalogs/test-0/00000000000000000000.index  --print-datalog
萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

如圖所示,index 中存儲(chǔ)了索引以及物理偏移量。 log 存儲(chǔ)了消息的內(nèi)容。索引文件的元數(shù)據(jù)執(zhí)行對(duì)應(yīng)數(shù)據(jù)文件中

message 的物理偏移地址。舉個(gè)簡(jiǎn)單的案例來說,以[4053,80899]為例,在 log 文件中,對(duì)應(yīng)的是第 4053 條記錄,物理偏移量( position )為 80899. position 是ByteBuffer 的指針位置

在 partition 中如何通過 offset 查找 message

  1. 根據(jù) offset 的值,查找 segment 段中的 index 索引文件。由于索引文件命名是以上一個(gè)文件的最后一個(gè)offset 進(jìn)行命名的,所以,使用二分查找算法能夠根據(jù)offset 快速定位到指定的索引文件。
  2. 找到索引文件后,根據(jù) offset 進(jìn)行定位,找到索引文件中的符合范圍的索引。(kafka 采用稀疏索引的方式來提高查找性能)
  3. 得到 position 以后,再到對(duì)應(yīng)的 log 文件中,從 position出開始查找 offset 對(duì)應(yīng)的消息,將每條消息的 offset 與目標(biāo) offset 進(jìn)行比較,直到找到消息

比如說,我們要查找 offset=2490 這條消息,那么先找到00000000000000000000.index, 然后找到[2487,49111]這個(gè)索引,再到 log 文件中,根據(jù) 49111 這個(gè) position 開始查找,比較每條消息的 offset 是否大于等于 2490。最后查找到對(duì)應(yīng)的消息以后返回

日志清除策略

前面提到過,日志的分段存儲(chǔ),一方面能夠減少單個(gè)文件內(nèi)容的大小,另一方面,方便 kafka 進(jìn)行日志清理。日志的清理策略有兩個(gè)

  1. 根據(jù)消息的保留時(shí)間,當(dāng)消息在 kafka 中保存的時(shí)間超過了指定的時(shí)間,就會(huì)觸發(fā)清理過程
  2. 根據(jù) topic 存儲(chǔ)的數(shù)據(jù)大小,當(dāng) topic 所占的日志文件大小大于一定的閥值,則可以開始刪除最舊的消息。 kafka會(huì)啟動(dòng)一個(gè)后臺(tái)線程,定期檢查是否存在可以刪除的消息

通過 log.retention.bytes 和 log.retention.hours 這兩個(gè)參數(shù)來設(shè)置,當(dāng)其中任意一個(gè)達(dá)到要求,都會(huì)執(zhí)行刪除。默認(rèn)的保留時(shí)間是:7 天

日志壓縮策略

Kafka 還提供了“日志壓縮(Log Compaction)”功能,通過這個(gè)功能可以有效的減少日志文件的大小,緩解磁盤緊張的情況,在很多實(shí)際場(chǎng)景中,消息的 key 和 value 的值之間的對(duì)應(yīng)關(guān)系是不斷變化的,就像數(shù)據(jù)庫(kù)中的數(shù)據(jù)會(huì)不斷被修改一樣,消費(fèi)者只關(guān)心 key 對(duì)應(yīng)的最新的 value。 因此,我們可以開啟 kafka 的日志壓縮功能,服務(wù)端會(huì)在后臺(tái)啟動(dòng)啟動(dòng) Cleaner 線程池,定期將相同的 key 進(jìn)行合并,只保留最新的 value 值。日志的壓縮原理是

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

partition 的高可用副本機(jī)制

我們已經(jīng)知道 Kafka 的每個(gè) topic 都可以分為多個(gè) Partition,并且多個(gè) partition 會(huì)均勻分布在集群的各個(gè)節(jié)點(diǎn)下。雖然這種方式能夠有效的對(duì)數(shù)據(jù)進(jìn)行分片,但是對(duì)于每個(gè)partition 來說,都是單點(diǎn)的,當(dāng)其中一個(gè) partition 不可用的時(shí)候,那么這部分消息就沒辦法消費(fèi)。所以 kafka 為了提高 partition 的可靠性而提供了副本的概念(Replica) ,通過副本機(jī)制來實(shí)現(xiàn)冗余備份。每個(gè)分區(qū)可以有多個(gè)副本,并且在副本集合中會(huì)存在一個(gè)leader 的副本,所有的讀寫請(qǐng)求都是由 leader 副本來進(jìn)行處理。剩余的其他副本都做為 follower 副本,follower 副本 會(huì) 從 leader 佛 本 同 步 笑 息 日 志 。 這 個(gè) 有 點(diǎn) 類 似zookeeper 中 leader 和 follower 的概念,但是具體的時(shí)間方式還是有比較大的差異。所以我們可以認(rèn)為,副本集會(huì)存在一主多從的關(guān)系。

一般情況下,同一個(gè)分區(qū)的多個(gè)副本會(huì)被均勻分配到集群中的不同 broker 上,當(dāng) leader 副本所在的 broker 出現(xiàn)故障后,可以重新選舉新的 leader 副本繼續(xù)對(duì)外提供服務(wù)。通過這樣的副本機(jī)制來提高 kafka 集群的可用性。

副本分配算法

將所有 N Broker 和待分配的 i 個(gè) Partition 排序.

將第 i 個(gè) Partition 分配到第(i mod n)個(gè) Broker 上.

將第 i 個(gè) Partition 的第 j 個(gè)副本分配到第((i + j) mod n)個(gè)Broker 上.

kafka 副本機(jī)制中的幾個(gè)概念

Kafka 分區(qū)下有可能有很多個(gè)副本(replica)用于實(shí)現(xiàn)冗余,從而進(jìn)一步實(shí)現(xiàn)高可用。副本根據(jù)角色的不同可分為 3 類:

leader 副本:響應(yīng) clients 端讀寫請(qǐng)求的副本

follower 副本:被動(dòng)的備份 leader 副本中的數(shù)據(jù),不能響應(yīng) clients 端讀寫請(qǐng)求。

ISR 副本:包含了 leader 副本和所有與 leader 副本保持同步的 follower 副本——如何判定是否與 leader 同步后面會(huì)提到每個(gè) Kafka 副本對(duì)象都有兩個(gè)重要的屬性:LEO 和HW。注意是所有的副本,而不只是 leader 副本。

LEO:即日志末端位移(log end offset),記錄了該副本底層日志(log)中下一條消息的位移值。注意是下一條消息!也就是說,如果 LEO=10,那么表示該副本保存了 10 條消息,位移值范圍是[0, 9]。另外, leader LEO 和follower LEO 的更新是有區(qū)別的。我們后面會(huì)詳細(xì)說

HW:即上面提到的水位值。對(duì)于同一個(gè)副本對(duì)象而言,其

HW 值不會(huì)大于 LEO 值。小于等于 HW 值的所有消息都被認(rèn)為是“ 已備份” 的(replicated )。同理, leader 副本和follower 副本的 HW 更新是有區(qū)別的

副本協(xié)同機(jī)制

剛剛提到了,消息的讀寫操作都只會(huì)由 leader 節(jié)點(diǎn)來接收和處理。follower 副本只負(fù)責(zé)同步數(shù)據(jù)以及當(dāng) leader 副本所在的 broker 掛了以后,會(huì)從 follower 副本中選取新的leader。

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

請(qǐng)求首先由 Leader 副本處理,之后 follower 副本會(huì)從leader 上拉取寫入的消息,這個(gè)過程會(huì)有一定的延遲,導(dǎo)致 follower 副本中保存的消息略少于 leader 副本,但是只要沒有超出閾值都可以容忍。但是如果一個(gè) follower 副本出現(xiàn)異常,比如宕機(jī)、網(wǎng)絡(luò)斷開等原因長(zhǎng)時(shí)間沒有同步到消息,那這個(gè)時(shí)候, leader 就會(huì)把它踢出去。 kafka 通過 ISR集合來維護(hù)一個(gè)分區(qū)副本信息

HW&LEO

關(guān)于 follower 副本同步的過程中,還有兩個(gè)關(guān)鍵的概念,HW(HighWatermark)和 LEO(Log End Offset). 這兩個(gè)參數(shù)跟 ISR 集合緊密關(guān)聯(lián)。 HW 標(biāo)記了一個(gè)特殊的 offset,當(dāng)消費(fèi)者處理消息的時(shí)候,只能拉去到 HW 之前的消息, HW之后的消息對(duì)消費(fèi)者來說是不可見的。也就是說,取partition 對(duì)應(yīng) ISR 中最小的 LEO 作為 HW,consumer 最多只能消費(fèi)到 HW 所在的位置。每個(gè) replica 都有 HW,leader 和 follower 各自維護(hù)更新自己的 HW 的狀態(tài)。一條消息只有被 ISR 里的所有 Follower 都從 Leader 復(fù)制過去才會(huì)被認(rèn)為已提交。這樣就避免了部分?jǐn)?shù)據(jù)被寫進(jìn)了Leader,還沒來得及被任何 Follower 復(fù)制就宕機(jī)了,而造成數(shù)據(jù)丟失(Consumer 無法消費(fèi)這些數(shù)據(jù))。而對(duì)于Producer 而言,它可以選擇是否等待消息 commit,這可以通過 acks 來設(shè)置。這種機(jī)制確保了只要 ISR 有一個(gè)或以上的 Follower,一條被 commit 的消息就不會(huì)丟失。

數(shù)據(jù)的同步過程

了解了副本的協(xié)同過程以后,還有一個(gè)最重要的機(jī)制,就是數(shù)據(jù)的同步過程。它需要解決

  1. 怎么傳播消息
  2. 在向消息發(fā)送端返回 ack 之前需要保證多少個(gè) Replica
    已經(jīng)接收到這個(gè)消息

初始狀態(tài)

初始狀態(tài)下,leader 和 follower 的 HW 和 LEO 都是 0,leader 副本會(huì)保存 remote LEO,表示所有 follower LEO,也會(huì)被初始化為 0,這個(gè)時(shí)候,producer 沒有發(fā)送消息。follower 會(huì)不斷地個(gè) leader 發(fā)送 FETCH 請(qǐng)求,但是因?yàn)闆]有數(shù)據(jù),這個(gè)請(qǐng)求會(huì)被 leader 寄存,當(dāng)在指定的時(shí)間之后會(huì) 強(qiáng) 制 完 成 請(qǐng) 秋 , 這 個(gè) 時(shí) 見 配 致 是(replica.fetch.wait.max.ms),如果在指定時(shí)間內(nèi) producer有消息發(fā)送過來,那么 kafka 會(huì)喚醒 fetch 請(qǐng)求,讓 leader繼續(xù)處理

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

這里會(huì)分兩種情況,第一種是 leader 處理完 producer 請(qǐng)求之后,follower 發(fā)送一個(gè) fetch 請(qǐng)求過來、第二種是follower 阻塞在 leader 指定時(shí)間之內(nèi),leader 副本收到producer 的請(qǐng)求。這兩種情況下處理方式是不一樣的。先來看第一種情況

一、follower 的 fetch 請(qǐng)求是當(dāng) leader 處理消息以后執(zhí)行的

leader 處理完 producer 請(qǐng)求之后,follower 發(fā)送一個(gè)fetch 請(qǐng)求過來 。狀態(tài)圖如下

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

leader 副本收到請(qǐng)求以后,會(huì)做幾件事情

  1. 把消息追加到 log 文件,同時(shí)更新 leader 副本的 LEO
  2. 嘗試更新 leader HW 值。這個(gè)時(shí)候由于 follower 副本還沒有發(fā)送 fetch 請(qǐng)求,那么 leader 的 remote LEO 仍然是 0。leader 會(huì)比較自己的 LEO 以及 remote LEO 的值發(fā)現(xiàn)最小值是 0,與 HW 的值相同,所以不會(huì)更新 HW

follower fetch 消息

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入

follower 發(fā)送 fetch 請(qǐng)求,leader 副本的處理邏輯是:

  1. 讀取 log 數(shù)據(jù)、更新 remote LEO=0(follower 還沒有寫入這條消息,這個(gè)值是根據(jù) follower 的 fetch 請(qǐng)求中的offset 來確定的)
  2. 嘗試更新 HW,因?yàn)檫@個(gè)時(shí)候 LEO 和 remoteLEO 還是不一致,所以仍然是 HW=0
  3. 把消息內(nèi)容和當(dāng)前分區(qū)的 HW 只發(fā)送給 follower 副本follower 副本收到 response 以后
  4. 將消息寫入到本地 log,同時(shí)更新 follower 的 LEO
  5. 更新 follower HW,本地的 LEO 和 leader 返回的 HW進(jìn)行比較取小的值,所以仍然是 0第一次交互結(jié)束以后, HW 仍然還是 0,這個(gè)值會(huì)在下一次follower 發(fā)起 fetch 請(qǐng)求時(shí)被更新

follower 發(fā)第二次 fetch 請(qǐng)求,leader 收到請(qǐng)求以后

萬字修行!消息中間件架構(gòu)體系:Kafka研究,從入門到深入
  1. 讀取 log 數(shù)據(jù)
  2. 更新 remote LEO=1, 因?yàn)檫@次 fetch 攜帶的 offset 是1.
  3. 更新當(dāng)前分區(qū)的 HW,這個(gè)時(shí)候 leader LEO 和 remoteLEO 都是 1,所以 HW 的值也更新為 1
  4. 把數(shù)據(jù)和當(dāng)前分區(qū)的 HW 值返回給 follower 副本,這個(gè)時(shí)候如果沒有數(shù)據(jù),則返回為空

follower 副本收到 response 以后

  1. 如果有數(shù)據(jù)則寫本地日志,并且更新 LEO
  2. 更新 follower 的 HW 值 到目前為止,數(shù)據(jù)的同步就完成了,意味著消費(fèi)端能夠消費(fèi) offset=0 這條消息。

二、follower 的 fetch 請(qǐng)求是直接從阻塞過程中觸發(fā)

前面說過,由于 leader 副本暫時(shí)沒有數(shù)據(jù)過來,所以follower 的 fetch 會(huì)被阻塞,直到等待超時(shí)或者 leader 接收到新的數(shù)據(jù)。當(dāng) leader 收到請(qǐng)求以后會(huì)喚醒處于阻塞的fetch 請(qǐng)求。處理過程基本上和前面說的一直

  1. leader 將消息寫入本地日志,更新 Leader 的 LEO
  2. 喚醒 follower 的 fetch 請(qǐng)求
  3. 更新 HWkafka 使用 HW 和 LEO 的方式來實(shí)現(xiàn)副本數(shù)據(jù)的同步,本身是一個(gè)好的設(shè)計(jì),但是在這個(gè)地方會(huì)存在一個(gè)數(shù)據(jù)丟失的問題,當(dāng)然這個(gè)丟失只出現(xiàn)在特定的背景下。我們回想一下, HW 的值是在新的一輪 FETCH 中才會(huì)被更新。我們分析下這個(gè)過程為什么會(huì)出現(xiàn)數(shù)據(jù)丟失

由于篇幅限制的原因,只能將文章展示到這里,希望可以對(duì)大家學(xué)習(xí)Kafka有幫助,喜歡的小伙伴可以幫忙轉(zhuǎn)發(fā)+關(guān)注,感謝大家~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,025評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,642評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,177評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,970評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,157評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,410評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,896評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,157評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容