Kafka的介紹和使用

 

一:介紹

參考:kafka

kafka是分佈式消息系統,以從“一個高吞吐量,分佈式的消息系統”改爲“一個分佈式流平臺”。

kafka與傳統消息系統不同在於:

  • kafka是一個分佈式系統,易於向外擴展;

  • 它同時爲發佈和訂閱提供高吞吐量;

  • 它支持多訂閱者,當失敗時能自動平衡消費者;

  • 消息的持久化;

 

二:入門實例

producer:

代碼塊

Java

import java.util.Properties;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
public class UserKafkaProducer extends Thread{
  private final KafkaProducer<Integer,String> producer;
  private final String topic;
  private final Properties props = new Properties();
  public UserKafkaProducer(String topic){
    props.put("metadata.broker.list","localhost:9092");
    props.put("bootstrap.servers","master2:6667");
    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");
    procuder = new KafkaProducer<Integer,String>(props);
    this.topic = topic;
  }

 
  @Override
  public void run(){
    int messageNo=1;
    while(true){
      String messageStr = new String("Message_"+messageNo);
      System.out.println("Send:"+messageStr);
      //返回的是Future<RecordMetadata>,異步發送
      producer.send(new ProducerRecord<Integer,String>(topic,messageStr));
      messageNo++;
      try{
        sleep(3000);
      }catch(InterruptedException e){
        e.printStackTrace();
      }
    }
  }
}

消費者consumer:

代碼塊

Java

Properties props = new Properties();
//定義kafka服務的地址,不需要將所有broker指定上
props.put("bootstrap.servers","localhost:9092");
//指定consumer group
props.put("group.id","test");
//是否自動確認offset
props.put("enable.auto.commit","true");
//自動確認offset的時間間隔
props.put("auto.commit.interval.ms","1000");
props.put("session.timeout.ms","30000");
//key的序列化類
props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
//value的序列化類
props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
//定義consumer
KafkaConsumer<String,String> consumer = new KafkaConsumer<>(props);
//消費者訂閱的topic,可同時訂閱多個
consumer.subscribe(Arrays.asList("foo","bar"));
//讀取數據,讀取超時時間爲100ms
while(true){
  ConsumerRecords<String,String> records = consumer.poll(100);
  for(ConsumerRecord<String,String> record:records){
    System.out.println("offset=%d,key=%s,value=%s",record.offset(),record.key(),record.value());
  }
}

三:Kafka架構原理

  • 問題:

1)Kafka的topic和分區內部是如何存儲的,有什麼特點?

2)與傳統的消息系統相比,Kafka的消費模型有什麼優點?

3)Kafka如何實現分佈式的數據存儲與數據讀取?

  • 架構圖:

Producer---push-》kafka broker《-pull---consumer

  • Kafka名詞解釋:

broker:消息中間件處理節點,一個kafka節點就是一個broker,一個或者多個broker可以組成一個kafka集羣;

topic:主題,kafka根據topic對消息進行歸類,發佈到kafka集羣的每條消息都需要指定一個topic;

producer:消息生產者,向broker發送消息的客戶端;

consumer:消息消費者,從broker讀取消息的客戶端;

consumer group:每個consumer屬於一個特定的consumer group,一條消息可以發送到多個不同的consumer group,但是一個consumer group中只能有一個consumer能夠消費該消息;

partition:物理上的概念,一個topic可以分爲多個partition,每個partition內部都是有序的;

  • topic和partition:

在kafka中的每條消息都有一個topic,一般不同類型的數據都可以設置不同的主題。一個主題一般會有多個消息的訂閱者,當生產者發佈消息到某個主題時,訂閱了這個主題的消費者都可以接收到生產者寫入的新消息。

kafka爲每個主題維護了分佈式的分區(partition)日誌文件,每個partition在kafka存儲層面是append log,任何發佈到此partition的消息都會被追加到log文件的尾部,在分區中的每條消息都會按照時間順序分配到一個單調遞增的順序編號,也就是我們的offset,通過offset編號可以確定一條在該partition下的唯一消息。在partition下面保證了有序性,但是topic下面沒有保證有序性。

1)如果沒有key值,則進行輪詢發送;

2)如果有key值,對key值進行hash,然後對分區數量取餘,保證了同一個key值的會被路由到同一個分區,如果想隊列的強順序一致性,可以讓所有消息設置爲同一個key。

  • 消費模型

由於推的模型,發送給消費這後,可能由於消費進程掛掉或者網絡原因沒有收到這條消息,如果標記已發送,則消息會永久消失;如果利用生產者收到消息後回覆方法,需要記錄消息消費狀態,不可取。

kafka採取拉取模型poll,由自己控制消費速度,以及消費進度,消費者可以按照任意的偏移量進行消費。比如消費者可以消費已經消費過的消息今習慣重新處理,或者消費最近的消息等等。

 

四:producer發佈消息

參考學習鏈接:kafka

1)寫入方式

producer採用push模式將消息發佈到broker,每條消息都被append到partition中,屬於順序寫磁盤。

2)消息路由

producer發送消息到broker時,會根據分區算法選擇將其存儲到哪一個partition。其路由機制爲:

指定了partition,則直接使用;

未指定partition但指定key,通過key的value進行hash選擇一個partition;

partition和key都未指定,使用輪詢選擇一個partition;

3)寫入流程

1⃣️producer先從zookeeper的/brokers/.../state節點找到該partition的leader;

2⃣️producer將消息發送給該leader;

3⃣️leader將消息寫入本地log;

4⃣️followers從leader pull消息,寫入本地log後leader發送ACK;

5⃣️leader收到所有ISR中的replica的ACK後,增加HW(high watermark,最後commit的offset),並向producer發送ACK;

4)producer delivery guarantee

參考三種模式代碼:https://blog.csdn.net/laojiaqi/article/details/79034798

1⃣️At most once消息可能會丟,但絕不會重複傳輸;

2⃣️At least one消息絕不會丟,但可能會重複傳輸;

3⃣️Exactly once每條消息肯定會被傳輸依次且僅傳輸依次;

當producer向broker發送消息時,一旦這條消息被commit,由於replication的存在,它就不會丟。但是如果producer發送數據給broker後,遇到網絡問題而造成通信中斷,那producer就無法判斷該消息是否被commit;雖然kafka無法確定網絡故障期間發生了什麼,但是producer可以生成一種類似於主鍵的東西,發生故障時冪等性的重試多次,這樣就做到了Exactly once,但是目前沒有實現。所以目前默認一條消息從producer到broker是確保了At least once,可以通過設置producer異步發送實現At most once。

五:broker保存消息

1)存儲方式

物理上把topic分成一個或多個partition,每個partition物理上對應一個文件,該文件存儲該partition的所有消息和索引文件;

2)存儲策略

無論消息是否被消費,kafka都會保留所有消息,有兩種策略可以刪除舊數據:

1⃣️基於時間

2⃣️基於大小

需要注意的是,因爲kafka讀取特定消息的時間複雜度爲O(1),即與文件大小無關,所以這裏刪除過期文件與提高kafka性能無關;

3)topic創建與刪除

topic創建:

1⃣️controller在zookeeper的/brokers/topic節點上註冊watcher,當topic被創建,則controller會通過watch得到該topic的partition/replica分配。

2⃣️controller從/brokers/ids讀取當前所有可用的broker列表,對於set_p中的每一個partition:

1)從分配給該partition的所有replica(成爲AR)中任選一個可用的broker作爲新的leader,並將AR設置爲新的ISR;

2)將新的leader和ISR寫入/brokers/topics/[topic]/partitions/[partition]/state;

3⃣️controller通過RPC向相關的broker發送LeaderAndISRRequest。

 

刪除topic:

1⃣️controller在zookeeper的/brokers/topics節點上註冊watcher,當topic被刪除,則controller會通過watch得到該topic的partition/replica分配。

2⃣️若delete.topic.enable=false,結束;否則controller註冊在/admin/delete_topics上的watch被fire,controller通過回調向對應的broker發送StopReplicaRequest。

六:consumer消費消息

1:kafka提供兩套consumer API:

1)The high-level Consumer API;提供從kafka消費數據的高層抽象;

2)The SimpleConsumer API;需要開發人員更多的關注細節;

1.1:The high-level Consumer API提供了consumer group的語義,一個消息只能被group內的一個consumer所消費,且consumer消費消息時不關注offset,最後一個offset由zookeeper保存。

注意:

1)如果消費線程大於partition數量,則有些線程將收不到消息;

2)如果partition數量大於線程數,則有些線程多收到多個partition消息;

3)如果一個線程消費多個partition,則無法保證收到消息的順序,而一個partition內的消息是有序的。

1.2:The SimpleConsumer API可以是開發人員有更多partition的控制權。

1)多次讀取一個消息;

2)只消費一個partition中的部分消息;

3)使用事務來保證一個消息僅被消費一次;

注意:使用此API,partition/offset/broker/leader對你不再透明,需要自己管理,需做大量的額外工作:

1)必須在應用程序中跟蹤offset,從而確定下一條應該消費哪條消息;

2)應用程序需要通過程序獲知每個partition的leader是誰;

3)需要處理leader的變更使用simpleConsumer API的一般流程如下:

1⃣️查好到一個活着的broker,並且找出每個partition的leader;

2⃣️找出每個partition的follower;

3⃣️定義好請求,該請求應該能描述應用程序需要那些數據;

4⃣️fetch數據;

5⃣️識別leader的變化,並對之作出必要的相應;

 

2:consumer group

kafka的分配單位是partition,每個consumer都屬於一個group,一個partition只能被同一個group內的一個consumer所消費(保障了一個消息只能被group內的一個consumer所消費),但是多個group可以同時消費這個partition。即partition和group是一對多的關係。

kafka的設計目標之一就是同時實現離線處理和實時處理,根據這一特性,可以使用spark/Storm這些實時處理系統對消息在線處理,同時使用Hadoop批處理系統進行離線處理,還可以將數據備份到另一個數據中心,只需要保證這三者屬於不同的consumer group。

3:消費方式

consumer採用pull模式從broker中讀取數據。

push模式很難適應消費速率不同的消費者,因爲消息發送速率是由broker決定的。它的目標是儘可能以最快速度傳遞消息,但是這樣很容易造成consumer來不及處理消息,典型的表現就是拒絕服務以及網絡擁塞。而pull模式可以根據consumer的消費能力以適當的速率消費消息。

對於kafka而言,pull模式更合適,可簡化broker的設計,consumer可自主控制消費消息的速率,同時consumer可以自己控制消費方式--即可批量消費也可逐條消費,同時還能選擇不同的提交方式從而實現不同的傳輸語義。

 

4:consumer delivery guarantee

實際使用中應用程序並非在consumer讀取完數據就結束了,而是要進一步處理:

1)讀完消息先commit再處理消息;這種模式,如果consumer在commit後還每來得及處理消息就crash了,下次重新開始工作後就無法讀到剛剛已提交而未處理的消息,對應 at most once;

2)讀完消息先處理再commit;這種模式,如果處理完消息之後commit之前consumer crash了,下次重新開始工作還會處理剛剛未commit的消息,實際上該消息已經處理過了,對應at least once。

3)如果一定要做到exactly once,就需要協調offset和實際操作的輸出。經典做法是引入兩階段提交,如果能讓offset和操作輸入存在同一個地方,會更簡潔和通用,這種方式可能更好,因爲許多輸出系統可能不支持兩階段提交。比如,consumer拿到數據後可能把數據放到HDFS,如果把最新的offset和數據本身一起寫到HDFS,那就可以保證數據的輸出和offset的更新要麼都完成,要麼都不完成,間接實現Exactly once。

(high-level API,offset存於zookeeper中,無法存於HDFS;而simpleConsumer API的offset自己維護,可存於HDFS中)

5:consumer rebalance

當有consumer加入或退出/以及partition的改變時會出發rebalance。consumer rebalance算法如下:

1)將目標topic下的所有partition排序,存於PT;

2)對某consumer group下所有consumer排序,存於CG,第i個consumer記爲Ci;

3)N=size(PT)/size(CG),向上取整;

4)解除Ci對原來分配的partition的消費權(i從0開始);

5)將第i*N到(i+1)*N-1個partition分配給Ci;

 

在0.8.*版本後,爲了保證整個consumer group的一致性,當一個consumer觸發了rebalance時,該group內的其他所有consumer也觸發rebalance,導致以下幾個問題:

1)herd effect:任何broker活着consumer的增減都會觸發所有的consumer的rebalance;

2)split brain:每個consumer分別單獨通過zookeeper判斷哪些broker和consumer當掉了,那麼不同consumer在同一時刻從zookeeper看到的view就可能不一樣,這是由zookeeper的特性決定的,這就會造成不正確的rebalance嘗試;

3)調整結果不可控:所有consumer都並不直到其他consumer的rebalance是否成功,這可能會導致kafka工作在一個不正確的狀態。

上述問題,將在0.9.*版本開始使用中心coordinator來控制consumer rebalance。

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