【知識梳理】《Kafka權威指南》知識梳理

第1章  初識Kafka

消息:Kafka數據單元

批次:一組消息

主題:Kafka通過主題進行分類,由於一個主題包含幾個分區,因此無法保證整個主題範圍內消息的順序,但可以保證消息在單個分區內的順序

生產者、消費者:消費者把每個分區最後讀取的消息偏移量保存在Zookeeper或kafka上,如果消費者關閉或重啓,讀取狀態不會丟失

消費者羣組:會有一個或多個消費者共同讀取一個主題,羣組保證每個分區只能被一個消費者使用

broker:一個獨立的Kafka服務器,單個broker可以輕鬆處理數千個分區以及每秒百萬級的消息量

partition:物理概念,每個topic包含一個或多個partition。每個partition爲一個目錄,partition命名規則爲topic名稱+有序序號

 

分區的複製:

Kafka broker默認的消息保留策略:(1)保留一段時間(2)保留到消息達到一定大小字節數

 

多集羣的使用範圍:

  • 數據類型分離
  • 安全需求隔離
  • 多數據中心

多集羣數據複製:MirrorMaker

 

Kafka使用場景:

  • 活動跟蹤
  • 傳遞消息
  • 度量指標和日誌記錄
  • 提交日誌

第2章  安裝Kafka

 

安裝Zookeeper:Zookeeper保存broker、主題、分區的元數據;Kafka最好使用自己的Zookeeper羣組

安裝Kafka Broker

常規配置:

  • broker.id:每個broker的唯一標示符
  • port:單個事件處理監聽9092端口
  • zookeeper.connect: 保存broker元數據的Zookeeper地址
  • log.dirs:日誌片段,會在擁有最少分區的路徑新增分區
  • num.recovery.threads.per.data.dir
  • auto.create.topics.enable:自動創建主題

 

主題配置:

  • num.partitions:新創建的主題包含多少個分區(默認爲1,可增加分區個數,但不能減少分區個數,分區的大小限制在25G以內)
  • log.retention.ms:根據時間來決定數據可以被保留多久(默認1周)
  • log.retention.bytes:通過保留的消息字節數來判斷消息是否過期
  • log.segments.bytes:當日志片段大小達到log.segment.bytes指定上限時,日誌片段會被關閉,一個新的日誌片段會打開
  • log.segments.ms:多長時間日誌片段會被關閉
  • message.max.bytes:限制單個消息的大小

 

跨集羣負載均衡:

 

如果要把一個broker加入到集羣中,只需要修改兩個配置參數

(1)所有的broker都必須配置相同的zookeeper.connect

(2)每個broker的broker.id唯一

 

操作系統調優:

虛擬內存:

  • vm.swappiness:避免內存交換,該值儘量小一點(建議爲1)
  • vm.dirty.backgroundratio
  • vm.dirty_ratio:刷新到磁盤之前的髒頁數量(大於20)

磁盤

網絡:

  • socket緩衝區:net.core.wmem_default、net.core.rmem_default
  • TCP_Socket讀寫緩衝區:net.ipv4.tcp_wmem、net.ipv4.tcp_rmem
  • 其他:net.ipv4.tcp_window_scaling(時間窗擴展,提升客戶端傳輸數據的效率)、net.ipv4.tcp_max_syn_backlog(併發連接)、net.core.netdev_max_backlog(網絡流量爆發,允許更多數據包排隊等待內核處理)

第3章  Kafka生產者---向Kafka寫入數據

Kafka發送消息步驟:

ProducerRecord包含目標主題和要發送的內容,還可以指定鍵或分區。在發送ProducerRecord對象時,生產者要先把鍵和值對象序列化成字節數組,才能在網絡傳播

記錄會被添加到批次中,這個批次會發送到相同的主題和分區上,獨立線程負責將記錄批次發送到broker上

如果消息成功寫入Kafka,就返回一個RecordMetaData對象,包含了主題和分區信息;如果寫入失敗,則返回一個錯誤。

 

生產者設置的屬性:

  • bootstrap.servers:broker地址清單
  • key.serializer:鍵對象序列化成字節數組(ByteArraySerializer、StringSerializer、IntegerSerializer)
  • value.serializer:值對象序列化成字節數組

 

發送消息到Kafka

發送並忘記

ProducerRecord<String,String> record = new ProducerRecord<>("CustomerCountry","Precision Products","France");
try{
    producer.send(record);//消息先放入到緩衝區,單獨線程發送到服務器端
}catch (Exception e){
    e.printStackTrace();
}

 

同步發送消息

producer.send(record).get();

異步發送消息

producer.send(record,new DemoProducerCallback());
private class DemoProducerCallback implements Callback{
    @Override
   public void onCompletion(RecordMetadata recordMetadata,Exception e){
      if(e!=null) e.printStackTrace();
   }
}

 

生產者配置:

  • acks:

    acks=0:不等待任何服務器的響應,會丟失信息

    acks=1:只要集羣的首領節點收到消息,生產者就會收到一個來自服務器的成功響應(如果一個沒有收到消息的節點成爲新首領,消息還會丟失)

    acks=2:只有當所有參與複製的節點全部收到消息時,纔會成功

  • buffer.memory:生產者內存緩衝區的大小
  • compression.type(snappy、gzip、lz4)
  • retries:重發消息的次數,如果達到次數,則生產者會放棄重試並返回錯誤
  • batch.size:當有多個消息需要發送到同一個分區時,生產者會把它們放在同一個批次中
  • linger.ms:生產者發送批次前等待更多消息加入的時間
  • client.id:識別消息的來源
  • max.in.flight.requests.per.connection:生產者在收到服務器響應前可以發送多少個消息
  • timeout.ms、request.timeout.ms、metadata.fetch.timeout.ms
  • max.block.ms:最大阻塞時間
  • max.request.size:生產者發送請求的大小
  • receive.buffer.bytes、send.buffer.bytes:TCP socket接收和發送數據包的緩衝區大小

 

自定義序列化器

分區:

鍵的用途:(1)可以作爲消息的附加信息 (2)決定消息被寫入到主題的哪個分區

  • 如果鍵值爲null,且使用了默認的分區器,分區器會輪詢將消息分佈到各個分區上
  • 如果鍵值不爲空,且使用了默認的分區器,鍵值通過散列映射

一旦主題增加了新的分區,新來的記錄的映射可能會發生改變

 

自定義分區:

public class DemoPartitioner implements Partitioner{
   public void configure(Map<String,?> configs){}
     public int partition(String topic,Object key,byte[] keyBytes,Object value,byte[] valueBytes,Cluster cluster){
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
         int numPartitions = partitions.size();
         
          if((keyBytes==null)||(!(key instanceof String)))  throw new InvalidRecordException("...");
           if(((String)key).equals("Banana"))  return numPartitions;
             
            return (Math.abs(Utils.murmur2(keyBytes))%(numPartitions - 1));
    } 
}

第4章  Kafka消費者---從Kafka讀取數據

 

消費者羣組:一個羣組裏的消費者訂閱同一個主題,每個消費者接收主題一部分分區的信息(可伸縮讀取能力和處理能力)

不要讓消費者的數量超過主題分區的數量,多餘的消費者會被閒置

當一個消費者被關閉或發生崩潰時,就會離開羣組,原本它讀取的分區將由羣組裏的其他消費者負責

在主題發生變化時,會發生分區重分配

 

再均衡:分區所有權從一個消費者轉移到另一個消費者

再均衡期間,消費者無法讀取消息,造成整個羣組一小段時間不可用

當分區被重新分配給另一個消費者,消費者當前的讀取狀態會丟失。它有可能還需要去刷新緩存

 

羣組協調器:消費者通過向羣組協調器的broker發送心跳,來維持它們和羣組的從屬關係以及分區所屬權關係

           如果消費者停止發送心跳時間足夠長,會話會過期。羣組協調器會認爲其死亡,發生再均衡(如果消費者發生崩潰,協調器會等待幾秒鐘)

 

分配分區的過程:

    當消費者要加入羣組時,會向羣組協調器發送一個JoinGroup請求。第一個加入羣組的消費者將成爲“羣主”。羣主從協調器獲取羣組成員列表,並負責給每一個消費者分配分區

 

創建消費者:

Properties props = new Properties();
props.put("bootstrap.servers","broker1:9092,broker2:9092");
props.put("group.id","CountryCounter");//group.id 指定了消費者屬於哪個消費者羣主
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<String,String>(props);

 

訂閱主題:

consumer.subscribe(Collections.singletonList("customerCountries"));
consumer.subscribe("test.*");//正則表達式
輪詢:
try{
    while(true){
      ConsumerRecords<String,String> records = consumer.poll(100);//輪詢
      for(ConsumerRecords<String,String> record : records){
         //record.topic(),record.partition(),record.offset(),record.key(),record.value()
          
         int updatedCount = 1;
           if(custCountryMap.countainsValue(record.value()))  updatedCount = custCountryMap.get(record.value()) + 1;
           custCountryMap.put(record.value(),updatedCount);
          
           JSONObject json = new JSONObject(custCountryMap);
       }
    }
}finally{
    consumer.close();
}

 

消費者的配置:

  • fetch.min.bytes: 消費者從服務器獲取記錄的最小字節數
  • fetch.max.wait.ms: 指定broker的等待時間
  • max.partition.fetch.bytes: 指定了每個分區裏返回給消費者最大的字節數
  • session.timout.mx: 指定了消費者可以多久不發送心跳(heartbeat.interval.ms 指定了poll方法向協調器發送心跳的頻率,heartbeat.interval.ms必須比session.timeout.ms小)
  • auto.offset.reset: (latest / earliest)沒有偏移量或偏移量無效的情況下,對記錄讀取的處理
  • enable.auto.commit: 是否自動提交偏移量
  • partition.assignment.strategy:分區的分配策略

(Range:把主題的連續分區分配給消費者,會導致不均衡  RoundRobin:把主題逐個分配給消費者,會很均衡。所有消費者分配相同數量的分區)

  • client.id:標識客戶端
  • max.poll.records:控制單子調用call返回的記錄數量
  • receive.buffer.bytes 和 send.buffer.bytes :TCP緩衝區大小(-1表示使用系統默認值)

 

偏移量:針對發生崩潰,或再均衡之後處理數據使用

         消費者會向_consumer_offset的特殊主題發送包含偏移量的消息

 

出現的問題:

  • 重複處理:

  • 缺失:

 

  • 阻塞提交偏移量:consumer.commitSync();//阻塞提交偏移量
  • 異步提交偏移量:consumer.commitAsync();

 

回調經常被用於記錄提交錯誤或生成度量指標

consumer.commitAsync(new OffsetCommitCallback(){
   public void onComplete(Map<TopicParition,OffsetAndMetadata> offsets,Exception e){
      ......
   }
});

 

同步/異步組合提交:普通提交可以使用異步,但是消費者關閉前一定要用同步保證成功

try{
 consumer.commitAsync();
}finally{ 
 consumer.commitSync();
 consumer.close();
}

 

提交特定偏移量:

Map<TopicPartition,OffsetAndMetadata> currentOffsets = new HashMap();
currentOffsets.put(new TopicPartition(record.topic(),record.partition(),new OffsetAndMetadata(record.offset()+1,"no metadata")));
consumer.commitAsync(currentOffets,null);

 

ConsumerRebalanceListener:再均衡監聽器

  • onPartitionsRevoked
  • onPartitionsAssigned

 

從特定偏移量開始處理記錄:

  • seekToBegining
  • seek
  • seekToEnd

 

消費者退出:

consumer.wakeup();會拋出WakeupException異常,不需要處理WakeupException,但退出之後要徹底關閉消費者

 

反序列化器:

p68

不建議使用自定義序列化器和自定義反序列化器,它們把生產者和消費者緊緊地耦合在一起,並且很脆弱,容易出錯


kafka使用zookeeper來維護羣成員的信息,在broker啓動的時候,通過創建臨時節點把自己ID註冊到zk上,kafka組件訂閱zookeeper的/brokers/ids路徑,當有broker加入集羣或退出集羣時,這些組件就會獲得通知

broker斷開會從zk節點上移除,監聽broker列表的kafka組件會被告知

在完全關閉一個broker之後,使用相同的ID啓動一個全新的broker,會立即加入集羣,並擁有與舊的broker相同的分區和主題

 

控制器:

    第一個啓動的broker在zk上創建/controller讓自己成爲控制器,其他broker在控制器上創建zookeeper watch對象,接收節點變更通知(zk負責選舉控制器)

    除了一般broker的功能外,還負責分區首領的選舉(控制器負責選擇分區首領)

   

   zookeeper epoch:每個新選出的控制器通過Zookeeper的條件遞增操作獲得一個全新的、數值更大的 zk epoch,epoch可以避免“腦裂”(兩個節點同時認爲自己是控制器)

 

複製:核心

    kafka使用主題來組織數據

    每個主題被分爲若干個分區(分區是基本存儲單元)

   每個分區有多個副本。這些副本保存在broker上,每個broker可以保存成百上千個屬於不同主題和分區的副本

首領副本:每個分區都有一個首領副本

跟隨者副本:從首領副本處複製消息,如果一個副本無法與首領保持一致,在首領發生失效時,它不可能成爲新首領

 

處理請求:

    broker大部分工作是處理:客戶端、分區副本、控制器發送給分區首領的請求

   

請求消息標準消息頭:

  • Request type
  • Request version
  • Correlation ID
  • Client ID

broker會在它所監聽的每一個端口運行一個Acceptor線程,該線程會創建一個連接,並交給Processor線程處理。

請求類型:1、生產請求(寫入) 2、獲取請求(讀取) 3、元數據請求

生產請求和獲取請求都必須發送給分區的首領副本上,元數據請求可以是任意一個broker

一般情況下,客戶端會把這些信息緩存起來,並直接往目標broker發送生產請求和獲取請求。但要不時發送元數據請求刷新信息

kafka使用零複製技術向客戶端發送消息(不需要經過任何中間緩衝區)

 

等所有同步副本複製了消息之後,才允許對消費者可見

    在數據訪問方面所有限制,因爲區域中心之間的數據完全獨立    

分區的分配:

首先通過輪詢的方式,給每個分區確定首領分區

爲分區選好broker之後,要在對應的broker下分配目錄。新的分區總是添加到數量最小的那個目錄中。

 

文件管理:

將分區劃分成片,方便在大文件中查找或刪除消息

主題設置了保留期限,規定數據被刪除之前保留多長時間

 

文件格式:

Kafka的消息和偏移量保存在文件中,保存在磁盤上的數據格式與從生產者發送過來或發送給消費者的消息格式一致。

kafka使用零複製技術給消費者發送消息,同時避免對消費者已經壓縮的消息解壓或再壓縮。

分區的分配:

首先通過輪詢的方式,給每個分區確定首領分區

爲分區選好broker之後,要在對應的broker下分配目錄。新的分區總是添加到數量最小的那個目錄中。

 

DumLogSegment 校驗壓縮內容

bin/kafka-run-class.sh  kafka.tools.DumpLogSegments

 

索引:

    Kafka爲每個分區維護一個索引,若索引損壞會重新生成。


第6章  可靠的數據傳遞

Kafka可靠性保證機制:

  • kafka可以保證分區消息的順序
  • 只有當消息被寫入分區的所有同步副本時,才被認爲是“已提交”的
  • 只要有一個副本活躍,已提交的消息就不會丟失
  • 消費者只能讀到已經提交的消息

消費者只能讀到已經提交的消息

kafka的複製機制和分區的多副本架構是kafka可靠保證的核心

 

分區副本時同步副本,跟隨者副本同步需要滿足以下條件:

  • 與zk之間有活躍會話
  • 規定時間內從首領處獲取過消息

    不滿足上述條件的副本會被認爲是不同步的。不同步副本可以恢復成同步副本。如果一個副本或多個副本在同步和非同步狀態之間快速切換,說明集羣內部有問題。通常是Java不恰當垃圾回收配置導致。

 

broker可靠性關鍵配置:

  • replication.factor: 主題的副本系數
  • unclean.leader.election: 不完全首領選舉   true:允許不同步的副本成爲首領,存在丟失數據風險  false:等待原先的首領重新上線,降低可用性
  • min.insync.replicas:最少有多少同步副本,才能寫入數據

 

消費者可靠性配置:

  • group.id
  • auto.offset.reset:規定沒有偏移量時做什麼,怎麼做(earlist、latest)
  • enable.auto.commit:讓消費者基於任務調度自動提交偏移量
  • auto.commit.interval.ms:自動提交的頻率

kafka現在不支持“僅僅一次”的語義,最常用的方法是寫入到支持唯一鍵的系統中(保持冪等性)


第7章  構建數據管道

 

構建數據通道的場景:

kafka作爲兩個通道的端點(s3->kafka、kafka->mongoDB)

kafka作爲兩個端點的中間媒介(Flume->kafka->Spark Streaming)

 

使用kafka價值:可以作爲數據管道各個數據段之間的大型緩衝區,有效地解耦數據管道數據的生產者和消費者

需要考慮的問題:(1)及時性(2)可靠性(3)高吞吐量(4)數據格式 (5)轉換

轉換:

  • ETL: 提取--轉換--加載
  • ELT: 提取--加載--轉換(只做少量轉換)(高保真數據管道 / 數據湖架構)

 

Connect API和客戶端API:

如果要將Kafka連接到數據存儲系統,可以使用Connect

如果要連接的數據存儲系統沒有相應的連接器,可以考慮客戶端API或Connect API開發應用程序

 

Connet:Connect是Kafka的一部分,爲在Kafka和外部數據存儲系統之間移動數據提供了一種可靠可伸縮的方式。

          Connect以worker進程集羣的方式運行,我們基於worker進程安裝連接器插件,然後使用REST API來管理和配置connector。worker進程是長時間持續運行的作業。

          Connect把數據提供給worker,數據池的連接器負責從worker進程獲取數據,並寫入目標系統

運行Connect:

  • 分佈模式:bin/connect-distributed.sh config/connect-distributed.properties
  • 單機模式:bin/connect-standalone.sh  config/connect-distributed.properties

  

Connect重要參數:

bootstrap.servers

group.id

 

檢查REST API是否正常: curl  http://localhost:8083/

檢查已經安裝好的連接器插件: curl  http://localhost:8083/connector-plugins

 

連接器負責的內容:

  • 決定需要運行多少個任務
  • 按照任務來拆分數據複製
  • 從worker進程獲取任務配置並將其傳遞下去

任務:

負責將數據移入或移出Kafka

worker進程

轉化器和Connect數據模型

偏移量管理


第8章  跨集羣數據鏡像

集羣間的複製叫做鏡像

Hub 和 Spoke架構:

    在數據訪問方面所有限制,因爲區域中心之間的數據完全獨立

雙活架構:

    它可以爲就近的用戶提供服務,具有性能上的優勢,而且不會因爲數據的可用性問題,在功能上作出犧牲。

   因爲每個數據中心具備完整的功能,一旦一個數據中心發生失效,就可以把用戶重定向到另一個數據中心。

   在這種架構中,在kafka頭部信息可以包含源數據中心的信息,避免信息循環鏡像。

主備架構:

MirrorMaker爲每一個消費者分配一個線程,消費者從原集羣的主題和分區上讀取數據,然後通過公共生產者將數據發送到目標集羣上。

消費者每60s通知生產者發送所有數據到Kafka,並等待Kakfa的確認。然後消費者再通知原集羣提交這些事件對應的偏移量

bin/kafka-mirror-maker  --consumer.config  xxx  --producer.config xxx --new.consumer --num.streams=2(流的數量) --whitelist ".*"(鏡像的主題)

MirrorMaker的基本命令行參數:

  • consumer.config:指定消費者的配置文件(auto.commit.enable一般爲false、auto.offset.reset一般爲latest)
  • producer.config
  • new.consumer
  • num.streams

whitelist:鏡像的主題名字(正則表達式)


第9章  管理Kafka

主題操作:

  • 創建、修改、刪除和查看集羣的主題:kafka-topics.sh
  • 創建主題:kafka-topics.sh  --zookeeper  xxx  --create  --topic(主題名稱)  --replication-factor(複製係數)  --partitions(分區數量)  --disable-rack-aware (不需要機架分配策略)  --if-not-exeists (忽略重複創建主題錯誤)
  • 刪除主題:

kafka-topics.sh  --zookeeper  xxx  --delete  --topic  xxxx (delete.topic.enable 必須設置爲true)

  • 列出主題:kafka-topics.sh  --zookeeper  xxx  --list
  • 列出主題詳細信息:

kakfa-topics.sh --zookeeper  xxx --describe  --under-replicated-partitions(列出所有包含不同步副本的分區)  --unavailable-partitions(列出所有沒有首領的分區) --topics-with-overrides(包含覆蓋配置的主題)

分區操作:

增加分區:kafka-topics.sh  --zookeeper  xxx  --alter  --topic xxx  --partitions nums

無法刪除分區。建議一開始就設置好分區,如果刪除分區,只能刪除主題後重建

 

消費者羣組:

    對於舊版本,信息保存在zookeeper上;對於新版本,信息保存在broker上

列出舊版本消費者羣組:kafka-consumer-groups.sh --zookeeper  xxx  --list

列出新版本消費者羣組:kafka-consumer-groups.sh --new-consumer --bootstrap-server xxxx --list

指定羣組:kafka-consumer-groups.sh --zookeeper  xxx --describe --group xxx

 

刪除羣組:

    只有舊版本消費者客戶端才支持刪除羣組的操作,在執行操作之前,必須關閉所有的消費者。

刪除羣組:kafka-consumer-groups.sh  --zookeeper  xxx  --delete  --group  xxx

刪除羣組某個topic:kafka-consumer-groups.sh  --zookeeper xxx delete --group xxx --topic xxx

 

偏移量管理:

導出偏移量:kafka-run-class.sh  kafka.tools.ExportZKOffsets --zkconnect  xxxx  --group xxxx --output-file  outputfile

導入偏移量:kafka-run-class.sh  kafka.tools.ImportZKOffets  --zkconnect  xxxx  --input-file  inputfile

 

動態配置管理:

kafka-configs.sh  p143

列出被覆蓋的配置:kafka-configs.sh --zookeeper  xxxx --describe --entity-type topics --entity-name xxx

移除被覆蓋的配置:kafka-configs.sh --zookeeper  xxxx --alter  --entity-type  topics  --entity-name xxxx --delete-config xxx

 

手動觸發分區選舉:kafka-preferred-replica-election.sh

修改分區副本:kafka-reassign-partitions.sh

副本驗證:kafka-replica-verification.sh(集羣副本的一致性):kafka-replica-verification.sh  --broker-list xxx,yyy,zzz  --topic-whrite-list 'ttt'

 

控制檯消費者:kafka-console-consumer.sh

--formatter  CLASSNAME:指定消息格式化器的類名,用於解碼消息

--from-begining

--max-message NUM:在推出前最多讀取NUM個消息

--partition NUM: 指定只讀ID爲NUM的分區

kafka-console-consumer.sh  --zookeeper  xxx --topic __consumer_offsets  --formatter 'kafka.coordinator.GroupMetadataManager$OffsetsMessages' --max-message 1

 

控制檯生產者:kafka-console-producer.sh

--broker-list:指定了一個或多個broker

--topic:指定生成消息的目標日誌,在生成完消息之後,需要發送一個EOF字符來關閉客戶端

kafka-console-producer.sh --broker-list xxx,yyy,zzz  --topic my-topic

 

移除集羣控制器:手動刪除ZooKeeper中的/controller

取消分區重分配:手動刪除/admin/reassign_partitions節點,重新進行控制器選舉

移除待刪除的主題:手動刪除/admin/delete_topic

手動刪除主題:

  1. 先關閉所有broker
  2. 刪除ZooKeeper下的/brokers/topics/TOPICNAME
  3. 刪除每個broker分區目錄
  4. 重啓所有broker

第10章  監控Kafka


第11章  流式處理 

kafka客戶端類庫中提供了強大的流式處理類庫

數據流:無邊界數據集的抽象表示

數據流模型的屬性:(1)有序(2)不可變的數據記錄(3)事件流可重播

流式處理中的“時間”:

  1. 事件時間:所追蹤事件發生時間和記錄的創建時間
  2. 日誌追加時間:指事件保存到broker的時間
  3. 處理時間:應用程序在收到事件之後要對其進行處理的時間

流式一系列事件,每個時間就是一個變更

爲了將流轉化成表,需要“應用”流裏面所包含的所有變更,也就是流的“物化”

 

流式處理的設計模式:

  • 單個事件處理

  • 使用本地狀態

  • 多階段處理和重分區

  • 使用外部查找

  • 流和流的對接

uploading.gif

Kafka有兩個基於流的PI,一個是底層的Processor API,一個是高級的Sstreams DSL

Streams DSL:在使用DSL API時,一般會先用StreamBuilder創建一個拓撲,拓撲是一個有向圖(DAG),包含了各個轉換過程,將會被應用到流的事件上。在創建好拓撲後,使用拓撲創建一個KafkaStreams執行對象。多個線程會隨着KafkaStreams對象啓動,將拓撲應用到流的事件上。

 

//簡單聚合Demo:生產者和消費者內嵌到Kafka Streams引擎裏

public class WordCountExample{
    public static void main(String[] args) throws Exception{
         Properties props = new Properties();
         props.put(StreamsConfig.APPLICATION_ID_CONFIG,"wordcount");
         props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
         props.put(StreamsConfig.KEY_SERDE_CLASS_CONFIG,Serdes.String().getClass().getName());
         props.put(StreamsConfig.VALUE_SERDE_CLASS_CONFIG,Serde.String().getClass().getName());
    }
        KStreamBuilder builder = new KStreamBuilder();
        KStream<String,String> source = builder.stream("wordcount-input");
        final Pattern pattern = Pattern.complie("\\W+");
        KStream counts = source.flatMapValues(value->Arrays.asList(pattern.split(value.toLowerCase()))).map(key,value) -> new KeyValue<Object,Object>(value,value)).filter((key,value)->(!value.equals("the")))).groupByKey().count("CountStore").mapValues(value->Long.toString(value)).toStream();
        counts.to("wordcount-output");
}

//基於時間窗口聚合Demo

KStream<TickerWindow,TradeStats> stats = source.groupByKey().aggregate(TradeStates::new,
(k,v,tradestats -> tradestats.add(v),TimeWindows.of(5000).advanceBy(1000), new TradeStatsSerde(),"trade-stats-store").toStream((key,value) -> new TickerWindow(key.key(),key.window().start()).mapValues((trade) -> trade.computeAvgPrice());

stats.to(new TickerWindowSerde(),new TradeStats(),"stockstatoutput");

 

//點擊流Demo

KStream<Integer,PageView> views = builder.stream(Serdes.Integer(),new PageViewSerde(),Constants,PAGE_VIEW_TOPIC);
KStream<Integer,Search> searches = builder.stream(Serdes.Integer(),new SearchSerde(),Constants.SEARCH_TOPIC);
KStream<Integer,UserProfile> profiles = builder.stream(Serdes.Integer90,new Pro.fileSerde(),Constants.USER_PROFILE_TOPIC,"profile-store");
KStream<Integer,UserActivity> viewsWithProfile = views.leftJoin(profiles,(page,profile) -> new UserActivity(profile.getUserID(),profile.getUserName(),profile.getZipcode(),profile.getInterests(),"",page.getPage());
KStream<Integer,UserActivity> userActivityKStream = viewsWithProfile.leftJoin(searches,(userActivity,search)->userActivity.updateSearch(search.getSearchTerms()),JoinWindows.of(1000),Serdes,Integer(),new UserActivitySerde(),new SearchSerde())
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章