本篇我们将使用Java语言来实现Flink的单词统计。

代码开发

环境准备

导入Flink 1.9 pom依赖

<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
</dependencies>

构建Flink流处理环境

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

自定义source

每秒生成一行文本

DataStreamSource<String> wordLineDS = env.addSource(new RichSourceFunction<String>() {
private boolean isCanal = false;
private String[] words = {
"important oracle jdk license update",
"the oracle jdk license has changed for releases starting april 16 2019",
"the new oracle technology network license agreement for oracle java se is substantially different from prior oracle jdk licenses the new license permits certain uses such as ",
"personal use and development use at no cost but other uses authorized under prior oracle jdk licenses may no longer be available please review the terms carefully before ",
"downloading and using this product an faq is available here ",
"commercial license and support is available with a low cost java se subscription",
"oracle also provides the latest openjdk release under the open source gpl license at jdk java net"
}; @Override
public void run(SourceContext<String> ctx) throws Exception {
// 每秒发送一行文本
while (!isCanal) {
int randomIndex = RandomUtils.nextInt(0, words.length);
ctx.collect(words[randomIndex]);
Thread.sleep(1000);
}
} @Override
public void cancel() {
isCanal = true;
}
});

单词计算

// 3. 单词统计
// 3.1 将文本行切分成一个个的单词
SingleOutputStreamOperator<String> wordsDS = wordLineDS.flatMap((String line, Collector<String> ctx) -> {
// 切分单词
Arrays.stream(line.split(" ")).forEach(word -> {
ctx.collect(word);
});
}).returns(Types.STRING); //3.2 将单词转换为一个个的元组
SingleOutputStreamOperator<Tuple2<String, Integer>> tupleDS = wordsDS
.map(word -> Tuple2.of(word, 1))
.returns(Types.TUPLE(Types.STRING, Types.INT)); // 3.3 按照单词进行分组
KeyedStream<Tuple2<String, Integer>, String> keyedDS = tupleDS.keyBy(tuple -> tuple.f0); // 3.4 对每组单词数量进行累加
SingleOutputStreamOperator<Tuple2<String, Integer>> resultDS = keyedDS
.timeWindow(Time.seconds(3))
.reduce((t1, t2) -> Tuple2.of(t1.f0, t1.f1 + t2.f1)); resultDS.print();

参考代码

public class WordCount {
public static void main(String[] args) throws Exception {
// 1. 构建Flink流式初始化环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 2. 自定义source - 每秒发送一行文本
DataStreamSource<String> wordLineDS = env.addSource(new RichSourceFunction<String>() {
private boolean isCanal = false;
private String[] words = {
"important oracle jdk license update",
"the oracle jdk license has changed for releases starting april 16 2019",
"the new oracle technology network license agreement for oracle java se is substantially different from prior oracle jdk licenses the new license permits certain uses such as ",
"personal use and development use at no cost but other uses authorized under prior oracle jdk licenses may no longer be available please review the terms carefully before ",
"downloading and using this product an faq is available here ",
"commercial license and support is available with a low cost java se subscription",
"oracle also provides the latest openjdk release under the open source gpl license at jdk java net"
}; @Override
public void run(SourceContext<String> ctx) throws Exception {
// 每秒发送一行文本
while (!isCanal) {
int randomIndex = RandomUtils.nextInt(0, words.length);
ctx.collect(words[randomIndex]);
Thread.sleep(1000);
}
} @Override
public void cancel() {
isCanal = true;
}
}); // 3. 单词统计
// 3.1 将文本行切分成一个个的单词
SingleOutputStreamOperator<String> wordsDS = wordLineDS.flatMap((String line, Collector<String> ctx) -> {
// 切分单词
Arrays.stream(line.split(" ")).forEach(word -> {
ctx.collect(word);
});
}).returns(Types.STRING); //3.2 将单词转换为一个个的元组
SingleOutputStreamOperator<Tuple2<String, Integer>> tupleDS = wordsDS
.map(word -> Tuple2.of(word, 1))
.returns(Types.TUPLE(Types.STRING, Types.INT)); // 3.3 按照单词进行分组
KeyedStream<Tuple2<String, Integer>, String> keyedDS = tupleDS.keyBy(tuple -> tuple.f0); // 3.4 对每组单词数量进行累加
SingleOutputStreamOperator<Tuple2<String, Integer>> resultDS = keyedDS
.timeWindow(Time.seconds(3))
.reduce((t1, t2) -> Tuple2.of(t1.f0, t1.f1 + t2.f1)); resultDS.print(); env.execute("app");
}
}

Flink对Java Lambda表达式支持情况

Flink支持Java API所有操作符使用Lambda表达式。但是,但Lambda表达式使用Java泛型时,就需要声明类型信息。

我们来看下上述的这段代码:

SingleOutputStreamOperator<String> wordsDS = wordLineDS.flatMap((String line, Collector<String> ctx) -> {
// 切分单词
Arrays.stream(line.split(" ")).forEach(word -> {
ctx.collect(word);
});
}).returns(Types.STRING);

之所以这里将所有的类型信息,因为Flink无法正确自动推断出来Collector中带的泛型。我们来看一下FlatMapFuntion的源代码

@Public
@FunctionalInterface
public interface FlatMapFunction<T, O> extends Function, Serializable {

/**
* The core method of the FlatMapFunction. Takes an element from the input data set and transforms
* it into zero, one, or more elements.
*
* @param value The input value.
* @param out The collector for returning result values.
*
* @throws Exception This method may throw exceptions. Throwing an exception will cause the operation
* to fail and may trigger recovery.
*/
void flatMap(T value, Collector<O> out) throws Exception;
}

我们发现 flatMap的第二个参数是Collector<O>,是一个带参数的泛型。Java编译器编译该代码时会进行参数类型擦除,所以Java编译器会变成成:

void flatMap(T value, Collector out)

这种情况,Flink将无法自动推断类型信息。如果我们没有显示地提供类型信息,将会出现以下错误:

org.apache.flink.api.common.functions.InvalidTypesException: The generic type parameters of 'Collector' are missing.
In many cases lambda methods don't provide enough information for automatic type extraction when Java generics are involved.
An easy workaround is to use an (anonymous) class instead that implements the 'org.apache.flink.api.common.functions.FlatMapFunction' interface.
Otherwise the type has to be specified explicitly using type information.

这种情况下,必须要显示指定类型信息,否则输出将返回值视为Object类型,这将导致Flink无法正确序列化。

所以,我们需要显示地指定Lambda表达式的参数类型信息,并通过returns方法显示指定输出的类型信息

我们再看一段代码:

SingleOutputStreamOperator<Tuple2<String, Integer>> tupleDS = wordsDS
.map(word -> Tuple2.of(word, 1))
.returns(Types.TUPLE(Types.STRING, Types.INT));

为什么map后面也需要指定类型呢?

因为此处map返回的是Tuple2类型,Tuple2是带有泛型参数,在编译的时候同样会被查出泛型参数信息,导致Flink无法正确推断。

更多关于对Java Lambda表达式的支持请参考官网:https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/java_lambdas.html

「Flink」使用Java lambda表达式实现Flink WordCount的更多相关文章

  1. Java Lambda表达式初探

    Java Lambda表达式初探 前言 本文受启发于Trisha Gee在JavaOne 2016的主题演讲Refactoring to Java 8. Java 8已经发行两年多,但很多人仍然在使用 ...

  2. Java Lambda表达式入门

    Java Lambda表达式入门 http://blog.csdn.net/renfufei/article/details/24600507 Java 8十个lambda表达式案例 http://w ...

  3. Java Lambda表达式入门[转]

    原文链接: Start Using Java Lambda Expressions http://blog.csdn.net/renfufei/article/details/24600507 下载示 ...

  4. Java Lambda表达式教程与示例

    Lambda表达式是Java 8中引入的一个新特性.一个lambda表达式是一个匿名函数,而且这个函数没有名称且不属于任何类.lambda表达式的概念最初是在LISP编程语言中引入的. Java La ...

  5. Java Lambda表达式forEach无法跳出循环的解决思路

    Java Lambda表达式forEach无法跳出循环的解决思路 如果你使用过forEach方法来遍历集合,你会发现在lambda表达式中的return并不会终止循环,这是由于lambda的底层实现导 ...

  6. java lambda表达式学习笔记

    lambda是函数式编程(FP,functional program),在java8中引入,而C#很早之前就有了.在java中lambda表达式是'->',在C#中是‘=>’. 杜甫说:射 ...

  7. 《Java基础知识》Java Lambda表达式

    接触Lambda表达式的时候,第一感觉就是,这个是啥?我居然看不懂,于是开始寻找资料,必须弄懂它. 先来看一个案例: @FunctionalInterface public interface MyL ...

  8. Java lambda 表达式常用示例

    实体类 package com.lkb.java_lambda.dto; import lombok.Data; /** * @program: java_lambda * @description: ...

  9. Java lambda 表达式详解(JDK 8 新特性)

    什么是 lambda 表达式 lambda 表达式(拉姆达表达式)是 JAVA 8 中提供的一种新的特性,它使 Java 也能进行简单的"函数式编程". lambda 表达式的本质 ...

随机推荐

  1. Linux下socket编程基本知识

    本文档主要讲解了Linux下socket编程的一些基本知识,主要包括套接字和字节序的概念,以及一些常用的结构体和函数. 本文是在网易云课堂学习过程中的记录,这个老师讲得很不错,推荐大家围观. Linu ...

  2. Linux网络文件共享服务之NFS

    一.NFS服务简介 NFS全称network file system 网络文件系统,基于内核的文件系统,有sun公司开发,通过使用NFS,用户和程序可以像访问本地文件一样访问远端系统上的文件,它基于r ...

  3. LeetCode刷题总结-链表

    LeetCode刷题总结-链表 一.链表     链表分为单向链表.单向循环链表和双向链表,一下以单向链表为例实现单向链表的节点实现和单链表的基本操作. 单向链表 单向链表也叫单链表,是链表中最简单的 ...

  4. 笔记常用Linux命令(三) 查看服务器日志

    服务器日志 用于记录服务器的运行情况 查看服务器日志 tail:查看后面几行 n 显示行数 f 持续侦测后面的内容,查看服务器日志常用 查看最新的服务日志(静态) 命令格式:tail -n 行数 日志 ...

  5. Brokers类型配置

    模块 配置项 作用域 备注 DynamicConnectionQuota max.connectionsmax.connections.per.ipmax.connections.per.ip.ove ...

  6. Scala 学习(8)之「trait (2) 」

    trait调用链 Scala 中支持让类继承多个 trait 后,依次调用多个 trait 中的同一个方法,只要让多个 trait 的同一个方法中,在最后都执行super.方法即可 类中调用多个 tr ...

  7. session、cookie、sessionStorage、localStorage的简要理解

    一.cookie和session 首先 session 和 cookie 用于浏览器客户端与服务端数据交互,通过会话的方式跟踪浏览器用户身份. 1.cookie (1).一般由服务器生成,可以设置失效 ...

  8. Ceph 存储集群-低级运维

    低级集群运维包括启动.停止.重启集群内的某个具体守护进程:更改某守护进程或子系统配置:增加或拆除守护进程.低级运维还经常遇到扩展.缩减 Ceph 集群,以及更换老旧.或损坏的硬件. 一.增加/删除 O ...

  9. Harbor 1.9.x 版本从源码构建和运行

    介绍 本指南为开发人员提供了从源代码构建和运行Harbor的说明. 步骤1:为Harbor的构建环境做准备 Harbor被部署为多个Docker容器,并且大多数代码都是用Go语言编写的.构建环境需要D ...

  10. 使用ClouderaManager管理的HBase的RegionServer无法启动(启动失败)的问题

    问题概述 "新冠期间"远程办公,需要重新搭建一套ClouderaManager(CM)开发环境,一位测试同事发现HBase的RegionServer无法启动,在CM界面上启动总是失 ...