Flink基础篇:数据界的HelloWorld和Flink SQL


实时计算在未来是每个数据人应该掌握的一项技能。随着5G技术的普及,大量可穿戴设备和终端的面世,物联网技术及应用,会产生海量的,数据价值密度不等的数据。那数据处理、计算、反馈的实时性要求会越来越迫切。要真正的了解和掌握那肯定要实践,今天我们将从一个数据界最经典的Hello World程序,WordCount 作为切入点,并且同时使用 SQL 方式进行实现,为以后的持续探索打好基础。

  • 首先从环境搭建入手,介绍如何搭建本地调试环境的脚手架工具;
  • 然后分别从DataSet(批处理)和 DataStream(流处理)两种方式如何进行单词计数;
  • 最后是Flink Table 和 SQL 的使用。

Flink 开发环境

通常任何计算框架在实际生产环境中都是以集群的形式运行,而我们调试代码大多数会在本地搭建一个模板工程。

Flink 一个以 Java 及 Scala 作为开发语言的开源大数据项目,通常我们推荐使用 Java 来作为开发语言,Maven 作为编译和包管理工具进行项目构建和编译。对于大多数开发者而言,JDK、Maven 和 Git 这三个开发工具是必不可少的。

关于 JDK、Maven 和 Git 的安装建议如下表所示:

工具 版本建议 备注
JDK 1.8或以上
Maven 建议Maven3.5 https://maven.apache.org/download.cgi
Git 无要求,较新版本即可 Git下载:https://git-scm.com/downloads
flink仓库:https://github.com/apache/flink
系统 建议Linux、Mac,最方便。
IDE 推荐Idea

工程创建

我们可以通过 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

我们在终端直接执行该命令:


直到出现 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 程序是大数据领域的入门HelloWorld程序,俗称“单词计数”。用来统计一段文字每个单词的出现次数,该程序主要分为两个部分:一部分是将文字拆分成单词;另一部分是单词进行分组计数并打印输出结果。

整体代码实现如下:

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

        // 创建Flink任务运行的环境
        final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

        // 创建DataSet,数据是一行一行的文本
        DataSet<String> text = env.fromElements(
                "Flink Hello World",
                "Flink Flink World",
                "Hive Elastic Search",
                "Hadoop Hive zookeeper",
                "Hello World Hello"
        );
        // 通过Flink内置的转换函数进行计算
        DataSet<Tuple2<String, Integer>> counts =
                text.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
                    @Override
                    public void flatMap(String value, Collector<Tuple2<String, Integer>> collector) throws Exception {
                        // 将文本分割
                        String[] tokens = value.toLowerCase().split("\\W+");

                        for (String token : tokens) {
                            if (token.length() > 0) {
                                collector.collect(new Tuple2<String, Integer>(token, 1));
                            }
                        }
                    }
                })
                        .groupBy(0)
                        .sum(1);
        //结果打印
        counts.printToErr();
    }

上面是实现WordCount最简洁的一种,整个过程有以下几个步。

首先,我们需要创建 Flink 的上下文运行环境:

ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

然后,使用 fromElements 函数创建一个 DataSet 对象;

接着,使用该DataSet 对象包含了我们的数据,使用 FlatMap、GroupBy、SUM 函数进行转换。

最后,直接在控制台打印输出。

我们可以直接右键运行一下 main 方法,在控制台会出现我们打印的计算结果:

image-20200523193730083.png

DataStream WordCount

为了模仿一个流式计算环境,我们选择监听一个本地的 Socket 端口,并且使用 Flink 中的滚动窗口,每 5 秒打印一次计算结果。代码如下:

public class StreamingJob {

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

        // 创建流式任务执行环境
        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);
                    }
                });

        // 用并行度为1的单线程打印结果
        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 方法:

INFO  org.apache.flink.streaming.api.functions.source.SocketTextStreamFunction  - Connecting to server socket 127.0.0.1:9000

可以看到,工程启动后开始监听 127.0.0.1 的 9000 端口。

在 nc 中输入:

$ nc -lk 9000
hello world flink
hello flink flink

可以在控制台看到:

world : 1
hello : 2
flink : 3

Flink Table & SQL WordCount

Flink SQL是为了简化计算模型、降低用户使用实时计算门槛而设计的一套符合标准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 程序作为FlinkSQL入门,上面已经通过 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 env = ExecutionEnvironment.getExecutionEnvironment();
BatchTableEnvironment batchTableEnv = BatchTableEnvironment.create(env);
  • 读取一行模拟数据作为输入:
String words = "hello flink hello world hello es hello stream flink";
String[] split = words.split("\\W+");
ArrayList<WordCount> list = new ArrayList<>();
for(String word : split){
    WordCount wc = new WordCount(word, 1);
    list.add(wc);
}
DataSet<WordCount> input = env.fromCollection(list);
  • 注册成表,执行 SQL,然后输出:
//DataSet->Table, 指定字段名
Table table = batchTableEnv.fromDataSet(input, "word,frequency");
table.printSchema();
//注册为一个表
batchTableEnv.createTemporaryView("WordCount", table);

Table table02 = batchTableEnv.sqlQuery("select word as word, sum(frequency) as frequency from WordCount GROUP BY word");
//转Table->DataSet
DataSet<WordCount> ds  = batchTableEnv.toDataSet(table02, WordCount.class);
ds.printToErr();

整体代码结构如下:

public class WordCountSQL {

    public static void main(String[] args) throws Exception{
        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        //创建一个tableEnvironment
        BatchTableEnvironment batchTableEnv = BatchTableEnvironment.create(env);

        String words = "hello flink hello world hello es hello stream flink";

        String[] split = words.split("\\W+");
        ArrayList<WordCount> list = new ArrayList<>();

        for(String word : split){
            WordCount wc = new WordCount(word, 1);
            list.add(wc);
        }

        DataSet<WordCount> input = env.fromCollection(list);
        //DataSet转Table, 指定字段名
        Table table = batchTableEnv.fromDataSet(input, "word,frequency");
        table.printSchema();
        //注册为一个表
        batchTableEnv.createTemporaryView("WordCount", table);

        Table table02 = batchTableEnv.sqlQuery("select word as word, sum(frequency) as frequency from WordCount GROUP BY word");
        //Table转DataSet
        DataSet<WordCount> ds  = batchTableEnv.toDataSet(table02, WordCount.class);
        ds.printToErr();
    }

    public static class WordCount {
        public String word;
        public long frequency;

        public WordCount() {}

        public WordCount(String word, long frequency) {
            this.word = word;
            this.frequency = frequency;
        }

        @Override
        public String toString() {
            return  word + ", " + frequency;
        }
    }
}

我们直接运行该程序,在控制台可以看到输出结果:

flink, 2
world, 1
hello, 4
es, 1
stream, 1

总结

本篇我们介绍了 Flink 的工程创建,如何搭建调试环境,同时以WordCount 单词计数这一经典的例子场景用 Flink 进行了实现,和初步体验了 Flink SQL 的强大和易用的特点。
关注我

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343