Flink流处理之迭代案例
当前Flink将迭代的重心集中在批处理上,之前我们谈及了批量迭代和增量迭代主要是针对批处理(DataSet)API而言的,而且Flink为批处理中的迭代提供了针对性的优化。
可是对于流处理(DataStream),Flink相同提供了对迭代的支持。这一节我们主要来分析流处理中的迭代,我们将会看到流处理中的迭代相较于批处理有类似之处。但差异也是十分之明显。
可迭代的流处理程序同意定义“步函数”(step function)并将其内嵌到一个可迭代的流(IterativeStream)中。由于一个流处理程序可能永不终止,因此不同于批处理中的迭代机制,流处理中无法设置迭代的最大次数。取而代之的是,你能够指定等待反馈输入的最大时间间隔(假设超过该时间间隔没有反馈元素到来。那么该迭代将会终止)。通过应用split或filter转换,你能够指定流的哪一部分用于反馈给迭代头,哪一部分分发给下游。这里我们以filter作为演示样例来展示可迭代的流处理程序的API使用模式。
首先。基于输入流构建IterativeStream。这是一个迭代的起始。通常称之为迭代头:
IterativeStream<Integer> iteration = inputStream.iterate();
接着。我们指定一系列的转换操作用于表述在迭代过程中运行的逻辑(这里简单以map转换作为演示样例)。map API所接受的UDF就是我们上文所说的步函数:
DataStream<Integer> iteratedStream = iteration.map(/* this is executed many times */);
然后。作为迭代我们肯定须要有数据反馈给迭代头进行反复计算,所以我们从迭代过的流中过滤出符合条件的元素组成的部分流,我们称之为反馈流:
DataStream<Integer> feedbackStream = iteratedStream.filter(/* one part of the stream */);
将反馈流反馈给迭代头就意味着一个迭代的完整逻辑的完毕,那么它就能够“关闭”这个闭合的“环”了。通过调用IterativeStream的closeWith这一实例方法能够关闭一个迭代(也可表述为定义了迭代尾)。传递给closeWith的数据流将会反馈给迭代头:
iteration.closeWith(feedbackStream);
另外,一个惯用的模式是过滤出须要继续向前分发的部分流,这个过滤转换事实上定义的是“终止迭代”的逻辑条件,符合条件的元素将被分发给下游而不用于进行下一次迭代:
DataStream<Integer> output = iteratedStream.filter(/* some other part of the stream */);
跟分析批处理中的迭代一样,我们仍然以解决实际问题的案例作为切入点来看看流处理中的迭代跟批处理中的迭代有何不同。
首先描写叙述一下须要解决的问题:产生一个由一系列二元组(两个字段都是在一个区间内产生的正整数来作为斐波那契数列的两个初始值)构成的数据流。然后对该数据流中的二元组不断地迭代使其产生斐波那契数列,直到某次产生的值大于给定的阈值,则停止迭代并输出迭代次数。
该案例參考自Flink随源代码公布的迭代演示样例,此案例问题规模较小而且能够说明问题。
但它演示样例代码中的一系列变量稍显混乱,为了增强程序的表述性,笔者会对其稍作调整。
这个案例假设拆分到对单个元素(二元组)的角度来看。其运行步骤例如以下图所看到的:
n表示迭代次数。在最初的map转换中初始化为0。m是判定迭代停止的阈值。
另外,T后面跟的是字段索引。比方T2表示取元组中位置为3的字段。
且注意随着迭代T在不断变化。
上面我们已经对问题的核心过程进行了分析。接下来我们会分步解决问题的构建迭代的流处理程序。
首先,我们先通过source函数创建初始的流对象inputStream:
DataStream<Tuple2<Integer, Integer>> inputStream = env.addSource(new RandomFibonacciSource());
该source函数会生成二元组序列,二元组的两个字段值是随机生成的作为斐波那契数列的初始值:
private static class RandomFibonacciSource
implements SourceFunction<Tuple2<Integer, Integer>> {
private Random random = new Random();
private volatile boolean isRunning = true;
private int counter = 0;
public void run(SourceContext<Tuple2<Integer, Integer>> ctx) throws Exception {
while (isRunning && counter < MAX_RANDOM_VALUE) {
int first = random.nextInt(MAX_RANDOM_VALUE / 2 - 1) + 1;
int second = random.nextInt(MAX_RANDOM_VALUE / 2 -1) + 1;
if (first > second) continue;
ctx.collect(new Tuple2<Integer, Integer>(first, second));
counter++;
Thread.sleep(50);
}
}
public void cancel() {
isRunning = false;
}
}
为了对新计算的斐波那契数列中的值以及累加的迭代次数进行存储,我们须要将二元组数据流转换为五元组数据流,并据此创建迭代对象:
IterativeStream<Tuple5<Integer, Integer, Integer, Integer, Integer>> iterativeStream =
inputStream.map(new TupleTransformMapFunction()).iterate(5000);
注意上面代码段中iterate API的參数5000,不是指迭代5000次,而是等待反馈输入的最大时间间隔为5秒。
流被觉得是无界的。所以无法像批处理迭代那样指定最大迭代次数。但它同意指定一个最大等待间隔,假设在给定的时间间隔里没有元素到来。那么将会终止迭代。
元组转换的map函数实现:
private static class TupleTransformMapFunction extends RichMapFunction<Tuple2<Integer,
Integer>, Tuple5<Integer, Integer, Integer, Integer, Integer>> {
public Tuple5<Integer, Integer, Integer, Integer, Integer> map(
Tuple2<Integer, Integer> inputTuples) throws Exception {
return new Tuple5<Integer, Integer, Integer, Integer, Integer>(
inputTuples.f0,
inputTuples.f1,
inputTuples.f0,
inputTuples.f1,
0);
}
}
上面五元组中,当中索引为0。1这两个位置的元素,始终都是最初生成的两个元素不会变化,而后三个字段都会随着迭代而变化。
在迭代流iterativeStream创建完毕之后,我们将基于它运行斐波那契数列的步函数并产生斐波那契数列流fibonacciStream:
DataStream<Tuple5<Integer, Integer, Integer, Integer, Integer>> fibonacciStream =
iterativeStream.map(new FibonacciCalcStepFunction());
这里的fibonacciStream仅仅是一个代称,当中的数据并非真正的斐波那契数列,事实上就是上面那个五元组。
当中用于计算斐波那契数列的步函数实现例如以下:
private static class FibonacciCalcStepFunction extends
RichMapFunction<Tuple5<Integer, Integer, Integer, Integer, Integer>,
Tuple5<Integer, Integer, Integer, Integer, Integer>> {
public Tuple5<Integer, Integer, Integer, Integer, Integer> map(
Tuple5<Integer, Integer, Integer, Integer, Integer> inputTuple) throws Exception {
return new Tuple5<Integer, Integer, Integer, Integer, Integer>(
inputTuple.f0,
inputTuple.f1,
inputTuple.f3,
inputTuple.f2 + inputTuple.f3,
++inputTuple.f4);
}
}
正如上文所述。后三个字段会产生变化。在计算之前,数列最后一个元素会被保留。也就是f3相应的元素,然后通过f2元素加上f3元素会产生最新值并更新f3元素。而f4则会累加。
随着迭代次数添加,不是整个数列都会被保留。仅仅有最初的两个元素和最新的两个元素会被保留,这里也不是必需保留整个数列,由于我们不须要完整的数列。我们仅仅须要对最新的两个元素进行推断就可以。
上文我们对每一个元素计算斐波那契数列的新值并产生了fibonacciStream,可是我们须要对最新的两个值进行推断。看它们是否超过了指定的阈值。超过了阈值的元组将会被输出,而没有超过的则会再次參与迭代。因此这将产生两个不同的分支。我们也为此构建了分支流:
SplitStream<Tuple5<Integer, Integer, Integer, Integer, Integer>> branchedStream =
fibonacciStream.split(new FibonacciOverflowSelector());
而对是否超过阈值的元组进行推断并分离的实现例如以下:
private static class FibonacciOverflowSelector implements OutputSelector<
Tuple5<Integer, Integer, Integer, Integer, Integer>> {
public Iterable<String> select(
Tuple5<Integer, Integer, Integer, Integer, Integer> inputTuple) {
if (inputTuple.f2 < OVERFLOW_THRESHOLD && inputTuple.f3 < OVERFLOW_THRESHOLD) {
return Collections.singleton(ITERATE_FLAG);
}
return Collections.singleton(OUTPUT_FLAG);
}
}
在筛选方法select中,我们对不同的分支以不同的常量标识符进行标识:ITERATE_FLAG(还要继续迭代)和OUTPUT_FLAG(直接输出)。
产生了分支流之后。我们就能够从中检出不同的流分支做迭代或者输出处理。
对须要再次迭代的,就通过迭代流的closeWith方法反馈给迭代头:
iterativeStream.closeWith(branchedStream.select(ITERATE_FLAG));
而对于不须要的迭代就直接让其流向下游处理,这里我们仅仅是简单得将流“重构”了一下然后直接输出:
DataStream<Tuple3<Integer, Integer, Integer>> outputStream = branchedStream
.select(OUTPUT_FLAG).map(new BuildOutputTupleMapFunction());
outputStream.print();
所谓的重构就是将之前的五元组又一次缩减为三元组,实现例如以下:
private static class BuildOutputTupleMapFunction extends RichMapFunction<
Tuple5<Integer, Integer, Integer, Integer, Integer>,
Tuple3<Integer, Integer, Integer>> {
public Tuple3<Integer, Integer, Integer> map(Tuple5<Integer, Integer, Integer, Integer,
Integer> inputTuple) throws Exception {
return new Tuple3<Integer, Integer, Integer>(
inputTuple.f0,
inputTuple.f1,
inputTuple.f4);
}
}
终于我们将会得到类似例如以下的输出:
(7,14,5)
(18,37,3)
(3,46,3)
(23,32,3)
(31,43,2)
(13,45,2)
(37,42,2)
……
前两个整数是斐波那契数列的两个初始值。第三个整数表示其须要经历多少次迭代其斐波那契数列最新的两个值才会超过阈值。
终于完整的主干程序代码例如以下:
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment
.getExecutionEnvironment().setBufferTimeout(1);
DataStream<Tuple2<Integer, Integer>> inputStream = env.addSource(new RandomFibonacciSource());
IterativeStream<Tuple5<Integer, Integer, Integer, Integer, Integer>> iterativeStream =
inputStream.map(new TupleTransformMapFunction()).iterate(5000);
DataStream<Tuple5<Integer, Integer, Integer, Integer, Integer>> fibonacciStream =
iterativeStream.map(new FibonacciCalcStepFunction());
SplitStream<Tuple5<Integer, Integer, Integer, Integer, Integer>> branchedStream =
fibonacciStream.split(new FibonacciOverflowSelector());
iterativeStream.closeWith(branchedStream.select(ITERATE_FLAG));
DataStream<Tuple3<Integer, Integer, Integer>> outputStream = branchedStream
.select(OUTPUT_FLAG).map(new BuildOutputTupleMapFunction());
outputStream.print();
env.execute("Streaming Iteration Example");
}
微信扫码关注公众号:Apache_Flink
QQ扫码关注QQ群:Apache Flink学习交流群(123414680)
Flink流处理之迭代案例的更多相关文章
- 第05讲:Flink SQL & Table 编程和案例
Flink系列文章 第01讲:Flink 的应用场景和架构模型 第02讲:Flink 入门程序 WordCount 和 SQL 实现 第03讲:Flink 的编程模型与其他框架比较 第04讲:Flin ...
- FLINK流计算拓扑任务代码分析<一>
我打算以 flink 官方的 例子 <<Monitoring the Wikipedia Edit Stream>> 作为示例,进行 flink 流计算任务 的源码解析说明. ...
- 带你玩转Flink流批一体分布式实时处理引擎
摘要:Apache Flink是为分布式.高性能的流处理应用程序打造的开源流处理框架. 本文分享自华为云社区<[云驻共创]手把手教你玩转Flink流批一体分布式实时处理引擎>,作者: 萌兔 ...
- Java基础知识强化之IO流笔记39:字符流缓冲流之复制文本文件案例01
1. 字符流缓冲流之复制文本文件案例 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中 数据源: a.txt -- 读取数据 -- 字符转换流 -- InputStreamRe ...
- Flink流处理的时间窗口
Flink流处理的时间窗口 对于流处理系统来说,流入的消息是无限的,所以对于聚合或是连接等操作,流处理系统需要对流入的消息进行分段,然后基于每一段数据进行聚合或是连接等操作. 消息的分段即称为窗口,流 ...
- IO流 简介 总结 API 案例 MD
目录 IO 流 简介 关闭流的正确方式 关闭流的封装方法 InputStream 转 String 的方式 转换流 InputStreamReader OutputStreamWriter 测试代码 ...
- FLINK流计算拓扑任务代码分析<二>
首先 是 StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment(); 我们在编写 fl ...
- flink 流式处理中如何集成mybatis框架
flink 中自身虽然实现了大量的connectors,如下图所示,也实现了jdbc的connector,可以通过jdbc 去操作数据库,但是flink-jdbc包中对数据库的操作是以ROW来操作并且 ...
- Flink流处理(一)- 状态流处理简介
1. Flink 简介 Flink 是一个分布式流处理器,提供直观且易于使用的API,以供实现有状态的流处理应用.它能够以fault-tolerant的方式高效地运行在大规模系统中. 流处理技术在当今 ...
随机推荐
- 【FTP资源】发现一个ArcGIS相关的FTP。
用谷歌 在搜索 ArcGISEngineRT的时候,发现了一个站点: ftp://ftp.geobc.gov.bc.ca/pub/outgoing/GeoBC_software_distributio ...
- boost::asio::ip::tcp实现网络通信的小例子
同步方式: Boost.Asio是一个跨平台的网络及底层IO的C++编程库,它使用现代C++手法实现了统一的异步调用模型. 头文件 #include <boost/asio.hpp> 名空 ...
- CSV 数字转化文本
最近遇到一个Bug问题,csv 数值转化为文本的问题. 数据如下: 运行效果 如下: 大家看到“01720” 前面的0 没有显示出来.怎样才能显示出来了, 这里的csv文件格式也没有什么问题.后来找到 ...
- html与css架构的一点体验
css本身,可以说是一门非常简单而容易入门的语言.制作一个页面,或者制作一个小企业站,对于css的要求都是非常低的.只要熟悉语法,通过英文单词的含义猜,都基本可以拼出一套样式.更何况市面上还有各种各样 ...
- 高德地图JS-API (超简单Get新技能√)
上几章我们研究了百度地图SDK的用法,虽然不难但是配置起来也是相当的繁琐,现在高德LBS开放平台推出了基于HTML5的地图组件,我们可以通过WebView直接用URL 以GET方式进行请求就可以实现位 ...
- Java-JUC(六):创建线程的4种方式
Java创建线程的4种方式: Java使用Thread类代表线程,所有线程对象都必须是Thread类或者其子类的实例.Java可以用以下4种方式来创建线程: 1)继承Thread类创建线程: 2)实现 ...
- ubuntu16.04忘了root密码
1.开机点击ESC,进去GUN GRUB界面 2.选择有recovery mode的选项,按e进入命令行 3.找到有recovery nomodeset的行,删除recovery nomodeset, ...
- (转)【风宇冲】Unity3D教程宝典之AssetBundles:第二讲
原创文章如需转载请注明:转载自风宇冲Unity3D教程学院 AssetBundles第二讲:AssetBundles与脚本 所有Unity的As ...
- MySQL表级锁和行级锁
一:概述 相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制.比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking ...
- 【python】理想论坛爬虫1.08
#------------------------------------------------------------------------------------ # 理想论坛爬虫1.08, ...