欢迎访问我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

欢迎访问我的GitHub

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

Flink处理函数实战系列链接

  1. 深入了解ProcessFunction的状态操作(Flink-1.10)
  2. ProcessFunction
  3. KeyedProcessFunction类
  4. ProcessAllWindowFunction(窗口处理)
  5. CoProcessFunction(双流处理)

本篇概览

  • 本文是《Flink处理函数实战》系列的第五篇,学习内容是如何同时处理两个数据源的数据;
  • 试想在面对两个输入流时,如果这两个流的数据之间有业务关系,该如何编码实现呢,例如下图中的操作,同时监听9998和9999端口,将收到的输出分别处理后,再由同一个sink处理(打印):

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

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

编码实战

接下来咱们开发一个应用来体验CoProcessFunction,功能非常简单,描述如下:

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

  • 接下来编码实现上述功能;

源码下载

如果您不想写代码,整个系列的源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):

名称 链接 备注
项目主页 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算子

  1. 做一个map算子,用来将字符串aaa,123转成Tuple2实例,f0是aaa,f1是123;
  2. 算子名为WordCountMap.java:
  1. package com.bolingcavalry.coprocessfunction;
  2. import org.apache.flink.api.common.functions.MapFunction;
  3. import org.apache.flink.api.java.tuple.Tuple2;
  4. import org.apache.flink.util.StringUtils;
  5. public class WordCountMap implements MapFunction<String, Tuple2<String, Integer>> {
  6. @Override
  7. public Tuple2<String, Integer> map(String s) throws Exception {
  8. if(StringUtils.isNullOrWhitespaceOnly(s)) {
  9. System.out.println("invalid line");
  10. return null;
  11. }
  12. String[] array = s.split(",");
  13. if(null==array || array.length<2) {
  14. System.out.println("invalid line for array");
  15. return null;
  16. }
  17. return new Tuple2<>(array[0], Integer.valueOf(array[1]));
  18. }
  19. }

便于扩展的抽象类

  • 开发一个抽象类,将前面图中提到的监听端口、map处理、keyby处理、打印都做到这个抽象类中,但是CoProcessFunction的逻辑却不放在这里,而是交给子类来实现,这样如果我们想进一步实践和扩展CoProcessFunction的能力,只要在子类中专注做好CoProcessFunction相关开发即可,如下图,红色部分交给子类实现,其余的都是抽象类完成的:

  • 抽象类AbstractCoProcessFunctionExecutor.java,源码如下,稍后会说明几个关键点:
  1. package com.bolingcavalry.coprocessfunction;
  2. import org.apache.flink.api.java.tuple.Tuple;
  3. import org.apache.flink.api.java.tuple.Tuple2;
  4. import org.apache.flink.streaming.api.datastream.KeyedStream;
  5. import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
  6. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  7. import org.apache.flink.streaming.api.functions.co.CoProcessFunction;
  8. /**
  9. * @author will
  10. * @email zq2599@gmail.com
  11. * @date 2020-11-09 17:33
  12. * @description 串起整个逻辑的执行类,用于体验CoProcessFunction
  13. */
  14. public abstract class AbstractCoProcessFunctionExecutor {
  15. /**
  16. * 返回CoProcessFunction的实例,这个方法留给子类实现
  17. * @return
  18. */
  19. protected abstract CoProcessFunction<
  20. Tuple2<String, Integer>,
  21. Tuple2<String, Integer>,
  22. Tuple2<String, Integer>> getCoProcessFunctionInstance();
  23. /**
  24. * 监听根据指定的端口,
  25. * 得到的数据先通过map转为Tuple2实例,
  26. * 给元素加入时间戳,
  27. * 再按f0字段分区,
  28. * 将分区后的KeyedStream返回
  29. * @param port
  30. * @return
  31. */
  32. protected KeyedStream<Tuple2<String, Integer>, Tuple> buildStreamFromSocket(StreamExecutionEnvironment env, int port) {
  33. return env
  34. // 监听端口
  35. .socketTextStream("localhost", port)
  36. // 得到的字符串"aaa,3"转成Tuple2实例,f0="aaa",f1=3
  37. .map(new WordCountMap())
  38. // 将单词作为key分区
  39. .keyBy(0);
  40. }
  41. /**
  42. * 如果子类有侧输出需要处理,请重写此方法,会在主流程执行完毕后被调用
  43. */
  44. protected void doSideOutput(SingleOutputStreamOperator<Tuple2<String, Integer>> mainDataStream) {
  45. }
  46. /**
  47. * 执行业务的方法
  48. * @throws Exception
  49. */
  50. public void execute() throws Exception {
  51. final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  52. // 并行度1
  53. env.setParallelism(1);
  54. // 监听9998端口的输入
  55. KeyedStream<Tuple2<String, Integer>, Tuple> stream1 = buildStreamFromSocket(env, 9998);
  56. // 监听9999端口的输入
  57. KeyedStream<Tuple2<String, Integer>, Tuple> stream2 = buildStreamFromSocket(env, 9999);
  58. SingleOutputStreamOperator<Tuple2<String, Integer>> mainDataStream = stream1
  59. // 两个流连接
  60. .connect(stream2)
  61. // 执行低阶处理函数,具体处理逻辑在子类中实现
  62. .process(getCoProcessFunctionInstance());
  63. // 将低阶处理函数输出的元素全部打印出来
  64. mainDataStream.print();
  65. // 侧输出相关逻辑,子类有侧输出需求时重写此方法
  66. doSideOutput(mainDataStream);
  67. // 执行
  68. env.execute("ProcessFunction demo : CoProcessFunction");
  69. }
  70. }
  • 关键点之一:一共有两个数据源,每个源的处理逻辑都封装到buildStreamFromSocket方法中;
  • 关键点之二:stream1.connect(stream2)将两个流连接起来;
  • 关键点之三:process接收CoProcessFunction实例,合并后的流的处理逻辑就在这里面;
  • 关键点之四:getCoProcessFunctionInstance是抽象方法,返回CoProcessFunction实例,交给子类实现,所以CoProcessFunction中做什么事情完全由子类决定;
  • 关键点之五:doSideOutput方法中啥也没做,但是在主流程代码的末尾会被调用,如果子类有侧输出(SideOutput)的需求,重写此方法即可,此方法的入参是处理过的数据集,可以从这里取得侧输出;

子类决定CoProcessFunction的功能

  1. 子类CollectEveryOne.java如下所示,逻辑很简单,将每个源的上游数据直接输出到下游算子:
  1. package com.bolingcavalry.coprocessfunction;
  2. import org.apache.flink.api.java.tuple.Tuple2;
  3. import org.apache.flink.streaming.api.functions.co.CoProcessFunction;
  4. import org.apache.flink.util.Collector;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. public class CollectEveryOne extends AbstractCoProcessFunctionExecutor {
  8. private static final Logger logger = LoggerFactory.getLogger(CollectEveryOne.class);
  9. @Override
  10. protected CoProcessFunction<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>> getCoProcessFunctionInstance() {
  11. return new CoProcessFunction<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>>() {
  12. @Override
  13. public void processElement1(Tuple2<String, Integer> value, Context ctx, Collector<Tuple2<String, Integer>> out) {
  14. logger.info("处理1号流的元素:{},", value);
  15. out.collect(value);
  16. }
  17. @Override
  18. public void processElement2(Tuple2<String, Integer> value, Context ctx, Collector<Tuple2<String, Integer>> out) {
  19. logger.info("处理2号流的元素:{}", value);
  20. out.collect(value);
  21. }
  22. };
  23. }
  24. public static void main(String[] args) throws Exception {
  25. new CollectEveryOne().execute();
  26. }
  27. }
  1. 上述代码中,CoProcessFunction后面的泛型定义很长:<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>> ,一共三个Tuple2,分别代表一号数据源输入、二号数据源输入、下游输出的类型;

验证

  1. 分别开启本机的9998和9999端口,我这里是MacBook,执行nc -l 9998和nc -l 9999
  2. 启动Flink应用,如果您和我一样是Mac电脑,直接运行CollectEveryOne.main方法即可(如果是windows电脑,我这没试过,不过做成jar在线部署也是可以的);
  3. 在监听9998和9999端口的控制台分别输入aaa,111和bbb,222
  4. 以下是flink控制台输出的内容,可见processElement1和processElement1方法的日志代码已经执行,并且print方法作为最下游,将两个数据源的数据都打印出来了,符合预期:
  1. 12:45:38,774 INFO CollectEveryOne - 处理1号流的元素:(aaa,111),
  2. (aaa,111)
  3. 12:45:43,816 INFO CollectEveryOne - 处理2号流的元素:(bbb,222)
  4. (bbb,222)

更多

  • 以上就是最基本的CoProcessFunction用法,其实CoProcessFunction的使用远不及此,结合状态,可以processElement1获得更多二号流的元素信息,另外还可以结合定时器来约束两个流协同处理的等待时间,您可以参考前面文章中的状态和定时器来自行尝试;

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...

https://github.com/zq2599/blog_demos

Flink处理函数实战之五:CoProcessFunction(双流处理)的更多相关文章

  1. Flink处理函数实战之一:深入了解ProcessFunction的状态(Flink-1.10)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. Flink处理函数实战之二:ProcessFunction类

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. Flink处理函数实战之三:KeyedProcessFunction类

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. Flink处理函数实战之四:窗口处理

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. [Java聊天室server]实战之五 读写循环(服务端)

    前言 学习不论什么一个稍有难度的技术,要对其有充分理性的分析,之后果断做出决定---->也就是人们常说的"多谋善断":本系列尽管涉及的是socket相关的知识,但学习之前,更 ...

  6. Python基础入门-函数实战登录功能

    ''' 函数实战: .加法计算器 .过滤器 .登录功能实战 ''' def add(a,b): return a+b def login_order(): return 'asdfasdfdasfad ...

  7. Mysql 开窗函数实战

    Mysql 开窗函数实战 Mysql 开窗函数在Mysql8.0+ 中可以得以使用,实在且好用. row number() over rank() over dense rank() ntile() ...

  8. Flink的sink实战之一:初探

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. Flink的sink实战之二:kafka

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

随机推荐

  1. 【转】Setting up SDL Extension Libraries on Code::Blocks 12.11

    FROM: http://lazyfoo.net/tutorials/SDL/06_extension_libraries_and_loading_other_image_formats/window ...

  2. js-同步和异步

    js异步 学习js开发,无论是前端开发还是node.js,都避免不了要接触异步编程这个问题,就和其它大多数以多线程同步为主的编程语言不同,js的主要设计是单线程异步模型.正因为js天生的与众不同,才使 ...

  3. 4G DTU为什么要具有透传的功能

    4G DTU为什么要透传 透传的目的就是为了在数据传输的过程中不对数据做任何出来,实现发送方和接收方的数据完全一样,长度和内容完全没有变化.它主要是使用在智能设备之间的远程串口数据传输,是一种和传输方 ...

  4. AWS SDK 使用说明

    AWS 的Python SDK包名为 boto3, 可以使用命令pip install boto3安装使用 BOTO3中的基本概念 boto3提供了两个级别的接口来访问AWS服务:High Level ...

  5. MarkdownPad 2中编辑

    一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 #######七级标题 ########八级标题 #!/bin/bash declare -i evenSum=0 declare -i i ...

  6. 12 RESTful架构(SOAP,RPC)

    12 RESTful架构(SOAP,RPC) 推荐: http://www.ruanyifeng.com/blog/2011/09/restful.html

  7. 编程,向内存0:200~0:23F依次传送数据0~63(3FH),程序中只能使用9条指令,9条指令包括 mov ax,4c00h 和 int 21h

    assume cs:code code segment mov bx,020H mov ds,bx mov bx,0 mov cx,63 s:mov [bx],bx inc bx loop s mov ...

  8. Spider--补充--Requests--session&cookie

    # session 与 cookie # 可能大家对session已经比较熟悉了,也大概了解了session的机制和原理,但是我们在做爬虫时如何会运用到session呢,就是接下来要讲到的会话保持. ...

  9. Socket accept 简要分析

    accept 用于从指定套接字的连接队列中取出第一个连接,并返回一个新的套接字用于与客户端进行通信,示例代码如下 #include <sys/types.h> /* See NOTES * ...

  10. linux Netfilterr中扩展match target

    Match: netfilter定义了一个通用的match数据结构struct xt_match /* 每个struct xt_match代表一个扩展match,netfilter中各个扩展match ...