Kafka 生產者(Producer)詳解

                             Producer : 消息生產者,就是向Kafka broker 發消息的客戶端。

      下面將從producer的分區策略,數據可靠性保證、Exactly Once、Producer API 來進行介紹。

1、分區策略

1)分區原因

  1.    方便在集羣中擴展(相當於負載) :每個Partition 可以通過調整以適應它所在的機器,而一個topic又可以有多個Partition組成,因此整個集羣就可以適應任意大小的數據了;
  2. 可以提高併發:因爲可以以Partition爲單位讀寫了;

2)分區原則

         我們需要將 producer 發送的數據封裝成一個 ProducerRecord 對象。

  1. 指明 partition 的情況下,直接將指明的值作爲partition的值
  2. 沒有指明partition但有key的情況下,將key的hash值與topic的partition數據進行取餘得到partition值
  3. 既沒有partition值又沒有key值的情況下,第一次調用是隨機生成一個整數(後面調用在這個整數上自增),將這個值與topic可用的parition總數取餘得到partition值,也就是常說的round-robin算法。

2、數據可靠性保證

         爲保證producer發送的數據,能可靠的發送到指定的topic,topic的每個partition收到producer發送的數據後都需要向producer發送ack(acknowledgement 確認收到),如果producer收到ack,就會進行下一輪的發送,否則重新發送數據。

1)副本數據同步策略

方案 優點 缺點
半數以上完成同步,就發送ack 延遲低 選舉新的leader時,容忍n臺節點故障,需要2n+1個副本
全部完成同步,才發送ack 選舉新的leader時,容忍n臺節點故障,需要n+1 個副本 延遲高

 

 

 

 

 

Kafka選擇了第二種方案,原因如下:

  1. 同樣爲了容忍 n 臺節點的故障,第一種方案需要 2n+1 個副本,而第二種方案只需要 n+1 個副本,而 Kafka 的每個分區都有大量的數據,第一種方案會造成大量數據的冗餘。
  2. 雖然第二種方案的網絡延遲會比較高,但網絡延遲對 Kafka 的影響較小。

2)ISR

        採用第二種方案之後,設想以下情景:leader 收到數據,所有 follower 都開始同步數據, 但有一個 follower,因爲某種故障,遲遲不能與 leader 進行同步,那 leader 就要一直等下去, 直到它完成同步,才能發送 ack。這個問題怎麼解決呢?

       Leader 維護了一個動態的 in-sync replica set (ISR),意爲和 leader 保持同步的 follower 集 合。當 ISR 中的 follower 完成數據的同步之後,leader 就會給 follower 發送 ack。如果 follower 長時間 未 向 leader 同 步 數 據 , 則 該 follower 將 被 踢 出 ISR , 該 時 間 閾 值 由replica.lag.time.max.ms 參數設定。Leader 發生故障之後,就會從 ISR 中選舉新的 leader。

3)ack 應答機制

      對於某些不太重要的數據,對數據的可靠性要求不是很高,能夠容忍數據的少量丟失, 所以沒必要等 ISR 中的 follower 全部接收成功。

    所以 Kafka 爲用戶提供了三種可靠性級別,用戶根據對可靠性和延遲的要求進行權衡, 選擇以下的配置。

acks 參數配置:

   acks:

       0:producer 不等待 broker 的 ack,這一操作提供了一個最低的延遲,broker 一接收到還 沒有寫入磁盤就已經返回,當 broker 故障時有可能丟失數據

       1:producer 等待 broker 的 ack,partition 的 leader 落盤成功後返回 ack,如果在 follower 同步成功之前 leader 故障,那麼將會丟失數據;

        -1(all):producer 等待 broker 的 ack,partition 的 leader 和 follower 全部落盤成功後才 返回 ack。但是如果在 follower 同步完成後,broker 發送 ack 之前,leader 發生故障,那麼會 造成數據重複。

4)故障處理細節

   Log文件中的HW和LEO

LEO:指的是每個副本最大的 offset;

HW:指的是消費者能見到的最大的 offset,ISR 隊列中最小的 LEO。

(1)follower 故障

        follower 發生故障後會被臨時踢出 ISR,待該 follower 恢復後,follower 會讀取本地磁盤 記錄的上次的 HW,並將 log 文件高於 HW 的部分截取掉,從 HW 開始向 leader 進行同步。 等該 follower 的 LEO 大於等於該 Partition 的 HW,即 follower 追上 leader 之後,就可以重 新加入 ISR 了

(2)leader 故障

        leader 發生故障之後,會從 ISR 中選出一個新的 leader,之後,爲保證多個副本之間的數據一致性,其餘的 follower 會先將各自的 log 文件高於 HW 的部分截掉,然後從新的 leader 同步數據。

        注意:這隻能保證副本之間的數據一致性,並不能保證數據不丟失或者不重複。

3、Exactly Once 語義

      將服務器的 ACK 級別設置爲-1,可以保證 Producer 到 Server 之間不會丟失數據,即 At Least Once 語義。相對的,將服務器 ACK 級別設置爲 0,可以保證生產者每條消息只會被 發送一次,即 At Most Once 語義。

      At Least Once 可以保證數據不丟失,但是不能保證數據不重複;相對的,At Least Once 可以保證數據不重複,但是不能保證數據不丟失。但是,對於一些非常重要的信息,比如說 交易數據,下游數據消費者要求數據既不重複也不丟失,即 Exactly Once 語義。在 0.11 版 本以前的 Kafka,對此是無能爲力的,只能保證數據不丟失,再在下游消費者對數據做全局 去重。對於多個下游應用的情況,每個都需要單獨做全局去重,這就對性能造成了很大影響。

      0.11 版本的 Kafka,引入了一項重大特性:冪等性。所謂的冪等性就是指 Producer 不論 向 Server 發送多少次重複數據,Server 端都只會持久化一條。冪等性結合 At Least Once 語 義,就構成了 Kafka 的 Exactly Once 語義。即:

      At Least Once + 冪等性 = Exactly Once

       要啓用冪等性,只需要將 Producer 的參數中 enable.idompotence 設置爲 true 即可。Kafka 的冪等性實現其實就是將原來下游需要做的去重放在了數據上游。開啓冪等性的 Producer 在 初始化的時候會被分配一個 PID,發往同一 Partition 的消息會附帶 Sequence Number。而 Broker 端會對做緩存,當具有相同主鍵的消息提交時,Broker 只 會持久化一條。 但是 PID 重啓就會變化,同時不同的 Partition 也具有不同主鍵,所以冪等性無法保證跨 分區跨會話的 Exactly Once。

 

4、Producer API 

1、消息發送流程

       Kafka 的 Producer 發送消息採用的是異步發送的方式。在消息發送的過程中,涉及到了 兩個線程——main 線程和 Sender 線程,以及一個線程共享變量——RecordAccumulator。 main 線程將消息發送給 RecordAccumulator,Sender 線程不斷從 RecordAccumulator 中拉取 消息發送到 Kafka broker。

相關參數:

 batch.size:只有數據積累到 batch.size 之後,sender 纔會發送數據。

 linger.ms:如果數據遲遲未達到 batch.size,sender 等待 linger.time 之後就會發送數據。

2、異步發送API

1)不帶回調函數的API

package com.bonc.qyl.producer;
/*
*   不帶回調函數的kafka API
* */

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class ProducerTest {
    public static void main(String[] args) {
        Properties props = new Properties();
        //kafka集羣,broker-list
        props.put("bootstrap.servers","qyl01:9092");
        props.put("acks","all");
        props.put("retries",1);  //重試次數
        props.put("batch.size",16384); //批次大小
        props.put("linger.mx",1);  //等待時間
        props.put("buffer.memory", 33554432); ///RecordAccumulator 緩衝區大小

        props.put("key.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        KafkaProducer<String, String> producer = new KafkaProducer<String,String>(props);

        for(int i = 0 ;i < 100 ; i++){
            producer.send(new ProducerRecord<String, String>("first",
                    Integer.toString(i),Integer.toString(i)));
        }
        producer.close();
    }
}

2) 帶回調函數的 API

     回調函數會在 producer 收到 ack 時調用,爲異步調用,該方法有兩個參數,分別是 RecordMetadata 和 Exception,如果 Exception 爲 null,說明消息發送成功,如果 Exception 不爲 null,說明消息發送失敗。

          注意:消息發送失敗會自動重試,不需要我們在回調函數中手動重試。

package com.bonc.qyl.producer;

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.concurrent.ExecutionException;
import java.util.Properties;


public class ProducerComeAPI {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "qyl01:9092");//kafka 集羣,broker-list
        props.put("acks", "all");
        props.put("retries", 1);//重試次數
        props.put("batch.size", 16384);//批次大小
        props.put("linger.ms", 1);//等待時間
        props.put("buffer.memory", 33554432);//RecordAccumulator 緩衝區大小
        props.put("key.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);
        for(int i = 0; i < 100; i++){
            producer.send(new ProducerRecord<String, String>(
                    "first", Integer.toString(i), Integer.toString(i)
            ), new Callback() {

                //回調函數,該方法會在producer收到ack時調用,爲異步調用
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                  if(e == null){
                      System.out.println("successs ->" + recordMetadata.offset());
                  }else{
                      e.printStackTrace();
                  }
                }
            });
        }
        producer.close();
    }
}

  3、 同步發送 API

            同步發送的意思就是,一條消息發送之後,會阻塞當前線程,直至返回 ack。

            由於 send 方法返回的是一個 Future 對象,根據 Futrue 對象的特點,我們也可以實現同 步發送的效果,只需在調用 Future 對象的 get 方發即可。

package com.bonc.qyl.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class CustomProducer {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
 Properties props = new Properties();
 props.put("bootstrap.servers", "hadoop102:9092");//kafka 集
羣,broker-list
 props.put("acks", "all");
 props.put("retries", 1);//重試次數
 props.put("batch.size", 16384);//批次大小
 props.put("linger.ms", 1);//等待時間
 props.put("buffer.memory", 33554432);//RecordAccumulator 緩
衝區大小
 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);
 for (int i = 0; i < 100; i++) {
 producer.send(new ProducerRecord<String, String>("first",
Integer.toString(i), Integer.toString(i))).get();
 }
 producer.close();
 }
}

4、依賴

        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>0.11.0.0</version>
        </dependency>

 

發佈了93 篇原創文章 · 獲贊 107 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章