CoProcessFunction实战三部曲之一:基本功能

本文涉及的产品
实时计算 Flink 版,1000CU*H 3个月
简介: 从CoProcessFunction的基本功能入手,为后面的状态、定时实战打好基础

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码): https://github.com/zq2599/blog_demos

关于《CoProcessFunction实战三部曲》系列

  • 《CoProcessFunction实战三部曲》旨在通过三次实战,由浅入深的学习和掌握Flink低阶处理函数CoProcessFunction的用法;
  • 整个系列的开篇先介绍CoProcessFunction,然后迅速进入实战,了解CoProcessFunction的基本功能;
  • 下一篇会结合状态,让双流元素的处理彼此保持关系;
  • 终篇的实战会加入定时器功能,确保同一个key的数据在双流场景下能够及时处理;

版本信息

  1. 开发环境操作系统:MacBook Pro 13寸, macOS Catalina 10.15.3
  2. 开发工具:IDEA ULTIMATE 2018.3
  3. JDK:1.8.0_211
  4. Maven:3.6.0
  5. Flink:1.9.2

关于CoProcessFunction

  • CoProcessFunction的作用是同时处理两个数据源的数据;
  • 试想在面对两个输入流时,如果这两个流的数据之间有业务关系,该如何编码实现呢,例如下图中的操作,同时监听99989999端口,将收到的输出分别处理后,再由同一个sink处理(打印):

在这里插入图片描述

  • Flink支持的方式是扩展CoProcessFunction来处理,为了更清楚认识,我们把KeyedProcessFunctionCoProcessFunction的类图摆在一起看,如下所示:

在这里插入图片描述

  • 从上图可见,CoProcessFunction和KeyedProcessFunction的继承关系一样,另外CoProcessFunction自身也很简单,在processElement1processElement2中分别处理两个上游流入的数据即可,并且也支持定时器设置;

本篇实战功能简介

本篇咱们要开发的应用,其功能非常简单,描述如下:

  1. 建两个数据源,数据分别来自本地99989999端口;
  2. 每个端口收到类似aaa,123这样的数据,转成Tuple2实例,f0是aaa,f1是123
  3. 在CoProcessFunction的实现类中,对每个数据源的数据都打日志,然后全部传到下游算子;
  4. 下游操作是打印,因此99989999端口收到的所有数据都会在控制台打印出来;
  5. 整个demo的功能如下图所示:

在这里插入图片描述

  • 接下来开始编码;

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本章的应用在flinkstudy文件夹下,如下图红框所示:

在这里插入图片描述

代码简介

  • 开发一个Map算子,将字符串转成Tuple2;
  • 再开发抽象类AbstractCoProcessFunctionExecutor,功能包括:flink启动、监听端口、调用算子处理数据、双流连接、将双流处理结果打印出来;
  • 从上面的描述可见,AbstractCoProcessFunctionExecutor做了很多事情,唯独没有实现双流连接后的具体业务逻辑,这些没有做的是留给子类来实现的,整个三部曲系列的重点都集中在AbstractCoProcessFunctionExecutor的子类上,把双流连接后的业务逻辑做好,如下图所示,红色为CoProcessFunction的业务代码,其他的都在抽象类中完成:

在这里插入图片描述

Map算子

  • 做一个map算子,用来将字符串aaa,123转成Tuple2实例,f0是aaa,f1是123
  • 算子名为WordCountMap.java
package com.bolingcavalry.coprocessfunction;

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.StringUtils;

public class WordCountMap implements MapFunction<String, Tuple2<String, Integer>> {
    @Override
    public Tuple2<String, Integer> map(String s) throws Exception {

        if(StringUtils.isNullOrWhitespaceOnly(s)) {
            System.out.println("invalid line");
            return null;
        }

        String[] array = s.split(",");

        if(null==array || array.length<2) {
            System.out.println("invalid line for array");
            return null;
        }

        return new Tuple2<>(array[0], Integer.valueOf(array[1]));
    }
}

抽象类

  • 抽象类AbstractCoProcessFunctionExecutor.java,源码如下,稍后会说明几个关键点:
package com.bolingcavalry.coprocessfunction;

import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.co.CoProcessFunction;

/**
 * @author will
 * @email zq2599@gmail.com
 * @date 2020-11-09 17:33
 * @description 串起整个逻辑的执行类,用于体验CoProcessFunction
 */
public abstract class AbstractCoProcessFunctionExecutor {

    /**
     * 返回CoProcessFunction的实例,这个方法留给子类实现
     * @return
     */
    protected abstract CoProcessFunction<
            Tuple2<String, Integer>,
            Tuple2<String, Integer>,
            Tuple2<String, Integer>> getCoProcessFunctionInstance();

    /**
     * 监听根据指定的端口,
     * 得到的数据先通过map转为Tuple2实例,
     * 给元素加入时间戳,
     * 再按f0字段分区,
     * 将分区后的KeyedStream返回
     * @param port
     * @return
     */
    protected KeyedStream<Tuple2<String, Integer>, Tuple> buildStreamFromSocket(StreamExecutionEnvironment env, int port) {
        return env
                // 监听端口
                .socketTextStream("localhost", port)
                // 得到的字符串"aaa,3"转成Tuple2实例,f0="aaa",f1=3
                .map(new WordCountMap())
                // 将单词作为key分区
                .keyBy(0);
    }

    /**
     * 如果子类有侧输出需要处理,请重写此方法,会在主流程执行完毕后被调用
     */
    protected void doSideOutput(SingleOutputStreamOperator<Tuple2<String, Integer>> mainDataStream) {
    }

    /**
     * 执行业务的方法
     * @throws Exception
     */
    public void execute() throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 并行度1
        env.setParallelism(1);

        // 监听9998端口的输入
        KeyedStream<Tuple2<String, Integer>, Tuple> stream1 = buildStreamFromSocket(env, 9998);

        // 监听9999端口的输入
        KeyedStream<Tuple2<String, Integer>, Tuple> stream2 = buildStreamFromSocket(env, 9999);

        SingleOutputStreamOperator<Tuple2<String, Integer>> mainDataStream = stream1
                // 两个流连接
                .connect(stream2)
                // 执行低阶处理函数,具体处理逻辑在子类中实现
                .process(getCoProcessFunctionInstance());

        // 将低阶处理函数输出的元素全部打印出来
        mainDataStream.print();

        // 侧输出相关逻辑,子类有侧输出需求时重写此方法
        doSideOutput(mainDataStream);

        // 执行
        env.execute("ProcessFunction demo : CoProcessFunction");
    }
}
  • 关键点之一:一共有两个数据源,每个源的处理逻辑都封装到buildStreamFromSocket方法中;
  • 关键点之二:stream1.connect(stream2)将两个流连接起来;
  • 关键点之三:process接收CoProcessFunction实例,合并后的流的处理逻辑就在这里面;
  • 关键点之四:getCoProcessFunctionInstance是抽象方法,返回CoProcessFunction实例,交给子类实现,所以CoProcessFunction中做什么事情完全由子类决定;
  • 关键点之五:doSideOutput方法中啥也没做,但是在主流程代码的末尾会被调用,如果子类有侧输出(SideOutput)的需求,重写此方法即可,此方法的入参是处理过的数据集,可以从这里取得侧输出;

子类,对连接后的双流进行操作

  • 本篇子类CollectEveryOne.java如下所示,逻辑很简单,将每个源的上游数据直接输出到下游算子:
package com.bolingcavalry.coprocessfunction;

import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.functions.co.CoProcessFunction;
import org.apache.flink.util.Collector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CollectEveryOne extends AbstractCoProcessFunctionExecutor {

    private static final Logger logger = LoggerFactory.getLogger(CollectEveryOne.class);

    @Override
    protected CoProcessFunction<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>> getCoProcessFunctionInstance() {
        return new CoProcessFunction<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>>() {

            @Override
            public void processElement1(Tuple2<String, Integer> value, Context ctx, Collector<Tuple2<String, Integer>> out) {
                logger.info("处理1号流的元素:{},", value);
                out.collect(value);
            }

            @Override
            public void processElement2(Tuple2<String, Integer> value, Context ctx, Collector<Tuple2<String, Integer>> out) {
                logger.info("处理2号流的元素:{}", value);
                out.collect(value);
            }
        };
    }

    public static void main(String[] args) throws Exception {
        new CollectEveryOne().execute();
    }
}
  • 上述代码中,CoProcessFunction后面的泛型定义很长:<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>> ,一共三个Tuple2,分别代表一号数据源输入、二号数据源输入、下游输出的类型;
  • 编码完成,运行起来试试;

验证

  • 分别开启本机的99989999端口,我这里是MacBook,执行nc -l 9998nc -l 9999
  • 启动Flink应用,如果您和我一样是Mac电脑,直接运行CollectEveryOne.main方法即可(如果是windows电脑,我这没试过,不过做成jar在线部署也是可以的);
  • 在监听9998和9999端口的控制台分别输入aaa,111bbb,222
  • 以下是flink控制台输出的内容,可见processElement1和processElement2方法的日志代码已经执行,并且print方法作为最下游,将两个数据源的数据都打印出来了,符合预期:
12:45:38,774 INFO CollectEveryOne - 处理1号流的元素:(aaa,111),
(aaa,111)
12:45:43,816 INFO CollectEveryOne - 处理2号流的元素:(bbb,222)
(bbb,222)
  • 至此,咱们的第一个双流处理低阶函数就完成了,对CoProcessFunction也有了最基本的认识,当然CoProcessFunction的作用远不及此,下一篇咱们借助状态让processElement1processElement2分别对方处理过的状态,让每个元素的处理都和另一个流关联,不再孤立;

欢迎关注阿里云开发者社区博客:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...
相关实践学习
基于Hologres+Flink搭建GitHub实时数据大屏
通过使用Flink、Hologres构建实时数仓,并通过Hologres对接BI分析工具(以DataV为例),实现海量数据实时分析.
实时计算 Flink 实战课程
如何使用实时计算 Flink 搞定数据处理难题?实时计算 Flink 极客训练营产品、技术专家齐上阵,从开源 Flink功能介绍到实时计算 Flink 优势详解,现场实操,5天即可上手! 欢迎开通实时计算 Flink 版: https://cn.aliyun.com/product/bigdata/sc Flink Forward Asia 介绍: Flink Forward 是由 Apache 官方授权,Apache Flink Community China 支持的会议,通过参会不仅可以了解到 Flink 社区的最新动态和发展计划,还可以了解到国内外一线大厂围绕 Flink 生态的生产实践经验,是 Flink 开发者和使用者不可错过的盛会。 去年经过品牌升级后的 Flink Forward Asia 吸引了超过2000人线下参与,一举成为国内最大的 Apache 顶级项目会议。结合2020年的特殊情况,Flink Forward Asia 2020 将在12月26日以线上峰会的形式与大家见面。
相关文章
|
9月前
|
算法 测试技术
EvalPlanner:基于“计划-执行”双阶段的大语言模型评估框架
EvalPlanner是一种创新的大语言模型(LLM)评估算法,采用计划-执行双阶段范式,生成无约束的评估计划并执行,从而提升评估的系统性和可靠性。该系统包含评估计划、计划执行模块和最终判决三个核心组件,通过自训练循环优化计划和执行过程。EvalPlanner在多个基准测试中表现出色,特别是在数据效率和泛化能力方面,为构建高效、稳健的LLM评估模型提供了新方向。
412 13
EvalPlanner:基于“计划-执行”双阶段的大语言模型评估框架
|
编解码 Android开发
Android native层实现MediaCodec编码H264/HEVC
Android平台在上层实现mediacodec的编码,资料泛滥,已经不再是难事,今天给大家介绍下,如何在Android native层实现MediaCodec编码H264/HEVC,网上千篇一律的接口说明,这里不再赘述,本文主要介绍下,一些需要注意的点,权当抛砖引玉,相关设计界面如下:
477 0
|
6月前
|
负载均衡 前端开发 JavaScript
LVS-DR模式、keepalived、Nginx与Tomcat合作,打造动静分离,高效负载均衡与高可用性
为了采用这样的架构,你需要对LVS-DR、Keepalived、Nginx与Tomcat有一定的理解和掌握,同时也需要投入一些时间去研究和配置,但是一旦你把它运行起来,你将会发现,这一切都是值得的。
248 11
|
11月前
|
人工智能 小程序 API
【一步步开发AI运动小程序】十二、自定义一个运动分析器,实现计时计数01
随着AI技术的发展,AI运动APP如雨后春笋般涌现,如“乐动力”、“天天跳绳”等,推动了云上运动会、线上健身等热潮。本文将指导你从零开始开发一个AI运动小程序,利用“云智AI运动识别小程序插件”,介绍运动识别原理、计量方式及运动分析器基类的使用,帮助你在小程序中实现运动计时和计数功能。下篇将继续探讨运动姿态检测规则的编写。
|
存储 运维 监控
阿里云实时计算Flink版的评测
阿里云实时计算Flink版的评测
234 15
|
小程序 安全 数据安全/隐私保护
微信小程序项目实例——密码管理器
微信小程序项目实例——密码管理器
微信小程序项目实例——密码管理器
【Matlab 2019b】Matlab在figure中如何把横坐标或者纵坐标单位转换为10的几次方
本文提供了在Matlab中如何改变图形坐标轴单位的方法,举例说明了如何将横轴刻度标签设置为特定的年份,并调整刻度取值以匹配自变量的变化。
2088 1
|
Java 关系型数据库 测试技术
基于SpringBoot+Vue的商业辅助决策系统的设计与实现(源码+部署说明+演示视频+源码介绍)(1)
基于SpringBoot+Vue的商业辅助决策系统的设计与实现(源码+部署说明+演示视频+源码介绍)
163 1
|
算法 安全 C语言
【C 言专栏】C 语言中的多线程编程
【5月更文挑战第5天】本文探讨了C语言中的多线程编程,包括多线程概念、通过POSIX线程库和Windows线程库的实现方式、基本步骤(创建、执行、同步、销毁线程)、线程同步机制(互斥锁、条件变量、信号量)以及优点(提高性能、增强并发处理、改善用户体验)。同时,文章指出多线程编程面临的挑战如线程安全、死锁和资源竞争,并提及内存管理问题。通过案例分析和展望未来趋势,强调了掌握多线程编程在提升程序效率和应对复杂任务中的重要性。
600 0
【C 言专栏】C 语言中的多线程编程