Flink-Kafka相關學習筆記

Kafka

1.什麼是消息隊列:
  ->簡稱爲MQ(Message Queue),我們把要傳輸的數據放在隊列中 (把數據放到消息隊列叫做生產者,從消息隊列裏取數據叫做消費者)
  ->消息隊列的兩種模式:
    消息隊列包括兩種模式,點對點模式(point to point, queue)和發佈/訂閱模式(publish/subscribe, topic)
    點對點:消息被消費以後,queue中不再有存儲,所以消息接收者不可能消費到已經被消費到消息
    發佈/訂閱模式:發佈者將消息發送到Topic,系統將這些消息傳遞給多個訂閱者
  ->常用消息隊列:
    Kafka -> 在於分佈式架構
    RabbitMQ -> 基於AMQP協議來實現,主從結構,適合事務性業務
    ActiveM ->  
    RocketMQ -> 參考kafka,但是主從結構,適合事務性業務
2.爲什麼要用消息隊列:
  解耦合:多應用間通過消息隊列對統一消息進行處理,避免調用接口失敗導致整個過程失敗;
  異步處理:多應用對消息隊列中同一消息進行處理,應用間併發處理消息,相比串行處理,減少處理時間;
  限流/削峯:避免流量過大導致應用系統掛掉的情況;
  ->應用場景:
    1.用戶爲了使用某個應用,進行註冊,系統需要發送註冊郵件並驗證短信,兩種處理方式:串行/並行(假設三個子步驟均爲50ms)
      串行:50 + 50 + 50
      並行:50 + 50
      ->假設註冊信息成功寫入消息隊列後即返回給客戶端成功註冊,這樣相比於串行和並行就只剩下註冊信息寫入這個步驟的耗時了
    2.用戶使用QQ相冊上傳一張圖片,人臉識別系統會對該圖片進行人臉識別,一般做法是服務器收到圖片後,圖片上傳系統立即調用人臉識別系統,調用完成後再返回成功(
      缺點:
        人臉識別系統被調失敗,導致圖片上傳失敗;
        延遲高,需要人臉識別系統處理完成後,再返回給客戶端,即使用戶並不需要立即知道結果;
        圖片上傳系統與人臉識別系統之間相互調用,需要做耦合;)
      使用消息隊列:
        客戶端上傳圖片,圖片上傳系統將圖片信息等批次寫入消息隊列,直接返回成功;
        人臉識別系統則定時從消息隊列中取數據,完成對新增圖片等識別
    3.購物網站開展秒殺活動,一般由於瞬時訪問量過大,服務器接收過大,會導致流量暴增,相關係統無法處理請求甚至崩潰,加入消息隊列後,系統可以從消息隊列中取數據,相當於消息隊列做了一次緩衝
3.怎麼用
  常見問題:
    服務高可用;
    數據丟失問題;
    消費者怎麼得到消息隊列中的數據:
      生產者將數據放到消息隊列中,消息隊列有數據了,主動叫消費者去拿(俗稱push)
      消費者不斷去輪詢消息隊列,看看有沒有新的數據,如果有就消費(俗稱pull)
4.Kafka:(官方文檔 -> https://kafka.apache.org/documentation/)
  流處理平臺有三個關鍵能力:
    以類似於消息隊列的形式進行發佈和訂閱消息;
    低錯誤率的存儲流數據;
    當記錄到達時開始處理
  -> Kafka可以達到前兩種能力
    在系統和應用之間構建可靠的實時流數據管道;
    構建實時流應用程序以轉換或響應數據流
  什麼是Kafka:
    Kafka以集羣的形式運行在跨多個數據中心的單臺或多臺服務器上;
    Kafka集羣通過主題topic存儲流數據;
    每條記錄包含一個key, 一個value和一個時間戳
    Kafka有五個核心API:
      1.Producer API (官方文檔地址 -> https://kafka.apache.org/documentation.html#producerapi) 發佈流數據到一個或多個Kafka topic的應用;
      2.Consumer API (官方文檔地址 -> https://kafka.apache.org/documentation.html#consumerapi) 訂閱一個或多個topic並對其進行處理的應用;
      3.Stream API (官方文檔 -> https://kafka.apache.org/documentation/streams/)  以流處理應用的身份從一個或多個源topic進行消費併產生輸出流到一個或多個目標topic中;
      4.Connector API (官方文檔 -> https://kafka.apache.org/documentation.html#connect) 構建一個生產者或消費者到Kafka topic的持續連接;
      5.AdminClient API (官方文檔 -> https://kafka.apache.org/documentation/#adminapi) 管理和檢查topics、brokers和其他的Kafka對象
    kafka的通信是通過TCP協議來實現的;
    topic是已經發布的數據的一個主題或概要名稱;(Kafka中的topic總是多者訂閱的);
    對於每個topic,Kafka集羣維護一個partition log:
      每個partition都是有序的切固定不變的記錄序列,並且可以持續添加的結構化提交日誌;
      partition中的記錄都是通過offset進行唯一標記的;
      消費者控制自己的offset:通常,一個消費者會線性的按照offset讀取數據,但事實上,消費者可以以任何順序進行消費,例如消費者可以消費老的數據來重新處理過去的數據,或者跳過最近的一些數據,然後從"現在"開始消費
    生產者:...
    消費者:
      如果所有的消費者都在同一個消費組內,則數據會被均衡的發給消費者;
      如果所有的消費者都不在同一個消費族內,則記錄會被廣播給所有的消費者進程;
  爲什麼是Kafka:
    Kafka Architecture:
      Producer、Consumer、Stream、Connector
      brokers -> zookeeper
      topic -> brokers    partitions -> topic
    Kafka設計思想:
      動機:Kafka被設計爲一個用來處理企業級實時數據的一體化平臺
        高吞吐
        妥善處理數據積壓
        低延遲
      持久化:(文件系統帶來的問題)
        Kafka對消息的存儲和緩存嚴重依賴於文件系統(磁盤速度是瓶頸):
          6個7200rpm、SATA接口、RAID-5的磁盤陣列在JBOD配置下的順序寫入性能約爲600MB/秒;
          隨機寫入的性能僅約100k/秒;
          ->參考https://queue.acm.org/detail.cfm?id=1563874
      ...

    ...
  Kafka的使用:
    ->kafka快速入門實際操作(基於獨立的zookeeper集羣環境下,多機器 現在最新版本的kafka 執行命令中用--bootstrap-server代替了--zookeeper是什麼意思):
    ->啓動zookeeper集羣和kafka集羣:(在kafka根目錄下,在後臺運行時,需要在命令後加1>/dev/null 2>&1 &)
      ./bin/kafka-server-start.sh ./config/server.properties
    ->創建topic:(3個副本數,一個分區,topic名稱是youshuo_test_topic)
      ./bin/kafka-topics.sh --create --zookeeper hadoop:2181 --replication-factor 3 --partitions 1 --topic test01
    ->查看topic列表:
      ./bin/kafka-topics.sh --list --zookeeper hadoop:2181
      hadoopTopic2
      test01
      youshuo_test_topic
    ->創建生產者:
      ./bin/kafka-console-producer.sh --broker-list hadoop:9092 --topic test01
    ->創建消費者:
      ./bin/kafka-console-consumer.sh --zookeeper hadoop:9092 --topic test01 --from-beginning
    ->查看某個話題狀態信息 :
      ./bin/kafka-topics.sh --describe --zookeeper hadoop:2181 --topic test01
    ->配置多服務節點集羣:(https://kafka.apache.org/documentation/#quickstart_multibroker)

    Producer API:發送流數據到Kafka集羣的topic的應用
      需要引入的依賴:
	    <dependency>
		  <groupId>org.apache.kafka</groupId>
		  <artifactId>kafka-clients</artifactId>
		  <version>2.3.0</version>
	    </dependency>
      官方實例-Examples:(官方文檔javadocs -> https://kafka.apache.org/23/javadoc/index.html?org/apache/kafka/clients/producer/KafkaProducer.html)
    Consumer API:從Kafka集羣的topic中讀取數據流的應用
      需要引入的依賴:
        同上
      官方實例-Examples: (官方文檔javadocs -> https://kafka.apache.org/23/javadoc/index.html?org/apache/kafka/clients/consumer/KafkaConsumer.html)
    Stream API: 以流處理應用的身份從一個或多個源topic進行消費併產生輸出流到一個或多個目標topic中
      需要引入的依賴:
        <dependency>
		  <groupId>org.apache.kafka</groupId>
		  <artifactId>kafka-streams</artifactId>
		  <version>2.3.0</version>
		</dependency>
	  官方實例-Examples: (官方文檔javadocs -> https://kafka.apache.org/23/javadoc/index.html?org/apache/kafka/streams/KafkaStreams.html)
	  新增Streams接口文檔地址:https://kafka.apache.org/23/documentation/streams/
	Connect API:構建一個生產者或消費者到Kafka topic的持續連接
	  ...


->kafka基本概念:
  Fast: 非常快,單個Kafka broker每秒能夠讀寫數百兆字節從數千臺客戶端,
  Scalable: 可擴展,容量不夠之後,可以通過增加機器來解決
  Durable: 消息可以持久化到硬盤上
  Distributed by Design

  kafka是一個分佈式的消息緩存系統
  kafka集羣中的服務器都叫做broker
  kafka有兩類客戶端,一類叫producer(消息生產者),一類叫做consumer(消費者),客戶端和broker服務器之間採用tcp協議連接
  kafka中不同業務系統的消息可以通過topic進行區分,而且每一個消息topic都會被分區,以分擔消息讀寫的負載
  每一個分區都可以有多個副本,以防止數據的丟失
  某一個分區中的數據如果需要更新,都必須通過該分區所有副本中的leader進行更新
  消費者可以分組,比如有兩個消費者組A和B,共同消費一個topic: order_info, A和B所消費的消息不會重複,比如:order_info中有100個消息,每個消息有一個id, 編號從0-99,那麼,如果A組消費0-49號,B組就消費50-99號
  消費者在具體消費某個topic中的消息時,可以指定起始偏移量(宕機後,可以從指定地方讀起)

  ->kafka快速入門實際操作(基於獨立的zookeeper集羣環境下,多機器 現在最新版本的kafka 執行命令中用--bootstrap-server代替了--zookeeper是什麼意思):
    ->啓動zookeeper集羣和kafka集羣:(在kafka根目錄下,在後臺運行時,需要在命令後加1>/dev/null 2>&1 &)
      ./bin/kafka-server-start.sh ./config/server.properties
    ->創建topic:(3個副本數,一個分區,topic名稱是youshuo_test_topic)
      ./bin/kafka-topics.sh --create --zookeeper hadoop:2181 --replication-factor 3 --partitions 1 --topic test01
    ->查看topic列表:
      ./bin/kafka-topics.sh --list --zookeeper hadoop:2181
      hadoopTopic2
      test01
      youshuo_test_topic
    ->創建生產者:
      ./bin/kafka-console-producer.sh --broker-list hadoop:9092 --topic test01
    ->創建消費者:
      ./bin/kafka-console-consumer.sh --zookeeper hadoop:9092 --topic test01 --from-beginning
    ->查看某個話題狀態信息 :
      ./bin/kafka-topics.sh --describe --zookeeper hadoop:2181 --topic test01

->kafka客戶端編程:
//生產者
public class ProducerDemo {
  public static void main(String[] args) {
    Properties props = new Properties();
    props.put("zk.connect", "hadoop:2181,hadoop02:2181,hadoop03:2181");
    props.put("metadata.broker.list", "hadoop:9092,hadoop02:9092,hadoop03:9092");
    props.put("serializer.class", "kafka.serializer.StringEncoder");
    ProducerConfig config = new ProducerConfig(props);
    Producer<String, String> producer = new Producer<String, String>(config);

    //發送業務消息
    //讀取文件 讀取內存數據庫 讀socket端口
    for (int i = 1; i <= 1000; i++) {
      producer.send(new KeyedMessage<String, String>("Hey ", "i said i love you for " + i + "times"));
      Thread.sleep(500);
    }
  }
}
//消費者
public class ConsumerDemo {
  private static final String topic = "order";
  private static final Integer threads = 1;

  public static void main(String[] args) {
    Properties props = new Properties();
    props.put("zk.connect", "hadoop:2181,hadoop02:2181,hadoop03:2181");
    props.put("group.id", "1111");
    props.put("auto.offset.reset", "smallest");

    ConsumerConfig = config = new ConsumerConfig(props);
    ConsumerConnector consumer = Consumer.createJavaConsumerConnector(config);
    Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
    topicCountMap.put("test01", 1); //param1: topicName; param2: threads
    topicCountMap.put("test02", 10);
    Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
    List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(topic);

    for (final KafkaStream<byte[], byte[]> KafkaStream : streams) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          for(MessageAndMetadata<byte[], byte[]> mm : kafkaStream) {
            String msg = new String(mm.message);
            sout(msg);
          }
        }
      }).start();
    }
  }
}

flink中國github社區: PPT + 視頻

->flink介紹
    bound (bound、unbound)
    state (stateful、)
    time (Event Time、Processing Time)
    API
  ->flink基本概念:有狀態流式處理引擎的基石
    有狀態流式處理
    有狀態流式處理的挑戰
      狀態容錯(State Fault Tolerance)
      狀態維護(State Managerment)
      Event-time處理(Event-time Processing)
      狀態保存與遷移(Savepoints and Job Migration)
  ->flink安裝部署以及環境部署等快速入門:
    ->選擇flink的版本進行安裝(視頻中採用源碼文件編譯安裝的方法,實際操作打算採用二進制壓縮文件進行安裝)
    ->IDEA中添加Java的Checkstyle:
      Flink在編譯時會強制代碼風格的檢查,如果代碼風格不符合規範,可能會編譯失敗。
      Intellij內置對Checkstyle的支持,可以檢查一下Checkstyle-IDEA plugin是否安裝(IntelliJ
	  IDEA -> Preferences -> Plugins,搜索“Checkstyle-IDEA”)
	    相關配置詳見1.3 Flink 安裝部署、環境配置及運行應用程序

->flink DataStream API編程:
    分佈式流處理基礎
    Flink Datastream API概覽
    其他問題及源碼簡析
    ->分佈式流處理的基本模型:
      邏輯模型(DAG):圖略,Source數據源->Operation操作->Sink數據匯
    ->流處理API的衍變:
      Storm: ("面向操作",底層)
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("spout", new RandomSentenceSpout(), 5);
        builder.setBolt("split", new SplitSentence(), 8).shuffleGrouping("spout");
        builder.setBolt("count", new WordCount(), 12).fieldsGrouping("split", new Fields("word"));
      Flink: ("面向數據",高層)
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStream<String> text = env.readTextFile("input");
        DataStream<Tuple2<String, Integer>> counts = text.flatMap(new Tokenizer()).keyBy(0).sum(1);
        counts.writeAsText("output");

->DataStream 基本轉換:(Flink中的所有轉換都是圍繞着DataStream的抽象進行轉換)
      1.DataStream以map的形式進行轉換(即輸入是DataStream,輸出是DataStream)
      2.將一條數據(SplitStream接口對輸入的DataStream進行切分,後可以調用select方法選擇帶有某個標籤的切分流)
      3.將兩條DataStream流通過connect(DataStream)進行合併,合併後的流是ConnectedStream對象
      4.對ConnectedStream進行的map操作,叫做coMap(本身還是map)
      5.對無限的流進行切分(可以按照時間或按照元素的個數,即把無限的流給分成無數個mini-batch),每個mini-batch的生成都是由AllWindowedStream中的邏輯來生成的。(DataStream通過windowAll生成AllWindowedStream)
      6.AllWindowedStream對已經拆分好的mini-batch(DataStream)通過apply進行邏輯處理,最終得到的還是DataStream.
      7.對DataStream進行keyBy操作(即一種分組操作),得到的是一個KeyedStream
      8.(對於普通的DataStream我們是沒有辦法對其進行reduce操作的,只有把DataStream按照某些key進行分組之後,纔可以對於每個分組進行reduce操作,主要是出於計算量的一個考慮)
      9.KeyedStream也可以進行window操作,得到的是WindowedStream
      10.WindowedStream通過apply方法同樣也可以回到DataStream
      ->理解KeyedStream:(將一條數據流利用窗口這個操作切分成若干個窗口,每個窗口叫做AllWindowedStream,因爲Flink是分佈式計算引擎,當我們把切分好的窗口中的數據不做任何處理直接進行計算後得出唯一結果,則絲毫體現不出分佈式計算的優勢。所以,可以調用Keyby()將數據根據不同的類別進行橫向的切分,從而將數據流中不同類別的數據分別發送到不同的處理節點/任務上進行處理,即不同任務之間是並行的)
    ->物理分組:
      dataStream.global() 			全部發往第一個task
      dataStream.broadcast()		廣播(複製n份分別發送給下游節點)
      dataStream.forward()			上下游併發度一樣時一對一發送
      dataStream.shuffle()			隨機均勻分配
      dataStream.rebalance()		Round-Robin(輪流分配)
      dataStream.recale()			Local Round-Robin(本地輪流分配)
      dataStream.partitionCustom()	自定義單播
    ->類型系統:(TypeInformation:如 String、Tuple2<String, Integer>)
      基本類型		Java的基本類型(包裝類)以及void、String、Date、BigDecimal、BigInteger
      符合類型		Tuple和Scala case class(不支持null)、Row、POJO
      輔助、集合類型	Option、Either、List、Map等
      上述類型的數組	
      其他類型		自定義TypeInformation或Kryo處理,不推薦使用

DataStream轉換圖解


->源碼解析(實例:實時統計成交額):
	  數據源: Tuple2<String, Integer> 商品類別 -> 成交額
	  任務  : 
	    1.實時統計每個類別的成交額;		
	    2.實時統計全部類別的成交額。
RichParallelSourceFunction<Tuple2<String, Integer>>: 支持以併發的形式產生數據
private static class DataSource extends RichParallelSourceFunction<Tuple2<String, Integer>> {
  private volatile boolean running = true;

  @Override
  public void run(SourceContext<Tuple2<String, Integer>> ctx) throws Exception {

    Random random = new Random(System.currentTimeMills());
    while(running) {
      Thread.sleep((getRuntimeContext().getIndexOfThisSubtask() + 1) * 1000 + 500);
      String key = "類別" + (char)('A' + random.nextInt(3));
      int value = random.nextInt(10) + 1;
      System.out.println(String.format("Emit:\t(%s, %d)", key, value));
      ctx.collect(new Tuple2<>(key, value));
    }
  }

  @Override
  public void cancel() {
    running = false;
  }
}

public static void main(String[] args) {
  StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  env.setParallelism(2);

  DataStream<Tuple2<String, Integer>> ds = env.addSource(new DataSource());
  KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = ds.keyBy(0);

  keyedStream.sum(1).keyBy(new KeySelector<Tuple2<String, Integer>, Object>() {
    //爲了統計全局信息我們通過getKey()方法裏返回常數“欺騙”Flink將所有數據分成一組
    @Override
    public Object getKey(Tuple2<String, Integer> value) throws Exception {
      return "";
    }
  }).fold(
          new HashMap<String, Integer>(), new FoldFunction<Tuple2<String, Integer>, Map<String, Integer>>() {
            @Override
            public Map<String, Integer> fold(Map<String, Integer> accumulator, Tuple2<String, Integer> value) throws Exception {
              accumulator.put(value.f0, value.f1);
              return accumulator;
            }
          }
  ).addSink(new SinkFunction<Map<String, Integer>>() {
    @Override
    public void invoke(Map<String, Integer> value, Context context) throws Exception {
      System.out.println(value.values().stream().mapToInt(v -> v).sum());
    }
  });
  ds.addSink(new SinkFunction<Tuple2<String, Integer>>() {
    public void invoke(Tuple2<String, Integer> value, Context context) throws Exception {
      System.out.println(String.format("Get:\t(%s, %d)", value.f0, value.f1));
    }
  });

  env.execute();
}
   ->API原理:(DataStream 是如何轉換成另一個 DataStream的)

Flink整合kafka:

->Flink的kafka消費者被稱爲FlinkKafkaConsumer。它提供對一個或多個kafka主題的訪問,構造函數接受一下參數:
    1.主題名稱/主題名稱列表
    2.DeserializationSchema / KeyedDeserializationSchema用於反序列化來自kafka的數據
    3.Kafka消費者的屬性。需要以下屬性:
      -> "bootstrap.servers"
      -> "zookeeper.connect"
      -> "group.id"

正在更新…

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