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的方式高效地运行在大规模系统中. 流处理技术在当今 ...
随机推荐
- Svg.js 图片加载
一.SVG.Image 1.创建和修改图片 var draw = SVG('svg1').size(300, 300); //SVG.Image 加载图片文件 var image = draw.ima ...
- C# ApartmentState 枚举,指定线程单元状态
1.ApartmentState 指定的单元状态 Thread. 命名空间: System.Threading程序集: mscorlib(位于 mscorlib.dll) 成员 成员 ...
- [转]HIVE UDF/UDAF/UDTF的Map Reduce代码框架模板
FROM : http://hugh-wangp.iteye.com/blog/1472371 自己写代码时候的利用到的模板 UDF步骤: 1.必须继承org.apache.hadoop.hive ...
- 一个简易的WebServer程序
现在这个公司的it管理真不知道说什么了,我主要从事.net方向的开发,公司电脑配置还是win7了,可是自己没有安装iis的权限,it也拒绝安装,想玩玩web安全都不行.于是乎决定开发一个window ...
- iOS开发-UIButton浅谈
UIButton算是最基本的一个控件了,不过有的时候用法挺多关于UIButton文字的位置,字体大小,字体的颜色 1.设置UIButton字体大小,尤其注意不要使用直接调用setFont: [self ...
- protobuf Protocol Buffers 简介 案例 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- python3 杀死进程
在windows10杀死进程:Shadowsocks.exe os.system('taskkill /f /im %s' % 'Shadowsocks.exe')
- Tensorflow动态seq2seq使用总结(r1.3)
https://www.jianshu.com/p/c0c5f1bdbb88 动机 其实差不多半年之前就想吐槽Tensorflow的seq2seq了(后面博主去干了些别的事情),官方的代码已经抛弃原来 ...
- Mac关闭摄像头
为了防止黑客入侵,我们可以手动执行命令,关掉摄像头设备的访问权限,让黑客去看黑夜吧. Mac关闭禁用摄像头的方法,打开终端,拷贝如下代码,回车,输入管理员密码,再次拷贝,回车. sudo chmod ...
- 杂谈:大容量(T级容量)的网盘的意义
这两天,大容量的网盘的消息不断的推出.有百度的网盘推1T容量的:有腾讯的推10T容量的:有的还推不限容量的等等不一而足. 先看看大容量网盘的历史 早先是没有网盘这个概念的.能提供免费空间是电子邮箱 早 ...