Flink 侧流输出源码示例解析


Posted in Servers onSeptember 23, 2022

Flink 侧流输出源码解析

Flink 的 side output 为我们提供了侧流(分流)输出的功能,根据条件可以把一条流分为多个不同的流,之后做不同的处理逻辑,下面就来看下侧流输出相关的源码。

先来看下面的一个 Demo,一个流被分成了 3 个流,一个主流,两个侧流输出。

SingleOutputStreamOperator<JasonLeePOJO> process =
        kafka_source1.process(
                new ProcessFunction<JasonLeePOJO, JasonLeePOJO>() {
                    @Override
                    public void processElement(
                            JasonLeePOJO value,
                            ProcessFunction<JasonLeePOJO, JasonLeePOJO>.Context ctx,
                            Collector<JasonLeePOJO> out)
                            throws Exception {
                        // 这个是主流输出
                        if (value.getName().equals("flink")) {
                            out.collect(value);
                        // 下面两个是测流输出
                        } else if (value.getName().equals("spark")) {
                            ctx.output(test, value);
                        // 测流
                        } else if (value.getName().equals("hadoop")) {
                            ctx.output(test1, value);
                        }
                    }
                });

为了更加清楚的查看每一个算子,我禁用了 operator chain,任务的 DAG 图如下所示:

Flink 侧流输出源码示例解析

这样就比较清晰了,很明显从 process 算子开始,1 个数据流分为了 3 个数据流,当然,在默认情况下没有禁止

operator chain 所有的算子都是 chain 在一起的。

源码解析

我们先来看第一个主流输出也就是 out.collect(value) 的源码,这里的 out 实际上是 TimestampedCollector 对象。

TimestampedCollector#collect

@Override
public void collect(T record) {
    output.collect(reuse.replace(record));
}

在 collect 方法中持有一个 output 对象,用来输出数据,在这里实际上是一个 CountingOutput 它是一个包装了 Output 的对象,主要用于更新发送数据的 metric,并输出数据。

CountingOutput#collect

@Override
public void collect(StreamRecord<OUT> record) {
    numRecordsOut.inc();
    output.collect(record);
}

在 CountingOutput 中也持有一个 output 对象,但是这里的 output 是 BroadcastingOutputCollector 对象,从名字就可以看出它是往下游广播数据的,这里就有一个疑问?把数据广播到下游,那岂不是下游的每个数据流都有这条数据吗?这样的话是怎么实现分流的呢?带着这个疑问,我们来看 BroadcastingOutputCollector 的 collect 方法是怎么实现的。

BroadcastingOutputCollector#collect

@Override
public void collect(StreamRecord<T> record) {
    // 这里的 outputs 数组有三个 output 分别对应上面的三个输出流
    for (Output<StreamRecord<T>> output : outputs) {
        output.collect(record);
    }
}

在 BroadcastingOutputCollector 对象里也持有一个 output 对象,其实他们都实现了 Output 接口,用来往下游发送数据,这里的 outputs 是一个 Output 数组,代表了下游的所有 Output,因为上面有三个输出流,所以数组里面就包含了 3 个 Output 对象。

循环的调用 output 的 collect 方法往下游发送数据,因为我打断了 operator chain,所以 process 算子和下游的 Print 算子不在同一个 operatorChain 内,那么上下游算子之间数据传输用的就是 RecordWriterOutput,否则用的是 CopyingChainingOutput 或者 ChainingOutput,具体使用的是哪个 Output 这里就不多介绍了,后面有时间的话会单独介绍。

RecordWriterOutput#collect

@Override
public void collect(StreamRecord<OUT> record) {
    // 主流是没有 outputTag 的,只有测流有 outputTag
    if (this.outputTag != null) {
        // we are not responsible for emitting to the main output.
        return;
    }

    pushToRecordWriter(record);
}

接着来看 RecordWriterOutput 的 collect 方法,在 collect 方法里面会先判断 outputTag 是否为空,如果不为空不做任何处理,直接返回,否则就把数据推送到下游算子,只有侧流输出才需要定义 outputTag,主流(正常流)是没有 outputTag 的,所以这里会走 pushToRecordWriter 方法把数据写入到下游,也就是说虽然会以广播的形式把数据广播到所有下游,但其实另外两个侧流是直接返回的,只有主流才会把数据推送到下游,这也就解释了上面的疑问。

然后再来看第二个侧流输出 ctx.output(test, value) 的源码,这里的 ctx 实际上是 ProcessOperator#ContextImpl 对象。

ProcessOperator#ContextImpl#output

@Override
public <X> void output(OutputTag<X> outputTag, X value) {
    if (outputTag == null) {
        throw new IllegalArgumentException("OutputTag must not be null.");
    }
    output.collect(outputTag, new StreamRecord<>(value, element.getTimestamp()));
}

如果 outputTag 是空,直接抛出异常,因为这个是侧流,所以必须要定义 OutputTag。这里的 output 实际上是父类 AbstractStreamOperator 所持有的变量,如果 outputTag 不为空,就调用 output 的 collect 方法把数据发送到下游,这里的 output 和上面的一样是 CountingOutput 但是 collect 方法是另外一个重载的方法。

CountingOutput#collect

@Override
public <X> void collect(OutputTag<X> outputTag, StreamRecord<X> record) {
    numRecordsOut.inc();
    output.collect(outputTag, record);
}

可以发现,这个 collect 方法比上面那个多了一个 OutputTag 参数,也就是使用侧流输出的时候定义的 OutputTag 对象,然后调用 output 的 collect 方法发送数据,这个也和上面的一样,同样是 BroadcastingOutputCollector 对象的另外一个重载方法,多了一个 OutputTag 参数。

BroadcastingOutputCollector#collect

@Override
public <X> void collect(OutputTag<X> outputTag, StreamRecord<X> record) {
    for (Output<StreamRecord<T>> output : outputs) {
        output.collect(outputTag, record);
    }
}

这里的逻辑和上面是一样的,同样的循环调用 collect 方法发送数据。

RecordWriterOutput#collect

@Override
public <X> void collect(OutputTag<X> outputTag, StreamRecord<X> record) {
    // 先要判断两个 OutputTag 是否一样
    if (OutputTag.isResponsibleFor(this.outputTag, outputTag)) {
        pushToRecordWriter(record);
    }
}

在这个 collect 方法中会先判断传入的 OutputTag 对象和成员变量 this.outputTag 是不是相等,如果是的话,就发送数据,否则不做任何处理,所以这里每次只会选择一个下游侧流输出数据,这样就实现了所谓的分流。

OutputTag#isResponsibleFor

public static boolean isResponsibleFor(
        @Nullable OutputTag<?> owner, @Nonnull OutputTag<?> other) {
    return other.equals(owner);
}

可以看到在 isResponsibleFor 方法内是直接调用 OutputTag 的 equals 方法判断两个对象是否相等的。

第三个侧流 test1 ctx.output(test1, value) 和第二个侧流 test 是完全一样的情况,这里就不在看代码了。

上面是完成了分流操作,那怎么获取到分流后结果呢(数据流)?我们可以通过 getSideOutput 方法获取。

DataStream<JasonLeePOJO> sideOutput = process.getSideOutput(test);
DataStream<JasonLeePOJO> sideOutput1 = process.getSideOutput(test1);

getSideOutput 源码

public <X> DataStream<X> getSideOutput(OutputTag<X> sideOutputTag) {
    sideOutputTag = clean(requireNonNull(sideOutputTag));

    // make a defensive copy
    sideOutputTag = new OutputTag<X>(sideOutputTag.getId(), sideOutputTag.getTypeInfo());

    TypeInformation<?> type = requestedSideOutputs.get(sideOutputTag);
    if (type != null && !type.equals(sideOutputTag.getTypeInfo())) {
        throw new UnsupportedOperationException(
                "A side output with a matching id was "
                        + "already requested with a different type. This is not allowed, side output "
                        + "ids need to be unique.");
    }

    requestedSideOutputs.put(sideOutputTag, sideOutputTag.getTypeInfo());

    SideOutputTransformation<X> sideOutputTransformation =
            new SideOutputTransformation<>(this.getTransformation(), sideOutputTag);
    return new DataStream<>(this.getExecutionEnvironment(), sideOutputTransformation);
}

getSideOutput 方法里先是构建了一个 SideOutputTransformation 对象,然后又构建了 DataStream 对象,这样我们就可以基于分流后的 DataStream 做不同的处理逻辑了,从而实现了把一个 DataStream 分流成多个 DataStream 功能。

总结

通过对侧流输出的源码进行解析,在分流的时候,数据是通过广播的方式发送到下游算子的,对于主流的数据来说,只有 OutputTag 为空的才会处理,侧流因为 OutputTag 不为空,所以直接返回,不做任何处理,那对于侧流的数据来说,是通过判断两个 OutputTag 是否相等,所以每次只会把数据发送到下游对应的那一个侧流上去,这样即可实现分流逻辑。

以上就是Flink 侧流输出源码示例解析的详细内容,更多关于Flink 侧流输出的资料请关注三水点靠木其它相关文章!

Servers 相关文章推荐
Nginx设置日志打印post请求参数的方法
Mar 31 Servers
Nginx 过滤静态资源文件的访问日志的实现
Mar 31 Servers
Nginx+SpringBoot实现负载均衡的示例
Mar 31 Servers
Nginx URL重写rewrite机制原理及使用实例
Apr 01 Servers
Nginx如何配置Http、Https、WS、WSS的方法步骤
May 11 Servers
Apache POI的基本使用详解
Nov 07 Servers
使用 Docker Compose 构建复杂的多容器App
Apr 30 Servers
shell进度条追踪指令执行时间的场景分析
Jun 16 Servers
Linux中各个目录的作用与内容
Jun 28 Servers
Apache Kafka 分区重分配的实现原理解析
Jul 15 Servers
Nginx报错104:Connection reset by peer问题的解决及分析
Jul 23 Servers
Flink 侧流输出源码示例解析
Sep 23 Servers
修改Nginx配置返回指定content-type的方法
Sep 23 #Servers
Nginx如何配置多个服务域名解析共用80端口详解
Sep 23 #Servers
VMware虚拟机安装 Windows Server 2022的详细图文教程
zabbix如何添加监控主机和自定义监控项
Aug 14 #Servers
教你使用RustDesk 搭建一个自己的远程桌面中继服务器
Aug 14 #Servers
nginx配置指令之server_name的具体使用
Aug 14 #Servers
ssh服务器拒绝了密码 请再试一次已解决(亲测有效)
You might like
英雄试炼之肉山谷—引领RPG新潮流
2020/04/20 DOTA
国内咖啡文化
2021/03/03 咖啡文化
PHP入门学习笔记之一
2010/10/12 PHP
PHP代码保护--Zend Guard的使用详解
2013/06/03 PHP
ThinkPHP实现将本地文件打包成zip下载
2014/06/26 PHP
windows7下安装php的php-ssh2扩展教程
2014/07/04 PHP
thinkPHP简单遍历数组方法分析
2016/05/16 PHP
ThinkPHP5.0框架验证码功能实现方法【基于第三方扩展包】
2019/03/11 PHP
php微信扫码支付 php公众号支付
2019/03/24 PHP
goto语法在PHP中的使用教程
2020/09/17 PHP
轻量级 JS ToolTip提示效果
2010/07/20 Javascript
js下判断 iframe 是否加载完成的完美方法
2010/10/26 Javascript
JS随即打乱数组实现代码
2012/12/03 Javascript
js控制table合并具体实现
2014/02/20 Javascript
JS遍历Json字符串中键值对先转成JSON对象再遍历
2014/08/15 Javascript
JQuery中$.each 和$(selector).each()的区别详解
2015/03/13 Javascript
深入解析JavaScript中函数的Currying柯里化
2016/03/19 Javascript
JavaScript实现Base64编码转换
2016/04/23 Javascript
JavaScript编程中实现对象封装特性的实例讲解
2016/06/24 Javascript
JS中with的替代方法与String中的正则方法详解
2016/12/23 Javascript
完美实现js选项卡切换效果(二)
2017/03/08 Javascript
Vue.js 实现数据展示全部和收起功能
2018/09/05 Javascript
使用Javascript简单计算器
2018/11/17 Javascript
vue多次循环操作示例
2019/02/08 Javascript
JavaScript中的一些实用小技巧总结
2019/04/07 Javascript
浅谈bootstrap layer.open中end的使用方法
2019/09/12 Javascript
Python os模块学习笔记
2015/06/21 Python
CentOS 7下安装Python 3.5并与Python2.7兼容并存详解
2017/07/07 Python
优秀大学生的自我评价
2014/01/16 职场文书
新护士岗前培训制度
2014/02/02 职场文书
工地安全标语
2014/06/07 职场文书
2014年小学少先队工作总结
2014/12/18 职场文书
实习协议书
2015/01/27 职场文书
2015年度优秀员工自荐书
2015/03/06 职场文书
教师学习中国梦心得体会
2016/01/05 职场文书
导游词之重庆渣滓洞
2020/01/08 职场文书