第02講:Flink 入門程式 WordCount 和 SQL 實現

大資料技術派發表於2022-01-22

我們右鍵執行時相當於在本地啟動了一個單機版本。生產中都是叢集環境,並且是高可用的,生產上提交任務需要用到flink run 命令,指定必要的引數。

本課時我們主要介紹 Flink 的入門程式以及 SQL 形式的實現。

上一課時已經講解了 Flink 的常用應用場景和架構模型設計,這一課時我們將會從一個最簡單的 WordCount 案例作為切入點,並且同時使用 SQL 方式進行實現,為後面的實戰課程打好基礎。

我們首先會從環境搭建入手,介紹如何搭建本地除錯環境的腳手架;然後分別從DataSet(批處理)和 DataStream(流處理)兩種方式如何進行單詞計數開發;最後介紹 Flink Table 和 SQL 的使用。

通常來講,任何一門大資料框架在實際生產環境中都是以叢集的形式執行,而我們除錯程式碼大多數會在本地搭建一個模板工程,Flink 也不例外。

Flink 一個以 Java 及 Scala 作為開發語言的開源大資料專案,通常我們推薦使用 Java 來作為開發語言,Maven 作為編譯和包管理工具進行專案構建和編譯。對於大多數開發者而言,JDK、Maven 和 Git 這三個開發工具是必不可少的。

關於 JDK、Maven 和 Git 的安裝建議如下表所示:

img

工程建立

一般來說,我們在通過 IDE 建立工程,可以自己新建工程,新增 Maven 依賴,或者直接用 mvn 命令建立應用:

複製程式碼

mvn   archetype:generate  \

        -DarchetypeGroupId=org.apache.flink \

        -DarchetypeArtifactId=flink-quickstart-java \

        -DarchetypeVersion=1.10.0

通過指定 Maven 工程的三要素,即 GroupId、ArtifactId、Version 來建立一個新的工程。同時 Flink 給我提供了更為方便的建立 Flink 工程的方法:

複製程式碼

curl https://flink.apache.org/q/quickstart.sh | bash -s 1.10.0

我們在終端直接執行該命令:

img

img

直接出現 Build Success 資訊,我們可以在本地目錄看到一個已經生成好的名為 quickstart 的工程。

這裡需要的主要的是,自動生成的專案 pom.xml 檔案中對於 Flink 的依賴註釋掉 scope:

複製程式碼

<dependency>

   <groupId>org.apache.flink</groupId>

   <artifactId>flink-java</artifactId>

   <version>${flink.version}</version>

   <!--<scope>provided</scope>-->

</dependency>

<dependency>

   <groupId>org.apache.flink</groupId>

   <artifactId>flink-streaming-java_${scala.binary.version}</artifactId>

   <version>${flink.version}</version>

   <!--<scope>provided</scope>-->

</dependency>

DataSet WordCount

WordCount 程式是大資料處理框架的入門程式,俗稱“單詞計數”。用來統計一段文字每個單詞的出現次數,該程式主要分為兩個部分:一部分是將文字拆分成單詞;另一部分是單詞進行分組計數並列印輸出結果。

整體程式碼實現如下:

複製程式碼

 public static void main(String[] args) throws Exception {

      // 建立Flink執行的上下文環境
      final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

      // 建立DataSet,這裡我們的輸入是一行一行的文字
      DataSet<String> text = env.fromElements(
            "Flink Spark Storm",
            "Flink Flink Flink",
            "Spark Spark Spark",
            "Storm Storm Storm"
      );
      // 通過Flink內建的轉換函式進行計算
      DataSet<Tuple2<String, Integer>> counts =
            text.flatMap(new LineSplitter())
                  .groupBy(0)
                  .sum(1);
      //結果列印
      counts.printToErr();

   }
public static final class LineSplitter implements FlatMapFunction<String, Tuple2<String, Integer>> {

      @Override
      public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
         // 將文字分割
         String[] tokens = value.toLowerCase().split("\\W+");

         for (String token : tokens) {
            if (token.length() > 0) {
               out.collect(new Tuple2<String, Integer>(token, 1));
            }
         }
      }
    }

實現的整個過程中分為以下幾個步驟。

首先,我們需要建立 Flink 的上下文執行環境:

複製程式碼

ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

然後,使用 fromElements 函式建立一個 DataSet 物件,該物件中包含了我們的輸入,使用 FlatMap、GroupBy、SUM 函式進行轉換。

最後,直接在控制檯列印輸出。

我們可以直接右鍵執行一下 main 方法,在控制檯會出現我們列印的計算結果:

img

DataStream WordCount

為了模仿一個流式計算環境,我們選擇監聽一個本地的 Socket 埠,並且使用 Flink 中的滾動視窗,每 5 秒列印一次計算結果。程式碼如下:

複製程式碼

public class StreamingJob {



    public static void main(String[] args) throws Exception {



        // 建立Flink的流式計算環境

        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();



        // 監聽本地9000埠

        DataStream<String> text = env.socketTextStream("127.0.0.1", 9000, "\n");



        // 將接收的資料進行拆分,分組,視窗計算並且進行聚合輸出

        DataStream<WordWithCount> windowCounts = text

                .flatMap(new FlatMapFunction<String, WordWithCount>() {

                    @Override

                    public void flatMap(String value, Collector<WordWithCount> out) {

                        for (String word : value.split("\\s")) {

                            out.collect(new WordWithCount(word, 1L));

                        }

                    }

                })

                .keyBy("word")

                .timeWindow(Time.seconds(5), Time.seconds(1))

                .reduce(new ReduceFunction<WordWithCount>() {

                    @Override

                    public WordWithCount reduce(WordWithCount a, WordWithCount b) {

                        return new WordWithCount(a.word, a.count + b.count);

                    }

                });



        // 列印結果

        windowCounts.print().setParallelism(1);



        env.execute("Socket Window WordCount");

    }



    // Data type for words with count

    public static class WordWithCount {



        public String word;

        public long count;



        public WordWithCount() {}



        public WordWithCount(String word, long count) {

            this.word = word;

            this.count = count;

        }



        @Override

        public String toString() {

            return word + " : " + count;

        }

    }

}

整個流式計算的過程分為以下幾步。

首先建立一個流式計算環境:

複製程式碼

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

然後進行監聽本地 9000 埠,將接收的資料進行拆分、分組、視窗計算並且進行聚合輸出。程式碼中使用了 Flink 的視窗函式,我們在後面的課程中將詳細講解。

我們在本地使用 netcat 命令啟動一個埠:

複製程式碼

nc -lk 9000

然後直接執行我們的 main 方法:

img

可以看到,工程啟動後開始監聽 127.0.0.1 的 9000 埠。

在 nc 中輸入:

複製程式碼

$ nc -lk 9000

Flink Flink Flink 

Flink Spark Storm

可以在控制檯看到:

複製程式碼

Flink : 4

Spark : 1

Storm : 1

Flink SQL 是 Flink 實時計算為簡化計算模型,降低使用者使用實時計算門檻而設計的一套符合標準 SQL 語義的開發語言。

一個完整的 Flink SQL 編寫的程式包括如下三部分。

  • Source Operator:是對外部資料來源的抽象, 目前 Apache Flink 內建了很多常用的資料來源實現,比如 MySQL、Kafka 等。
  • Transformation Operators:運算元操作主要完成比如查詢、聚合操作等,目前 Flink SQL 支援了 Union、Join、Projection、Difference、Intersection 及 window 等大多數傳統資料庫支援的操作。
  • Sink Operator:是對外結果表的抽象,目前 Apache Flink 也內建了很多常用的結果表的抽象,比如 Kafka Sink 等。

我們也是通過用一個最經典的 WordCount 程式作為入門,上面已經通過 DataSet/DataStream API 開發,那麼實現同樣的 WordCount 功能, Flink Table & SQL 核心只需要一行程式碼:

複製程式碼

//省略掉初始化環境等公共程式碼

SELECT word, COUNT(word) FROM table GROUP BY word;

首先,整個工程中我們 pom 中的依賴如下圖所示:

複製程式碼

<dependency>

         <groupId>org.apache.flink</groupId>

         <artifactId>flink-java</artifactId>

         <version>1.10.0</version>

     </dependency>

<dependency>

         <groupId>org.apache.flink</groupId>

         <artifactId>flink-streaming-java_2.11

         <version>1.10.0</version>

</dependency>

<dependency>

         <groupId>org.apache.flink</groupId>

         <artifactId>flink-table-api-java-bridge_2.11</artifactId>

         <version>1.10.0</version>

</dependency>

<dependency>

         <groupId>org.apache.flink</groupId>

         <artifactId>flink-table-planner-blink_2.11</artifactId>

         <version>1.10.0</version>

</dependency>

<dependency>

         <groupId>org.apache.flink</groupId>

         <artifactId>flink-table-planner_2.11</artifactId>

         <version>1.10.0</version>

</dependency>

     <dependency>

         <groupId>org.apache.flink</groupId>

         <artifactId>flink-table-api-scala-bridge_2.11</artifactId>

         <version>1.10.0</version>

</dependency>

第一步,建立上下文環境:

複製程式碼

ExecutionEnvironment fbEnv = ExecutionEnvironment.getExecutionEnvironment();

BatchTableEnvironment fbTableEnv = BatchTableEnvironment.create(fbEnv);

第二步,讀取一行模擬資料作為輸入:

複製程式碼

String words = "hello flink hello lagou";

String[] split = words.split("\\W+");

ArrayList<WC> list = new ArrayList<>();



for(String word : split){

    WC wc = new WC(word,1);

    list.add(wc);

}

DataSet<WC> input = fbEnv.fromCollection(list);

第三步,註冊成表,執行 SQL,然後輸出:

複製程式碼

//DataSet 轉sql, 指定欄位名

Table table = fbTableEnv.fromDataSet(input, "word,frequency");

table.printSchema();



//註冊為一個表

fbTableEnv.createTemporaryView("WordCount", table);



Table table02 = fbTableEnv.sqlQuery("select word as word, sum(frequency) as frequency from WordCount GROUP BY word");



//將錶轉換DataSet

DataSet<WC> ds3  = fbTableEnv.toDataSet(table02, WC.class);

ds3.printToErr();

整體程式碼結構如下:

複製程式碼

public class WordCountSQL {



    public static void main(String[] args) throws Exception{



        //獲取執行環境

        ExecutionEnvironment fbEnv = ExecutionEnvironment.getExecutionEnvironment();

        //建立一個tableEnvironment

        BatchTableEnvironment fbTableEnv = BatchTableEnvironment.create(fbEnv);



        String words = "hello flink hello lagou";



        String[] split = words.split("\\W+");

        ArrayList<WC> list = new ArrayList<>();



        for(String word : split){

            WC wc = new WC(word,1);

            list.add(wc);

        }

        DataSet<WC> input = fbEnv.fromCollection(list);



        //DataSet 轉sql, 指定欄位名

        Table table = fbTableEnv.fromDataSet(input, "word,frequency");

        table.printSchema();



        //註冊為一個表

        fbTableEnv.createTemporaryView("WordCount", table);



        Table table02 = fbTableEnv.sqlQuery("select word as word, sum(frequency) as frequency from WordCount GROUP BY word");



        //將錶轉換DataSet

        DataSet<WC> ds3  = fbTableEnv.toDataSet(table02, WC.class);

        ds3.printToErr();

    }



    public static class WC {

        public String word;

        public long frequency;



        public WC() {}



        public WC(String word, long frequency) {

            this.word = word;

            this.frequency = frequency;

        }



        @Override

        public String toString() {

            return  word + ", " + frequency;

        }

    }

}

我們直接執行該程式,在控制檯可以看到輸出結果:

img

總結

本課時介紹了 Flink 的工程建立,如何搭建除錯環境的腳手架,同時以 WordCount 單詞計數這一最簡單最經典的場景用 Flink 進行了實現。第一次體驗了 Flink SQL 的強大之處,讓你有一個直觀的認識,為後續內容打好基礎。

相關文章