Javalambda表达式如何实现FlinkWordCount
创始人
2024-12-20 12:02:12
0次

环境准备

导入Flink 1.9 pom依赖

          org.apache.flink      flink-java      1.9.0              org.apache.flink      flink-streaming-java_2.11      1.9.0              org.apache.commons      commons-lang3      3.7      

构建Flink流处理环境

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

自定义source

每秒生成一行文本

DataStreamSource wordLineDS = env.addSource(new RichSourceFunction() {      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 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 wordsDS = wordLineDS.flatMap((String line, Collector ctx) -> {      // 切分单词      Arrays.stream(line.split(" ")).forEach(word -> {        ctx.collect(word);      });    }).returns(Types.STRING);    //3.2 将单词转换为一个个的元组    SingleOutputStreamOperator> tupleDS = wordsDS        .map(word -> Tuple2.of(word, 1))        .returns(Types.TUPLE(Types.STRING, Types.INT));    // 3.3 按照单词进行分组    KeyedStream, String> keyedDS = tupleDS.keyBy(tuple -> tuple.f0);    // 3.4 对每组单词数量进行累加    SingleOutputStreamOperator> 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 wordLineDS = env.addSource(new RichSourceFunction() {      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 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 wordsDS = wordLineDS.flatMap((String line, Collector ctx) -> {      // 切分单词      Arrays.stream(line.split(" ")).forEach(word -> {        ctx.collect(word);      });    }).returns(Types.STRING);    //3.2 将单词转换为一个个的元组    SingleOutputStreamOperator> tupleDS = wordsDS        .map(word -> Tuple2.of(word, 1))        .returns(Types.TUPLE(Types.STRING, Types.INT));    // 3.3 按照单词进行分组    KeyedStream, String> keyedDS = tupleDS.keyBy(tuple -> tuple.f0);    // 3.4 对每组单词数量进行累加    SingleOutputStreamOperator> 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 wordsDS = wordLineDS.flatMap((String line, Collector ctx) -> {      // 切分单词      Arrays.stream(line.split(" ")).forEach(word -> {        ctx.collect(word);      });    }).returns(Types.STRING);

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

@Public@FunctionalInterfacepublic interface FlatMapFunction 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 out) throws Exception;}

我们发现 flatMap的第二个参数是Collector,是一个带参数的泛型。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> tupleDS = wordsDS        .map(word -> Tuple2.of(word, 1))        .returns(Types.TUPLE(Types.STRING, Types.INT));

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

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

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...