├── .gitignore ├── .idea └── vcs.xml ├── README.md ├── concept.md ├── doc ├── CEP │ └── FlinkCEPOfficeWeb.md ├── ManagExecution │ ├── ExecutionConfiguration.md │ ├── ExecutionPlans.md │ ├── ParallelExecution.md │ └── RestartStrategies.md ├── Operators │ ├── AsynchronousIO.md │ └── join.md ├── State & Fault Tolerance │ ├── Savepoints.md │ ├── State Backends.md │ ├── Tuning Checkpoints and Large State.md │ └── checkpoints.md ├── batch │ ├── ClusterExecution.md │ └── localExecution.md ├── quickstark │ ├── CreateJavaProject.md │ ├── FlinkDeploy.md │ └── what-is-flink.md ├── streaming │ └── overview.md └── table │ ├── Concept & Common API.md │ ├── Overview.md │ ├── TableOverview.md │ ├── UDF.md │ └── sql.md └── pic ├── CEP ├── cep-monitoring.svg ├── checkpoint_tuning.PNG ├── checkpoint_tuning.svg ├── local_recovery.PNG ├── 创建跳过策略.png └── 跳过策略.png ├── ManagingExecution └── plan_visualizer.png ├── WhatIsFlink ├── flinkCluster.png ├── function-state.png ├── local-state.png └── 有界流无界流图示.png ├── stream ├── IntervalJoin.png ├── SessionWindowsJoin.png ├── SlideWindowsJoin.png ├── TumbleWindowsJoin.png └── needAsyncIO.png └── table └── udf.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | *~ 3 | *.diff 4 | *# 5 | .classpath 6 | .eclipse 7 | .project 8 | .settings 9 | target 10 | *.idea 11 | *.iml 12 | .DB_Store 13 | !.gitignore 14 | *.releaseBackup 15 | release.properties 16 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oneFlink 2 | 3 | 这个 repo 主要分享 Flink 的设计、原理并翻译Flink(v1.6.0)所涉及的模块功能等相关内容,本着自愿、公开的精神,期待热爱开源和贡献的你一起加入! 4 | 5 | ## 目录 6 | 7 | [具体请查看内容目录](./concept.md) 8 | 9 | ## 贡献方式 10 | 11 | 欢迎大家一起来分享知识和维护这份文档,让更多的人了解 Flink 。 12 | 如果有想要分享的内容或发现有误的地方,可以通过以下方式修改和贡献,流程基于 Pull Request : 13 | 14 | 1. 首先 fork 该项目 15 | 2. 把 fork 后的项目(即是你的项目) clone 到你的本地 16 | 3. 运行 `git remote add crestofwave1 git@github.com:crestofwave1/oneFlink.git` 新增远端库 17 | 4. 运行 `git pull crestofwave1 master` 拉取最新修改及合并到本地 18 | 5. 修改内容 19 | 6. commit 后 push 到自己的库(`git push origin master`) 20 | 7. 登录 Github 在你首页可以看到一个 `pull request` 按钮,点击它,填写一些说明信息,然后提交即可。 21 | 22 | `1~3` 是初始化操作,执行一次即可。在修改前请先执行步骤 `4` 同步最新的修改(减少冲突),然后执行 `5~7` 既可。 23 | -------------------------------------------------------------------------------- /concept.md: -------------------------------------------------------------------------------- 1 | * [Flink是什么](doc/quickstark/what-is-flink.md) 2 | * quickstark 3 | * [简介](doc/quickstark/what-is-flink.md) 4 | * [Flink standalone 安装部署](doc/quickstark/FlinkDeploy.md) 5 | * [Flink on yarn 安装部署](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/deployment/yarn_setup.html) 6 | * [高可用 HA](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/jobmanager_high_availability.html) 7 | 8 | * [快速构建java工程](doc/quickstark/CreateJavaProject.md) 9 | 10 | * [基本API介绍](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/api_concepts.html) 11 | * batch 12 | * [概览 认领 by _coderrr 翻译中](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/) 13 | * [转换 认领 by zhengqiangtan](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/dataset_transformations.html) 14 | * [Fault Tolerance](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/fault_tolerance.html) 15 | * [Iterations](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/iterations.html) 16 | * [Zipping Elements](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/zip_elements_guide.html) 17 | * [Connectors](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/connectors.html) 18 | * [Hadoop Compatibility](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/hadoop_compatibility.html) 19 | * [本地执行](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/local_execution.html) 20 | * [集群执行](doc/batch/ClusterExecution.md) 21 | * streaming 22 | * [概述](doc/streaming/overview.md) 23 | * 事件时间 24 | * [事件时间](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/event_time.html) 25 | * [产生时间戳和水印](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/event_timestamps_watermarks.html) 26 | * [预定义时间戳抽取器和水印发舍器](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/event_timestamp_extractors.html) 27 | * State & Fault Tolerance 28 | * [State & Fault Tolerance](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/) 29 | * [Working with State](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/state.html) 30 | * [The Broadcast State Pattern](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/broadcast_state.html) 31 | * [Checkpointing](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/checkpointing.html) 32 | * [Queryable State](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/queryable_state.html) 33 | * [State Backends](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/state_backends.html) 34 | * [Custom Serialization](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/custom_serialization.html) 35 | * Operators 36 | * [Operators](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/operators/) 37 | * [windows](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/operators/windows.html) 38 | * [Joining](doc/Operators/join.md) 39 | * [Process Function](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/operators/process_function.html) 40 | * [Asynchronous I/O](doc/Operators/AsynchronousIO.md) 41 | * Streaming Connectors 42 | * [Streaming Connectors](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/connectors/) 43 | * [Fault Tolerance Guarantees](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/connectors/guarantees.html) 44 | * [kafka](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/connectors/kafka.html) 45 | * [es](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/connectors/elasticsearch.html) 46 | * [HDFS Connector](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/connectors/filesystem_sink.html) 47 | * Side Outputs 48 | * [Side Outputs](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/side_output.html) 49 | * table 50 | * [概览](认领 by Tomxl)(https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/) 51 | * [概念 & 常用API](认领 by 清风)(https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/common.html) 52 | * [Streaming Concepts](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/streaming.html) 53 | * [Connect to External Systems](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/connect.html) 54 | * [Table API](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/tableApi.html) 55 | * [SQL](认领 by 浪尖)(https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/sql.html) 56 | * [User-defined Sources & Sinks](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/sourceSinks.html) 57 | * [User-defined Functions](doc/table/UDF.md) 58 | * [SQL Client Beta](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/sqlClient.html) 59 | * Data Types & Serialization 60 | * [overview](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/types_serialization.html) 61 | * [Custom Serializers](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/custom_serializers.html) 62 | * Managing Execution 63 | * [Execution Configuration](doc/ManagExecution/ExecutionConfiguration.md) 64 | * [Program Packaging](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/packaging.html) 65 | * [Parallel Execution](doc/ManagExecution/ParallelExecution.md) 66 | * [Execution Plans](doc/ManagExecution/ExecutionPlans.md) 67 | * [Restart Strategies](doc/ManagExecution/RestartStrategies.md) 68 | 69 | * CEP 70 | * [Event Processing (CEP)](doc/CEP/FlinkCEPOfficeWeb.md) 71 | 72 | * State & Fault Tolerance 73 | * [checkpoints](认领 by heitao,翻译完毕)(doc/State & Fault Tolerance/checkpoints.md) 74 | * [savepoints](认领 by heitao,翻译完毕)(doc/State & Fault Tolerance/Savepoints.md) 75 | * [State Backends](认领 by heitao,翻译完毕)(doc/State & Fault Tolerance/State Backends.md) 76 | * [Tuning Checkpoints and Large State](认领 by heitao,翻译完毕)(doc/State & Fault Tolerance/Tuning Checkpoints and Large State.md) 77 | 78 | * Debugging & Monitoring 79 | * [Metrics](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/metrics.html) 80 | * [logging](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/logging.html) 81 | * [historyserver](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/historyserver.html) 82 | * [Monitoring Checkpointing](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/checkpoint_monitoring.html) 83 | * [Monitoring Back Pressure](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/back_pressure.html) 84 | * [Monitoring REST API](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/rest_api.html) 85 | * [Debugging Windows & Event Time](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/debugging_event_time.html) 86 | * [Debugging Classloading](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/debugging_classloading.html) 87 | * [Application Profiling](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/application_profiling.html) 88 | 89 | * 栗子 90 | * [batch](https://github.com/crestofwave1/flinkExamples/tree/master/src/main/java/Batch) 91 | * [streaming](https://github.com/crestofwave1/flinkExamples/tree/master/src/main/java/Streaming) 92 | * [table](https://github.com/crestofwave1/flinkExamples/tree/master/src/main/java/TableSQL) 93 | 94 | 95 | * 源码 96 | * ing 97 | 98 | * meetup分享 99 | * 2018.08.11 北京flink meetup 100 | 链接:https://pan.baidu.com/s/1CLp2aFfef1M15Qauya4rCQ 密码:93mr 101 | * TODO 欢迎继续补充 102 | -------------------------------------------------------------------------------- /doc/CEP/FlinkCEPOfficeWeb.md: -------------------------------------------------------------------------------- 1 | 2 | # Flink CEP 3 | ## 0. 本文概述简介 4 | 5 | FlinkCEP是在Flink之上实现的复杂事件处理(CEP)库。 它允许你在无界的事件流中检测事件模式,让你有机会掌握数据中重要的事项。 6 | 7 | 本文描述了Flink CEP中可用的API调用。 首先介绍Pattern API,它允许你指定要在流中检测的模式,然后介绍如何检测匹配事件序列并对其进行操作。 8 | 然后,我们将介绍CEP库在处理事件时间延迟时所做的假设。 9 | 10 | ## 1.入门 11 | 12 | 首先是要在你的pom.xml文件中,引入CEP库。 13 | ```java 14 | 15 | org.apache.flink 16 | flink-cep_2.11 17 | 1.5.0 18 | 19 | ``` 20 | 注意要应用模式匹配的DataStream中的事件必须实现正确的equals()和hashCode()方法,因为FlinkCEP使用它们来比较和匹配事件。 21 | 22 | 第一个demo如下: 23 | ```java 24 | DataStream input = ... 25 | 26 | Pattern pattern = Pattern.begin("start").where( 27 | new SimpleCondition() { 28 | @Override 29 | public boolean filter(Event event) { 30 | return event.getId() == 42; 31 | } 32 | } 33 | ).next("middle").subtype(SubEvent.class).where( 34 | new SimpleCondition() { 35 | @Override 36 | public boolean filter(SubEvent subEvent) { 37 | return subEvent.getVolume() >= 10.0; 38 | } 39 | } 40 | ).followedBy("end").where( 41 | new SimpleCondition() { 42 | @Override 43 | public boolean filter(Event event) { 44 | return event.getName().equals("end"); 45 | } 46 | } 47 | ); 48 | 49 | PatternStream patternStream = CEP.pattern(input, pattern); 50 | 51 | DataStream result = patternStream.select( 52 | new PatternSelectFunction { 53 | @Override 54 | public Alert select(Map> pattern) throws Exception { 55 | return createAlertFrom(pattern); 56 | } 57 | } 58 | }); 59 | ``` 60 | 61 | ## 2.Pattern API 62 | 63 | Pattern API允许你定义要从输入流中提取的复杂模式序列。 64 | 65 | 每个复杂模式序列都是由多个简单模式组成,即寻找具有相同属性的单个事件的模式。我们可以先定义一些简单的模式,然后组合成复杂的模式序列。 66 | 可以将模式序列视为此类模式的结构图,基于用户指定的条件从一个模式转换到下一个模式,例如, event.getName().equals("start")。 67 | 匹配是一系列输入事件,通过一系列有效的模式转换访问复杂模式图中的所有模式。 68 | 69 | 注意每个模式必须具有唯一的名称,以便后续可以使用该名称来标识匹配的事件。 70 | 71 | 注意模式名称不能包含字符“:”。 72 | 73 | 在本节接下来的部分,我们将首先介绍如何定义单个模式,然后如何将各个模式组合到复杂模式中。 74 | 75 | ### 2.1 单个模式 76 | Pattern可以是单单个,也可以是循环模式。单个模式接受单个事件,而循环模式可以接受多个事件。在模式匹配符号中,模式“a b + c?d”(或“a”,后跟一个或多个“b”,可选地后跟“c”,后跟“d”),a,c ?,和d是单例模式,而b +是循环模式。 77 | 默认情况下,模式是单个模式,您可以使用Quantifiers将其转换为循环模式。每个模式可以有一个或多个条件,基于它接受事件。 78 | 79 | #### 2.1.1 Quantifiers 80 | 81 | 在FlinkCEP中,您可以使用以下方法指定循环模式:pattern.oneOrMore(),用于期望一个或多个事件发生的模式(例如之前提到的b +);和pattern.times(#ofTimes), 82 | 用于期望给定类型事件的特定出现次数的模式,例如4个;和patterntimes(#fromTimes,#toTimes),用于期望给定类型事件的最小出现次数和最大出现次数的模式,例如, 2-4。 83 | 84 | 您可以使用pattern.greedy()方法使循环模式变得贪婪,但是还不能使组模式变得贪婪。您可以使用pattern.optional()方法使得所有模式,循环与否,变为可选。 85 | 86 | 对于名为start的模式,以下是有效的Quantifiers: 87 | 88 | ```java 89 | // expecting 4 occurrences 90 | start.times(4); 91 | 92 | // expecting 0 or 4 occurrences 93 | start.times(4).optional(); 94 | 95 | // expecting 2, 3 or 4 occurrences 96 | start.times(2, 4); 97 | 98 | // expecting 2, 3 or 4 occurrences and repeating as many as possible 99 | start.times(2, 4).greedy(); 100 | 101 | // expecting 0, 2, 3 or 4 occurrences 102 | start.times(2, 4).optional(); 103 | 104 | // expecting 0, 2, 3 or 4 occurrences and repeating as many as possible 105 | start.times(2, 4).optional().greedy(); 106 | 107 | // expecting 1 or more occurrences 108 | start.oneOrMore(); 109 | 110 | // expecting 1 or more occurrences and repeating as many as possible 111 | start.oneOrMore().greedy(); 112 | 113 | // expecting 0 or more occurrences 114 | start.oneOrMore().optional(); 115 | 116 | // expecting 0 or more occurrences and repeating as many as possible 117 | start.oneOrMore().optional().greedy(); 118 | 119 | // expecting 2 or more occurrences 120 | start.timesOrMore(2); 121 | 122 | // expecting 2 or more occurrences and repeating as many as possible 123 | start.timesOrMore(2).greedy(); 124 | 125 | // expecting 0, 2 or more occurrences and repeating as many as possible 126 | start.timesOrMore(2).optional().greedy(); 127 | 128 | ``` 129 | 130 | #### 2.1.2 Conditions-条件 131 | 132 | 在每个模式中,从一个模式转到下一个模式,可以指定其他条件。您可以将使用下面这些条件: 133 | 134 | 1. 传入事件的属性,例如其值应大于5,或大于先前接受的事件的平均值。 135 | 136 | 2. 匹配事件的连续性,例如检测模式a,b,c,序列中间不能有任何非匹配事件。 137 | 138 | #### 2.1.3 Conditions on Properties-关于属性的条件 139 | 140 | 可以通过pattern.where(),pattern.or()或pattern.until()方法指定事件属性的条件。 条件可以是IterativeConditions或SimpleConditions。 141 | 142 | 143 | 1. 迭代条件: 144 | 145 | 这是最常见的条件类型。 你可以指定一个条件,该条件基于先前接受的事件的属性或其子集的统计信息来接受后续事件。 146 | 147 | 下面代码说的是:如果名称以“foo”开头同时如果该模式的先前接受的事件的价格总和加上当前事件的价格不超过该值 5.0,则迭代条件接受名为“middle”的模式的下一个事件,。 148 | 迭代条件可以很强大的,尤其是与循环模式相结合,例如, oneOrMore()。 149 | 150 | ````java 151 | middle.oneOrMore().where(new IterativeCondition() { 152 | @Override 153 | public boolean filter(SubEvent value, Context ctx) throws Exception { 154 | if (!value.getName().startsWith("foo")) { 155 | return false; 156 | } 157 | 158 | double sum = value.getPrice(); 159 | for (Event event : ctx.getEventsForPattern("middle")) { 160 | sum += event.getPrice(); 161 | } 162 | return Double.compare(sum, 5.0) < 0; 163 | } 164 | }); 165 | ```` 166 | 167 | 注意对context.getEventsForPattern(...)的调用,将为给定潜在匹配项查找所有先前接受的事件。 此操作的代价可能会变化巨大,因此在使用条件时,请尽量减少其使用。 168 | 169 | 2. 简单条件: 170 | 171 | 这种类型的条件扩展了前面提到的IterativeCondition类,并且仅根据事件本身的属性决定是否接受事件。 172 | 173 | ```java 174 | start.where(new SimpleCondition() { 175 | @Override 176 | public boolean filter(Event value) { 177 | return value.getName().startsWith("foo"); 178 | } 179 | }); 180 | ``` 181 | 182 | 最后,还可以通过pattern.subtype(subClass)方法将接受事件的类型限制为初始事件类型的子类型。 183 | 184 | ```java 185 | start.subtype(SubEvent.class).where(new SimpleCondition() { 186 | @Override 187 | public boolean filter(SubEvent value) { 188 | return ... // some condition 189 | } 190 | }); 191 | ``` 192 | 193 | 3. 组合条件: 194 | 195 | 如上所示,可以将子类型条件与其他条件组合使用。 这适用于所有条件。 您可以通过顺序调用where()来任意组合条件。 196 | 最终结果将是各个条件的结果的逻辑AND。 要使用OR组合条件,可以使用or()方法,如下所示。 197 | 198 | ```java 199 | pattern.where(new SimpleCondition() { 200 | @Override 201 | public boolean filter(Event value) { 202 | return ... // some condition 203 | } 204 | }).or(new SimpleCondition() { 205 | @Override 206 | public boolean filter(Event value) { 207 | return ... // or condition 208 | } 209 | }); 210 | ``` 211 | 212 | 4. 停止条件: 213 | 214 | 在循环模式(oneOrMore()和oneOrMore().optional())的情况下,还可以指定停止条件,例如: 接受值大于5的事件,直到值的总和小于50。 215 | 216 | 为了更好的理解,可以看看下面的例子: 217 | 218 | 给定模式:(a+ until b),b之前,要出现一个或者多个a。 219 | 220 | 给定输入序列:a1,c,a2,b,a3 221 | 222 | 输出结果: {a1 a2}{a1}{a2}{a3} 223 | 224 | 可以看到{a1,a2,a3},{a2,a3}这两个并没有输出,这就是停止条件的作用。 225 | 226 | 5. 连续事件条件 227 | 228 | FlinkCEP支持事件之间以下形式进行连续: 229 | 230 | * 严格连续性:希望所有匹配事件一个接一个地出现,中间没有任何不匹配的事件。 231 | 232 | * 宽松连续性:忽略匹配的事件之间出现的不匹配事件。 不能忽略两个事件之间的匹配事件。 233 | 234 | * 非确定性轻松连续性:进一步放宽连续性,允许忽略某些匹配事件的其他匹配。 235 | 236 | 为了解释上面的内容,我们举个例子。假如有个模式序列"a+ b",输入序列"a1,c,a2,b",不同连续条件下有不同的区别: 237 | 238 | 1. 严格连续性:{a2 b} - 由于c的存在导致a1被废弃 239 | 240 | 2. 宽松连续性:{a1,b}和{a1 a2 b} - c被忽略 241 | 242 | 3. 非确定性宽松连续性:{a1 b}, {a2 b}, 和 {a1 a2 b} 243 | 244 | 对于循环模式(例如oneOrMore()和times()),默认是宽松的连续性。 如果你想要严格的连续性,你必须使用consecutive()显式指定它, 245 | 如果你想要非确定性的松弛连续性,你可以使用allowCombinations()方法。 246 | 247 | 248 | 注意在本节中,我们讨论的是单个循环模式中的连续性,并且需要在该上下文中理解consecutive()和allowCombinations()。 249 | 稍后在讲解组合模式时,我们将讨论其他方法,例如next()和followedBy(),用于指定模式之间的连续条件。 250 | 251 | #### 2.1.4 API简介 252 | 253 | 1. where(condition) 254 | 255 | 定义当前模式的条件。 为了匹配模式,事件必须满足条件。 多个连续的where(),其条件为AND: 256 | ```java 257 | pattern.where(new IterativeCondition() { 258 | @Override 259 | public boolean filter(Event value, Context ctx) throws Exception { 260 | return ... // some condition 261 | } 262 | }); 263 | ``` 264 | 265 | 2. or(condition) 266 | 267 | 添加与现有条件进行OR运算的新条件。 只有在至少通过其中一个条件时,事件才能匹配该模式: 268 | 269 | ```java 270 | pattern.where(new IterativeCondition() { 271 | @Override 272 | public boolean filter(Event value, Context ctx) throws Exception { 273 | return ... // some condition 274 | } 275 | }).or(new IterativeCondition() { 276 | @Override 277 | public boolean filter(Event value, Context ctx) throws Exception { 278 | return ... // alternative condition 279 | } 280 | }); 281 | ``` 282 | 283 | 3. until(condition) 284 | 285 | 指定循环模式的停止条件。 意味着如果匹配给定条件的事件发生,则不再接受该模式中的事件。 286 | 287 | 仅适用于oneOrMore() 288 | 289 | 注意:它允许在基于事件的条件下清除相应模式的状态。 290 | 291 | ```java 292 | pattern.oneOrMore().until(new IterativeCondition() { 293 | @Override 294 | public boolean filter(Event value, Context ctx) throws Exception { 295 | return ... // alternative condition 296 | } 297 | }); 298 | ``` 299 | 300 | 4. subtype(subClass) 301 | 302 | 定义当前模式的子类型条件。 如果事件属于此子类型,则事件只能匹配该模式: 303 | 304 | ```java 305 | pattern.subtype(SubEvent.class); 306 | ``` 307 | 308 | 5. oneOrMore() 309 | 310 | 指定此模式至少发生一次匹配事件。 311 | 312 | 默认情况下,使用宽松的内部连续性。 313 | 314 | 注意:建议使用until()或within()来启用状态清除 315 | 316 | ```java 317 | pattern.oneOrMore().until(new IterativeCondition() { 318 | @Override 319 | public boolean filter(Event value, Context ctx) throws Exception { 320 | return ... // alternative condition 321 | } 322 | }); 323 | ``` 324 | 325 | 6. timesOrMore(#times) 326 | 327 | 指定此模式至少需要#times次出现匹配事件。 328 | 329 | 默认情况下,使用宽松的内部连续性(在后续事件之间)。 330 | 331 | ```java 332 | pattern.timesOrMore(2); 333 | ``` 334 | 335 | 7. times(#ofTimes) 336 | 337 | 指定此模式需要匹配事件的确切出现次数。 338 | 339 | 默认情况下,使用宽松的内部连续性(在后续事件之间)。 340 | 341 | ```java 342 | pattern.times(2); 343 | ``` 344 | 345 | 8. times(#fromTimes, #toTimes) 346 | 347 | 指定此模式期望在匹配事件的#fromTimes次和#toTimes次之间出现。 348 | 349 | 默认情况下,使用宽松的内部连续性。 350 | 351 | ```java 352 | pattern.times(2, 4); 353 | ``` 354 | 355 | 9. optional() 356 | 357 | 指定此模式是可选的,即有可能根本不会发生。 这适用于所有上述量词。 358 | 359 | ```java 360 | pattern.oneOrMore().optional(); 361 | ``` 362 | 363 | 10. greedy() 364 | 365 | 指定此模式是贪婪的,即它将尽可能多地重复。 这仅适用于quantifiers,目前不支持组模式。 366 | 367 | ```java 368 | pattern.oneOrMore().greedy(); 369 | ``` 370 | 371 | 11. consecutive() 372 | 373 | 与oneOrMore()和times()一起使用并在匹配事件之间强加严格的连续性,即任何不匹配的元素都会中断匹配。 374 | 375 | 如果不使用,则使用宽松的连续性(如followBy())。 376 | 377 | 例如,这样的模式: 378 | 379 | ```java 380 | Pattern.begin("start").where(new SimpleCondition() { 381 | @Override 382 | public boolean filter(Event value) throws Exception { 383 | return value.getName().equals("c"); 384 | } 385 | }) 386 | .followedBy("middle").where(new SimpleCondition() { 387 | @Override 388 | public boolean filter(Event value) throws Exception { 389 | return value.getName().equals("a"); 390 | } 391 | }).oneOrMore().consecutive() 392 | .followedBy("end1").where(new SimpleCondition() { 393 | @Override 394 | public boolean filter(Event value) throws Exception { 395 | return value.getName().equals("b"); 396 | } 397 | }); 398 | 399 | ``` 400 | 401 | 针对上面的模式,我们假如输入序列如:C D A1 A2 A3 D A4 B 402 | 403 | 使用consecutive:{C A1 B}, {C A1 A2 B}, {C A1 A2 A3 B} 404 | 405 | 不使用:{C A1 B}, {C A1 A2 B}, {C A1 A2 A3 B}, {C A1 A2 A3 A4 B} 406 | 407 | 12. allowCombinations() 408 | 409 | 与oneOrMore()和times()一起使用,并在匹配事件之间强加非确定性宽松连续性(如 followedByAny())。 410 | 411 | 如果不应用,则使用宽松的连续性(如followBy())。 412 | 413 | 例如,这样的模式: 414 | 415 | ```java 416 | Pattern.begin("start").where(new SimpleCondition() { 417 | @Override 418 | public boolean filter(Event value) throws Exception { 419 | return value.getName().equals("c"); 420 | } 421 | }) 422 | .followedBy("middle").where(new SimpleCondition() { 423 | @Override 424 | public boolean filter(Event value) throws Exception { 425 | return value.getName().equals("a"); 426 | } 427 | }).oneOrMore().allowCombinations() 428 | .followedBy("end1").where(new SimpleCondition() { 429 | @Override 430 | public boolean filter(Event value) throws Exception { 431 | return value.getName().equals("b"); 432 | } 433 | }); 434 | ``` 435 | 436 | 针对上面的模式,我们假如输入序列如:C D A1 A2 A3 D A4 B 437 | 438 | 使用allowCombinations:{C A1 B}, {C A1 A2 B}, {C A1 A3 B}, {C A1 A4 B}, {C A1 A2 A3 B}, {C A1 A2 A4 B}, {C A1 A3 A4 B}, {C A1 A2 A3 A4 B} 439 | 440 | 不使用:{C A1 B}, {C A1 A2 B}, {C A1 A2 A3 B}, {C A1 A2 A3 A4 B} 441 | 442 | 443 | ### 2.2 组合模式 444 | 445 | #### 2.2.1 简介 446 | 447 | 已经了解了单个模式的样子,现在是时候看看如何将它们组合成一个完整的模式序列。 448 | 449 | 模式序列必须以初始模式开始,如下所示: 450 | 451 | ```java 452 | Pattern start = Pattern.begin("start"); 453 | ``` 454 | 455 | 接下来,您可以通过指定它们之间所需的连续条件,为模式序列添加更多模式。 在上一节中,我们描述了Flink支持的不同邻接模式,即严格,宽松和非确定性宽松,以及如何在循环模式中应用它们。 456 | 要在连续模式之间应用它们,可以使用: 457 | 458 | >next() 对应 严格, 459 | followedBy() 对应 宽松连续性 460 | followedByAny() 对应 非确定性宽松连续性 461 | 462 | 亦或 463 | 464 | >notNext() 如果不希望一个事件类型紧接着另一个类型出现。 465 | notFollowedBy() 不希望两个事件之间任何地方出现该事件。 466 | 467 | >注意 模式序列不能以notFollowedBy()结束。 468 | 469 | >注意 NOT模式前面不能有可选模式。 470 | 471 | ```java 472 | // strict contiguity 473 | Pattern strict = start.next("middle").where(...); 474 | 475 | // relaxed contiguity 476 | Pattern relaxed = start.followedBy("middle").where(...); 477 | 478 | // non-deterministic relaxed contiguity 479 | Pattern nonDetermin = start.followedByAny("middle").where(...); 480 | 481 | // NOT pattern with strict contiguity 482 | Pattern strictNot = start.notNext("not").where(...); 483 | 484 | // NOT pattern with relaxed contiguity 485 | Pattern relaxedNot = start.notFollowedBy("not").where(...); 486 | ``` 487 | 宽松连续性指的是仅第一个成功匹配的事件会被匹配到,然而非确定性宽松连续性,相同的开始会有多个匹配结果发出。距离,如果一个模式是"a b",给定输入序列是"a c b1 b2"。对于不同连续性会有不同输出。 488 | 489 | 490 | 1. a和b之间严格连续性,将会返回{},也即是没有匹配。因为c的出现导致a,抛弃了。 491 | 492 | 2. a和b之间宽松连续性,返回的是{a,b1},因为宽松连续性将会抛弃为匹配成功的元素,直至匹配到下一个要匹配的事件。 493 | 494 | 3. a和b之间非确定性宽松连续性,返回的是{a,b1},{a,b2}。 495 | 496 | 也可以为模式定义时间约束。 例如,可以通过pattern.within()方法定义模式应在10秒内发生。 时间模式支持处理和事件时间。 497 | 注意模式序列只能有一个时间约束。 如果在不同的单独模式上定义了多个这样的约束,则应用最小的约束。 498 | 499 | ```java 500 | next.within(Time.seconds(10)); 501 | ``` 502 | 503 | 可以为begin,followBy,followByAny和next定义一个模式序列作为条件。模式序列将被逻辑地视为匹配条件,而且将返回GroupPattern并且 504 | 可对GroupPattern使用oneOrMore(),times(#ofTimes),times(#fromTimes,#toTimes),optional(),consecutive(), allowCombinations()等方法。 505 | 506 | ```java 507 | 508 | PatternPatte start = Pattern.begin( 509 | Pattern.begin("start").where(...).followedBy("start_middle").where(...) 510 | ); 511 | 512 | // strict contiguity 513 | Pattern strict = start.next( 514 | Pattern.begin("next_start").where(...).followedBy("next_middle").where(...) 515 | ).times(3); 516 | 517 | // relaxed contiguity 518 | Pattern relaxed = start.followedBy( 519 | Pattern.begin("followedby_start").where(...).followedBy("followedby_middle").where(...) 520 | ).oneOrMore(); 521 | 522 | // non-deterministic relaxed contiguity 523 | Pattern nonDetermin = start.followedByAny( 524 | Pattern.begin("followedbyany_start").where(...).followedBy("followedbyany_middle").where(...) 525 | ).optional(); 526 | ``` 527 | 528 | #### 2.2.2 API 529 | 530 | 1. begin(#name) 531 | 532 | 定义一个开始模式 533 | ```java 534 | Pattern start = Pattern.begin("start"); 535 | ``` 536 | 537 | 2. begin(#pattern_sequence) 538 | 539 | 定义一个开始模式 540 | ```java 541 | Pattern start = Pattern.begin( 542 | Pattern.begin("start").where(...).followedBy("middle").where(...) 543 | ); 544 | ``` 545 | 546 | 3. next(#name) 547 | 548 | 追加一个新的模式。匹配事件必须直接跟着先前的匹配事件(严格连续性): 549 | ```java 550 | Pattern next = start.next("middle"); 551 | ``` 552 | 553 | 4. next(#pattern_sequence) 554 | 555 | 追加一个新的模式。匹配事件必须直接接着先前的匹配事件(严格连续性): 556 | ```java 557 | Pattern next = start.next( 558 | Pattern.begin("start").where(...).followedBy("middle").where(...) 559 | ); 560 | ``` 561 | 562 | 5. followedBy(#name) 563 | 564 | 追加加新模式。 匹配事件和先前匹配事件(宽松连续)之间可能发生其他非匹配事件: 565 | ```java 566 | Pattern followedBy = start.followedBy("middle"); 567 | ``` 568 | 569 | 6. followedBy(#pattern_sequence) 570 | 571 | 追加新模式。 匹配事件和先前匹配事件(宽松连续)之间可能发生其他非匹配事件: 572 | ```java 573 | Pattern followedBy = start.followedBy( 574 | Pattern.begin("start").where(...).followedBy("middle").where(...) 575 | ); 576 | ``` 577 | 578 | 7. followedByAny(#name) 579 | 580 | 添加新模式。 匹配事件和先前匹配事件之间可能发生其他事件,并且将针对每个备选匹配事件(非确定性放松连续性)呈现替代匹配: 581 | ```java 582 | Pattern followedByAny = start.followedByAny("middle"); 583 | ``` 584 | 585 | 8. followedByAny(#pattern_sequence) 586 | 587 | 添加新模式。 匹配事件和先前匹配事件之间可能发生其他事件,并且将针对每个备选匹配事件(非确定性放松连续性)呈现替代匹配: 588 | ```java 589 | Pattern followedByAny = start.followedByAny( 590 | Pattern.begin("start").where(...).followedBy("middle").where(...) 591 | ); 592 | ``` 593 | 594 | 9. notNext() 595 | 596 | 添加新的否定模式。 匹配(否定)事件必须直接跟着先前的匹配事件(严格连续性)才能丢弃部分匹配: 597 | ```java 598 | Pattern notNext = start.notNext("not"); 599 | ``` 600 | 601 | 10. notFollowedBy() 602 | 603 | 追加一个新的否定模式匹配。即使在匹配(否定)事件和先前匹配事件(宽松连续性)之间发生其他事件,也将丢弃部分匹配事件序列: 604 | ```java 605 | Pattern notFollowedBy = start.notFollowedBy("not"); 606 | ``` 607 | 608 | 11. within(time) 609 | 610 | 定义事件序列进行模式匹配的最大时间间隔。 如果未完成的事件序列超过此时间,则将其丢弃: 611 | ```java 612 | pattern.within(Time.seconds(10)); 613 | ``` 614 | 615 | ### 2.3 匹配后的跳过策略 616 | 617 | 对于给定模式,可以将同一事件分配给多个成功匹配。 要控制将分配事件的匹配数,需要指定名为AfterMatchSkipStrategy的跳过策略。 618 | 跳过策略有四种类型,如下所示: 619 | 620 | * NO_SKIP:将发出每个可能的匹配。 621 | 622 | * SKIP_PAST_LAST_EVENT:丢弃包含匹配事件的每个部分匹配。 623 | 624 | * SKIP_TO_FIRST:丢弃包含PatternName第一个之前匹配事件的每个部分匹配。 625 | 626 | * SKIP_TO_LAST:丢弃包含PatternName最后一个匹配事件之前的每个部分匹配。 627 | 628 | 请注意,使用SKIP_TO_FIRST和SKIP_TO_LAST跳过策略时,还应指定有效的PatternName。 629 | 630 | 例如,对于给定模式a b {2}和数据流ab1,ab2,ab3,ab4,ab5,ab6,这四种跳过策略之间的差异如下: 631 | 632 | ![image](../../pic/CEP/跳过策略.png) 633 | 634 | 要指定要使用的跳过策略,只需调用以下命令创建AfterMatchSkipStrategy: 635 | 636 | ![image](../../pic/CEP/创建跳过策略.png) 637 | 638 | 使用方法: 639 | 640 | ```java 641 | AfterMatchSkipStrategy skipStrategy = ... 642 | Pattern.begin("patternName", skipStrategy); 643 | ``` 644 | 645 | ### 2.4 检测模式-Detecting Patterns 646 | 647 | 指定要查找的模式序列后,就可以将其应用于输入流以检测潜在匹配。 要针对模式序列运行事件流,必须创建PatternStream。 648 | 给定输入流 input,模式 pattern 和可选的比较器 comparator,用于在EventTime的情况下对具有相同时间戳的事件进行排序或在同一时刻到达,通过调用以下命令创建PatternStream: 649 | 650 | ```java 651 | DataStream input = ... 652 | Pattern pattern = ... 653 | EventComparator comparator = ... // optional 654 | 655 | PatternStream patternStream = CEP.pattern(input, pattern, comparator); 656 | ``` 657 | 658 | 根据实际情况,创建的流可以是有key,也可以是无key的。 659 | 660 | 请注意,在无key的流上使用模式,将导致job的并行度为1。 661 | 662 | ### 2.5 Selecting from Patterns 663 | 664 | 获得PatternStream后,您可以通过select或flatSelect方法从检测到的事件序列中进行查询。 665 | 666 | select()方法需要PatternSelectFunction的实现。 PatternSelectFunction具有为每个匹配事件序列调用的select方法。 667 | 它以Map >的形式接收匹配,其中key是模式序列中每个模式的名称,值是该模式的所有已接受事件的列表(IN是输入元素的类型)。 668 | 给定模式的事件按时间戳排序。 返回每个模式的接受事件列表的原因是当使用循环模式(例如oneToMany()和times())时,对于给定模式可以接受多个事件。 669 | 选择函数只返回一个结果。 670 | 671 | ```java 672 | class MyPatternSelectFunction implements PatternSelectFunction { 673 | @Override 674 | public OUT select(Map> pattern) { 675 | IN startEvent = pattern.get("start").get(0); 676 | IN endEvent = pattern.get("end").get(0); 677 | return new OUT(startEvent, endEvent); 678 | } 679 | } 680 | ``` 681 | PatternFlatSelectFunction类似于PatternSelectFunction,唯一的区别是它可以返回任意数量的结果。 为此,select方法有一个额外的Collector参数,用于将输出元素向下游转发。 682 | 683 | ```java 684 | class MyPatternFlatSelectFunction implements PatternFlatSelectFunction { 685 | @Override 686 | public void flatSelect(Map> pattern, Collector collector) { 687 | IN startEvent = pattern.get("start").get(0); 688 | IN endEvent = pattern.get("end").get(0); 689 | 690 | for (int i = 0; i < startEvent.getValue(); i++ ) { 691 | collector.collect(new OUT(startEvent, endEvent)); 692 | } 693 | } 694 | } 695 | ``` 696 | 697 | ### 2.6 处理超时部分模式 698 | 699 | 每当模式具有通过within关键字附加的时间窗口长度时,部分事件序列可能因为超出时间窗口长度而被丢弃。 为了对这些超时的部分匹配作出相应的处理,select和flatSelect API调用允许指定超时处理程序。 700 | 为每个超时的部分事件序列调用此超时处理程序。 超时处理程序接收到目前为止由模式匹配的所有事件,以及检测到超时时的时间戳。 701 | 702 | 为了处理部分模式,select和flatSelect API提供了一个带参数的重载版本 703 | 704 | * PatternTimeoutFunction/ PatternFlatTimeoutFunction。 705 | * OutputTag 超时的匹配将会在其中返回。 706 | * PatternSelectFunction / PatternFlatSelectFunction。 707 | 708 | ```java 709 | 710 | PatternStreamPatte patternStream = CEP.pattern(input, pattern); 711 | 712 | OutputTag outputTag = new OutputTag("side-output"){}; 713 | 714 | SingleOutputStreamOperator result = patternStream.select( 715 | new PatternTimeoutFunction() {...}, 716 | outputTag, 717 | new PatternSelectFunction() {...} 718 | ); 719 | 720 | DataStream timeoutResult = result.getSideOutput(outputTag); 721 | 722 | SingleOutputStreamOperator flatResult = patternStream.flatSelect( 723 | new PatternFlatTimeoutFunction() {...}, 724 | outputTag, 725 | new PatternFlatSelectFunction() {...} 726 | ); 727 | 728 | DataStream timeoutFlatResult = flatResult.getSideOutput(outputTag); 729 | ``` 730 | 731 | ### 2.7 事件事件模式下处理滞后数据 732 | 733 | 在CEP中,元素处理的顺序很重要。为了保证在采用事件事件时以正确的顺序处理事件,最初将传入的事件放入缓冲区,其中事件基于它们的时间戳以升序排序, 734 | 并且当watermark到达时,处理该缓冲区中时间戳小于watermark时间的所有元素。这意味着watermark之间的事件按事件时间顺序处理。 735 | 736 | 请注意,在采用事件时间时,CEP library会假设watermark是正确的。 737 | 738 | 为了保证跨watermark的记录按照事件事件顺序处理,Flink的CEP库假定watermark是正确的,并将时间戳小于上次可见watermark的时间视为滞后事件。滞后事件不会被进一步处理。 739 | 740 | ### 2.8 栗子 741 | 742 | 以下示例检测事件的带key数据流上的模式start,middle(name =“error”) - > end(name =“critical”)。 事件的key是其id,并且有效模式必须在10秒内发生。 整个处理是用事件时间完成的。 743 | 744 | ```java 745 | StreamExecutionEnvironment env = ... 746 | env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); 747 | 748 | DataStream input = ... 749 | 750 | DataStream partitionedInput = input.keyBy(new KeySelector() { 751 | @Override 752 | public Integer getKey(Event value) throws Exception { 753 | return value.getId(); 754 | } 755 | }); 756 | 757 | Pattern pattern = Pattern.begin("start") 758 | .next("middle").where(new SimpleCondition() { 759 | @Override 760 | public boolean filter(Event value) throws Exception { 761 | return value.getName().equals("error"); 762 | } 763 | }).followedBy("end").where(new SimpleCondition() { 764 | @Override 765 | public boolean filter(Event value) throws Exception { 766 | return value.getName().equals("critical"); 767 | } 768 | }).within(Time.seconds(10)); 769 | 770 | PatternStream patternStream = CEP.pattern(partitionedInput, pattern); 771 | 772 | DataStream alerts = patternStream.select(new PatternSelectFunction() { 773 | @Override 774 | public Alert select(Map> pattern) throws Exception { 775 | return createAlert(pattern); 776 | } 777 | }); 778 | ``` 779 | -------------------------------------------------------------------------------- /doc/ManagExecution/ExecutionConfiguration.md: -------------------------------------------------------------------------------- 1 | 2 | # Execution Configuration 3 | 4 | -------------------------------------------------------------------------------- /doc/ManagExecution/ExecutionPlans.md: -------------------------------------------------------------------------------- 1 | 2 | ## 执行计划 3 | 4 | 根据各种参数(如数据大小或群集中的计算机数量),Flink的优化器会自动为您的程序选择执行策略。 在许多情况下,了解Flink将如何执行您的程序会很有用。 5 | 6 | Flink附带了一个用于执行计划的可视化工具。 包含可视化工具的HTML文档位于tools / planVisualizer.html下。 7 | 用json格式表示job的执行计划,并将其可视化为具有执行策略的完整注释的图。 8 | 9 | 以下代码显示了如何从程序中打印执行计划: 10 | ```java 11 | final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); 12 | ... 13 | 14 | System.out.println(env.getExecutionPlan()); 15 | ``` 16 | 要可视化执行计划,请执行以下操作: 17 | 1. 使用你的web浏览器打开planVisualizer.html 18 | 2. 将json字符串粘贴至文本输入框 19 | 3. 点击绘图按钮 20 | 完成这些步骤后,将显示详细的执行计划。 21 | 22 | ![image](../../pic/ManagingExecution/plan_visualizer.png) 23 | 24 | 25 | 26 | ## Web界面 27 | 28 | Flink提供用于提交和执行作业的Web界面。 该接口是JobManager用于监控的Web界面的一部分,默认情况下在端口8081上运行。 29 | 通过此接口提交作业需要在flink-conf.yaml中设置jobmanager.web.submit.enable:true。 30 | 31 | 可以在执行作业之前指定程序参数。 通过执行计划可视化可视化,可以在执行Flink作业之前显示执行计划。 -------------------------------------------------------------------------------- /doc/ManagExecution/ParallelExecution.md: -------------------------------------------------------------------------------- 1 | #Flink并行度 2 | 3 | ## 1. 并行执行 4 | 本节介绍如何在Flink中配置程序的并行执行。FLink程序由多个任务(转换/操作符、数据源和sinks)组成。 5 | 任务被分成多个并行实例来执行,每个并行实例处理任务的输入数据的子集。任务的并行实例的数量称之为并行性。 6 | 7 | 如果要使用保存点,还应该考虑设置最大并行性(或最大并行性)。当从保存点还原时,可以改变特定运算符或整个程序的并行性,并且该设置指定并行性的上限。 8 | 这是必需的,因为FLINK内部将状态划分为key-groups,并且我们不能拥有+INF的key-group数,因为这将对性能有害。 9 | Flink中人物的并行度可以从多个不同层面设置: 10 | 1.操作算子层面 11 | 2.执行环境层面 12 | 3.客户端层面 13 | 4.系统层面 14 | 15 | ### 1.1 操作算子层 16 | 操作算子,数据源,数据接收器等这些并行度都可以通过调用他们的setParallelism()方法设置。例如: 17 | ```scala 18 | val env = StreamExecutionEnvironment.getExecutionEnvironment 19 | 20 | val text = [...] 21 | val wordCounts = text 22 | .flatMap{ _.split(" ") map { (_, 1) } } 23 | .keyBy(0) 24 | .timeWindow(Time.seconds(5)) 25 | .sum(1).setParallelism(5) 26 | wordCounts.print() 27 | 28 | env.execute("Word Count Example") 29 | ``` 30 | ### 1.2 执行环境层面 31 | flink程序执行需要执行环境上下文。执行环境为其要执行的操作算子,数据源,数据sinks都是设置了默认的并行度。 32 | 执行环境的并行度可以通过操作算子显示指定并行度来覆盖掉。 33 | 默认的执行环境并行度可以通过调用setParallelism()来设置。例如,操作算子,数据源,数据接收器,并行度都设置为3,那么在执行环境层面,设置方式如下: 34 | ```scala 35 | val env = StreamExecutionEnvironment.getExecutionEnvironment 36 | env.setParallelism(3) 37 | 38 | val text = [...] 39 | val wordCounts = text 40 | .flatMap{ _.split(" ") map { (_, 1) } } 41 | .keyBy(0) 42 | .timeWindow(Time.seconds(5)) 43 | .sum(1) 44 | wordCounts.print() 45 | 46 | env.execute("Word Count Example") 47 | ``` 48 | 49 | ### 1.3 客户端层 50 | 51 | 在提交job 到flink的时候,在客户端侧也可以设置flink的并行度。客户端即可以是java工程,也可以是scala工程。Flink的Command-line Interface (CLI)就是这样一种客户端。 52 | 在客户端侧flink可以通过-p参数来设置并行度。例如: 53 | ```scala 54 | ./bin/flink run -p 10 ../examples/*WordCount-java*.jar 55 | 56 | ``` 57 | 58 | 在java/scala客户端,并行度设置方式如下: 59 | ```scala 60 | 61 | try { 62 | PackagedProgram program = new PackagedProgram(file, args) 63 | InetSocketAddress jobManagerAddress = RemoteExecutor.getInetFromHostport("localhost:6123") 64 | Configuration config = new Configuration() 65 | 66 | Client client = new Client(jobManagerAddress, new Configuration(), program.getUserCodeClassLoader()) 67 | 68 | // set the parallelism to 10 here 69 | client.run(program, 10, true) 70 | 71 | } catch { 72 | case e: Exception => e.printStackTrace 73 | } 74 | ``` 75 | ### 1.4 系统层面 76 | 77 | 系统层面的并行度设置,会针对所有的执行环境生效,可以通过parallelism.default,属性在conf/flink-conf.yaml文件中设置。 78 | 79 | ## 2. 设置最大并行度 80 | 设置最大并行度,实际上调用的方法是setMaxParallelism(),其调用位置和setParallelism()一样。 81 | 默认的最大并行度是近似于operatorParallelism + (operatorParallelism / 2),下限是127,上线是32768. 82 | 83 | 值得注意的是将最大的并行的设置为超级大的数可能会对性能造成不利的影响,因为一些状态后端是必须要保存内部数据结构的,这个数据结构跟key-group数量相匹配(这是可重定状态的内部实现机制)。 84 | 配置taskmanager的处理slot 85 | flink通过将项目分成tasks,来实现并行的执行项目,划分的tasks会被发到slot去处理。 86 | 集群中Flink的taskmanager提供处理slot。Slots数量最合适的是跟taskmanager的cores数量成正比。当然,taskmanager.numberOfTaskSlots的推荐值就是cpu核心的数目。 87 | 当启动一个任务的时候,我们可以为其提供默认的slot数目,其实也即是flink工程的并行度,设置方式在上面已经有详细介绍。 88 | 89 | 90 | -------------------------------------------------------------------------------- /doc/ManagExecution/RestartStrategies.md: -------------------------------------------------------------------------------- 1 | 2 | # RestartStrategies 3 | 4 | Flink支持不同的重启策略,可以控制在发生故障时如何重新启动作业。 可以使用默认重新启动策略启动集群,该策略在未定义任何特定的作业的重新启动策略时始终使用。 5 | 如果使用重新启动策略提交作业,此策略将覆盖群集的默认设置。 6 | 7 | ## 1. 概览 8 | 9 | Flink的默认重启策略可以通过flink-conf.yaml配置。前提是必须开启checkpoint,如果checkpoint未开启,将不会有执行策略这个概念。如果checkpoint被激活了同时重启策略没有配置, 10 | 那么固定延迟策略将使用Integer.MAX_VALUE作为重启尝试次数。 11 | 12 | 可以通过ExecutionEnvironment或者StreamExecutionEnvironment 调用setRestartStrategy设置重启策略。 13 | 14 | 下面举个例子,重启三次,每次间隔10s钟。 15 | ```java 16 | ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); 17 | env.setRestartStrategy(RestartStrategies.fixedDelayRestart( 18 | 3, // number of restart attempts 19 | Time.of(10, TimeUnit.SECONDS) // delay 20 | )); 21 | ``` 22 | 23 | ## 2. 重启策略详解 24 | 25 | ## 2.1 固定延迟重启策略 26 | 27 | 固定延迟重启策略用给定重启次数重启job。一旦执行次数达到了最大限制,job最终也会失败。在两次重拾中间有一个固定的时间间隔。 28 | 29 | flink-conf.yaml里面默认配置了该策略。 30 | 31 | ``` 32 | restart-strategy: fixed-delay 33 | restart-strategy.fixed-delay.attempts: 3 34 | restart-strategy.fixed-delay.delay: 10 s 35 | ``` 36 | 37 | restart-strategy.fixed-delay.attempts: 38 | 默认值是1或者如果开启了checkpoint那么值是Integer.MAX_VALUE。 39 | 该配置就是控制job失败后尝试的最大次数。 40 | 41 | restart-strategy.fixed-delay.delay: 42 | 默认值是akka.ask.timeout或者开启checkpoint的话就是10s钟。 43 | 44 | 该变量控制两次尝试重启的时间间隔。 45 | 46 | 在配置文件里配置就比较固定,适合默认配置。如果每个应用程序想有自己的特殊的配置,也可以在代码里编程,如下: 47 | ```java 48 | 49 | ExecutionEnvironmentExecu env = ExecutionEnvironment.getExecutionEnvironment(); 50 | env.setRestartStrategy(RestartStrategies.fixedDelayRestart( 51 | 3, // number of restart attempts 52 | Time.of(10, TimeUnit.SECONDS) // delay 53 | )); 54 | ``` 55 | 56 | ## 2.2 故障率重启策略 57 | 58 | 故障率重启策略在故障后重新启动作业,但是当超过故障率(每个时间间隔的故障)时,作业最终会失败。 在两次连续重启尝试之间,重启策略等待一段固定的时间。 59 | 60 | 通过在flink-conf.yaml中设置以下配置参数,默认启用此策略。 61 | 62 | ``` 63 | restart-strategy: failure-rate 64 | restart-strategy.failure-rate.max-failures-per-interval: 3 65 | restart-strategy.failure-rate.failure-rate-interval: 5 min 66 | restart-strategy.failure-rate.delay: 10 s 67 | ``` 68 | 69 | * restart-strategy.failure-rate.max-failures-per-interval: 70 | 71 | 在job宣告最终失败之前,在给定的时间间隔内重启job的最大次数。默认值是 1 。 72 | 73 | * restart-strategy.failure-rate.failure-rate-interval: 74 | 75 | 测量故障率的时间间隔。默认是1min。 76 | 77 | * restart-strategy.failure-rate.delay: 78 | 79 | 在两次连续重试之间的延时。默认值是akka.ask,timeout。 80 | 81 | 当然,该策略也可以在代码里定制: 82 | ```java 83 | ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); 84 | env.setRestartStrategy(RestartStrategies.failureRateRestart( 85 | 3, // max failures per interval 86 | Time.of(5, TimeUnit.MINUTES), //time interval for measuring failure rate 87 | Time.of(10, TimeUnit.SECONDS) // delay 88 | )); 89 | ``` 90 | 91 | ## 2.3 无重启策略 92 | 93 | 可以在flink-conf.yaml中将flink的重启策略设置为none。 94 | 95 | 也可以在代码里为每个job单独设置: 96 | ```java 97 | ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); 98 | env.setRestartStrategy(RestartStrategies.noRestart()); 99 | ``` 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /doc/Operators/AsynchronousIO.md: -------------------------------------------------------------------------------- 1 | 2 | 本节介绍flink的外部数据存储的异步I/O的API相关使用内容。 3 | 4 | 注:有关异步I/O实用程序的设计和实现的详细信息,请参阅提议和设计文档FLIP-12:异步I / O设计和实现。 5 | https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=65870673 6 | 7 | ## 需要异步I / O操作 8 | 9 | 当与外部系统交互时(例如,当使用存储在数据库中的数据来丰富流事件时),需要注意与外部系统的通信延迟不会影响流应用程序的整体工作。 10 | 11 | 直接访问外部数据库中的数据,例如在MapFunction中,通常意味着同步交互:向数据库发送请求,并且MapFunction等待直到收到响应。 12 | 在许多情况下,这种等待占据了函数的绝大部分时间。 13 | 14 | 与数据库的异步交互意味着单个并行函数实例可以同时处理许多请求并同时接收响应。 这样,可以通过发送其他请求和接收响应来覆盖等待时间。 15 | 至少,等待时间在多个请求上均摊。 这会使得大多数情况下流量吞吐量更高。 16 | 17 | ![image](../../pic/stream/needAsyncIO.png) 18 | 19 | 注意:通过将MapFunction扩展到非常高的并行度来提高吞吐量在某些情况下也是可能的, 20 | 但通常需要非常高的资源成本:拥有更多并行MapFunction实例意味着更多的任务,线程,Flink内部网络连接,数据库链接,缓冲区和通用内部bookkeeping开销。 21 | 22 | 23 | ## 先决条件 24 | 25 | 如上一节所示,对数据库(或key/value存储)实现适当的异步I/O需要客户端访问支持异步请求的数据库。 许多流行的数据库提供这样的客户端 26 | 27 | 在没有这样的客户端的情况下,可以通过创建多个客户端并使用线程池处理同步调用来尝试将同步客户端转变为有限的并发客户端。 但是,这种方法通常比适当的异步客户端效率低。 28 | 29 | ## 异步I / O API 30 | 31 | Flink的Async I/O API允许用户将异步请求客户端与数据流一起使用。 API处理与数据流的集成,以及处理顺序,事件时间,容错等。 32 | 33 | 假设有一个目标数据库的异步客户端,则需要三个部分来实现对数据库的异步I/O流转换: 34 | 35 | * 调度请求的AsyncFunction的实现 36 | 37 | * 一个回调,它接受操作的结果并将其交给ResultFuture 38 | 39 | * 在DataStream上应用异步I/O操作作为转换 40 | 41 | 以下代码示例说明了基本模式: 42 | 43 | ```java 44 | 45 | // This example implements the asynchronous request and callback with Futures that have the 46 | // interface of Java 8's futures (which is the same one followed by Flink's Future) 47 | 48 | /** 49 | * An implementation of the 'AsyncFunction' that sends requests and sets the callback. 50 | */ 51 | class AsyncDatabaseRequest extends RichAsyncFunction> { 52 | 53 | /** The database specific client that can issue concurrent requests with callbacks */ 54 | private transient DatabaseClient client; 55 | 56 | @Override 57 | public void open(Configuration parameters) throws Exception { 58 | client = new DatabaseClient(host, post, credentials); 59 | } 60 | 61 | @Override 62 | public void close() throws Exception { 63 | client.close(); 64 | } 65 | 66 | @Override 67 | public void asyncInvoke(String key, final ResultFuture> resultFuture) throws Exception { 68 | 69 | // issue the asynchronous request, receive a future for result 70 | final Future result = client.query(key); 71 | 72 | // set the callback to be executed once the request by the client is complete 73 | // the callback simply forwards the result to the result future 74 | CompletableFuture.supplyAsync(new Supplier() { 75 | 76 | @Override 77 | public String get() { 78 | try { 79 | return result.get(); 80 | } catch (InterruptedException | ExecutionException e) { 81 | // Normally handled explicitly. 82 | return null; 83 | } 84 | } 85 | }).thenAccept( (String dbResult) -> { 86 | resultFuture.complete(Collections.singleton(new Tuple2<>(key, dbResult))); 87 | }); 88 | } 89 | } 90 | 91 | // create the original stream 92 | DataStream stream = ...; 93 | 94 | // apply the async I/O transformation 95 | DataStream> resultStream = 96 | AsyncDataStream.unorderedWait(stream, new AsyncDatabaseRequest(), 1000, TimeUnit.MILLISECONDS, 100); 97 | 98 | ``` 99 | 100 | 重要说明:ResultFuture在第一次调用ResultFuture.complete时完成。 随后的所有完整调用都将被忽略。 101 | 102 | 以下两个参数控制异步操作: 103 | 104 | * 超时:超时定义异步请求在被视为失败之前可能需要多长时间。 此参数可防止dead/failed的请求。 105 | 106 | * 容量:此参数定义可能同时有多少异步请求正在进行中。 尽管异步I/O方法通常会带来更好的吞吐量,但操作算子仍然可能成为流应用程序的瓶颈。 107 | 限制并发请求的数量可确保操作算子不会累积不断增加的待处理请求积压,但一旦容量耗尽就会触发反压。 108 | 109 | ## 超时处理 110 | 111 | 当异步I/O请求超时时,默认情况下会引发异常并重新启动作业。如果要处理超时,可以覆盖AsyncFunction#timeout方法。 112 | 113 | ## 结果的顺序 114 | 115 | AsyncFunction发出的并发请求经常以某种未定义的顺序完成,具体取决于首先完成的请求。为了控制发出结果记录的顺序,Flink提供了两种模式: 116 | 117 | * Unordered:异步请求完成后立即发出结果记录。在异步I/O运算符之后,流中记录的顺序与之前不同。当使用处理时间作为基本时间特性时,此模式具有最低延迟和最低开销。 118 | 对此模式使用AsyncDataStream.unorderedWait(...)。 119 | 120 | * Ordered:在这种情况下,保留流顺序。结果记录的发出顺序与触发异步请求的顺序相同(运算符输入记录的顺序)。 121 | 为此,操作算子缓冲结果记录,直到其所有先前记录被发出(或超时)。这通常会在检查点中引入一些额外的延迟和一些开销,因为与无序模式相比,记录或结果在检查点状态下保持更长的时间。 122 | 对此模式使用AsyncDataStream.orderedWait(...)。 123 | 124 | ## 事件时间 125 | 126 | 当流应用程序与事件时间一起工作时,异步I / O操作符将正确处理watermark。这意味着两种顺序模式具体如下: 127 | 128 | * Unordered:watermark不会超过记录,反之亦然,这意味着watermark建立了一个顺序边界。记录仅在watermark之间无序发出。 129 | 只有在发出watermark后才会发出某个watermark后发生的记录。反过来,只有在输入的所有结果记录发出之后才会发出watermark。 130 | 131 | 这意味着在存在watermark的情况下,无序模式会引入一些与有序模式相同的延迟和管理开销。开销量取决于watermark频率。 132 | 133 | * Ordered:保留记录的watermark顺序,就像保留记录之间的顺序一样。与处理时间相比,开销没有显着变化。 134 | 135 | 请记住,注入时间是事件时间的一种特殊情况,其中自动生成的watermark基于源处理时间。 136 | 137 | ## 容错保证 138 | 139 | 异步I/O运算符提供完全一次的容错保证。它将检查点中的传输中异步请求的记录存储起来,并在从故障中恢复时恢复/重新触发请求。 140 | 141 | ## 实现技巧 142 | 143 | 对于有一个Executor(或scala的执行上下文-ExecutionContext)Future回调的实现,建议使用一个DirectExecutor,因为回调通常做最少的工作,此外DirectExecutor避免了额外的线程到线程切换的开销。 144 | 回调通常只将结果传递给ResultFuture,后者将其添加到输出缓冲区。从那里开始,包括记录发射和与检查点簿记交互的重要逻辑无论如何都发生在专用线程池中。 145 | 146 | DirectExecutor可以通过org.apache.flink.runtime.concurrent.Executors.directExecutor()或com.google.common.util.concurrent.MoreExecutors.directExecutor()获得。 147 | 148 | ## 警告 149 | AsyncFunction不能叫做多线程 150 | 151 | 想在这里明确指出的常见混淆是AsyncFunction不是以多线程方式调用的。只存在一个AsyncFunction实例,并且为流的相应分区中的每个记录顺序调用它。 152 | 除非asyncInvoke(...)方法返回快速并依赖于回调(由客户端),否则它将不会导致正确的异步I/O. 153 | 154 | 例如,以下模式导致阻塞asyncInvoke(...)函数,从而使异步行为无效: 155 | 156 | * 使用lookup/query方法调用数据库客户端会阻塞直到收到结果为止 157 | 158 | * 阻塞/等待异步客户端在asyncInvoke(...)方法中返回的future-type对象 159 | -------------------------------------------------------------------------------- /doc/Operators/join.md: -------------------------------------------------------------------------------- 1 | 2 | ## window join 3 | 4 | 窗口join 是对同一窗口相同key的元素进行join。来子两个流的元素传递给用户自定义JoinFunction 或者 FlatJoinFunction ,然后由其发出join的结果。 5 | 6 | 7 | 基本使用概括如下: 8 | ```java 9 | stream.join(otherStream) 10 | .where() 11 | .equalTo() 12 | .window() 13 | .apply() 14 | ``` 15 | 16 | 关于语义的一些提示: 17 | 18 | * 窗口join表现很想innerjoin,只有一方存在的key不会作为结果发出。 19 | 20 | * 那些join的元素将在其时间戳中包含仍位于相应窗口中的最大时间戳。 例如,以[5,10]作为其边界的窗口将导致join的元素使用9作为其时间戳。 21 | 在下一节中,我们将使用一些示例性场景概述不同类型的窗口连接的行为。 22 | 23 | 24 | ## tumbling window join 25 | 26 | tumbling window join翻译成滚动窗口。 27 | 28 | 执行滚动窗口连接时,对具有相同key和相同滚动窗口的所有元素进行成对组合,并将结果传递给JoinFunction或FlatJoinFunction。 29 | 因为它的行为类似于内连接,所以不会发出只在一个流存在的元素! 30 | 31 | ![image](../../pic/stream/TumbleWindowsJoin.png) 32 | 33 | 如图所示,我们定义了一个大小为2毫秒的翻滚窗口,这形成形式为[0,1],[2,3],......的窗口。图中显示了在每个窗口中的所有元素成对组合,最终将传递给JoinFunction。 34 | 注意,在翻滚窗口[6,7]中没有发出任何东西,因为在绿色流中没有元素存在来与橙色元素⑥和⑦join。 35 | 36 | ```java 37 | import org.apache.flink.api.java.functions.KeySelector; 38 | import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; 39 | import org.apache.flink.streaming.api.windowing.time.Time; 40 | 41 | ... 42 | 43 | DataStream orangeStream = ... 44 | DataStream greenStream = ... 45 | 46 | orangeStream.join(greenStream) 47 | .where() 48 | .equalTo() 49 | .window(TumblingEventTimeWindows.of(Time.seconds(2))) 50 | .apply (new JoinFunction (){ 51 | @Override 52 | public String join(Integer first, Integer second) { 53 | return first + "," + second; 54 | } 55 | }); 56 | ``` 57 | 58 | 59 | ## sliding window join 60 | 61 | sliding window join翻译成滑动窗口join。 62 | 63 | 执行滑动窗口时,所有具有相同key和存在于相同滑动窗口的元素将会被成对组合,然后传递给JoinFunction 或者 FlatJoinFunction。 64 | 在当前滑动窗口中,假如一个流存在某个元素而另一个流不存在,将不会发出该元素。请注意,某些元素可能在一个滑动窗口中join而在另一个滑动窗口中不join。 65 | 66 | ![image](../../pic/stream/SlideWindowsJoin.png) 67 | 68 | 在这个例子中,我们使用大小为2毫秒的滑动窗口并将每毫秒滑动一次,从而产生滑动窗口[-1,0],[0,1],[1,2],[2,3] ,...... x轴下方的连接元素是传递给每个滑动窗口的JoinFunction的元素。 69 | 在这里你还可以看到例如橙色②如何与窗口[2,3]中的绿色③join,但是没有与窗口[1,2]中的任何东西join。 70 | 71 | ```java 72 | import org.apache.flink.api.java.functions.KeySelector; 73 | import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows; 74 | import org.apache.flink.streaming.api.windowing.time.Time; 75 | 76 | 77 | 78 | DataStream orangeStream = ... 79 | DataStream greenStream = ... 80 | 81 | orangeStream.join(greenStream) 82 | .where() 83 | .equalTo() 84 | .window(SlidingEventTimeWindows.of(Time.milliseconds(2) /* size */, Time.milliseconds(1) /* slide */)) 85 | .apply (new JoinFunction (){ 86 | @Override 87 | public String join(Integer first, Integer second) { 88 | return first + "," + second; 89 | } 90 | }); 91 | ``` 92 | ## session window join 93 | 94 | session window join可以翻译成会话窗口join。 95 | 96 | 当执行会话窗口join时,具有相同key的所有元素在“组合”满足会话条件时以成对组合方式join并传递给JoinFunction或FlatJoinFunction。 97 | 由于执行内连接,因此如果有一个会话窗口只包含来自一个流的元素,则不会发出任何输出! 98 | 99 | ![image](../../pic/stream/SessionWindowsJoin.png) 100 | 101 | 这里我们定义一个会话窗口join,其中每个会话由至少1ms的间隙分割。 有三个会话,在前两个会话中,两个流的连接元素都传递给JoinFunction。 在第三阶段,绿色流中没有元素,所以⑧和⑨没有连接! 102 | 103 | ```java 104 | import org.apache.flink.api.java.functions.KeySelector; 105 | import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows; 106 | import org.apache.flink.streaming.api.windowing.time.Time; 107 | 108 | ... 109 | 110 | DataStream orangeStream = ... 111 | DataStream greenStream = ... 112 | 113 | orangeStream.join(greenStream) 114 | .where() 115 | .equalTo() 116 | .window(EventTimeSessionWindows.withGap(Time.milliseconds(1))) 117 | .apply (new JoinFunction (){ 118 | @Override 119 | public String join(Integer first, Integer second) { 120 | return first + "," + second; 121 | } 122 | }); 123 | ``` 124 | 125 | ## interval join 126 | 127 | interval join翻译成区间join 128 | 129 | 区间join使用公共key join两个流的元素(我们现在将它们称为A和B),并且流B的元素具有的时间戳位于流A中元素的时间戳的相对时间间隔中。 130 | 131 | 这也可以更正式地表达为b.timestamp∈[a.timestamp + lowerBound; a.timestamp + upperBound]或a.timestamp + lowerBound <= b.timestamp <= a.timestamp + upperBound 132 | 133 | 其中a和b是共享相同key的A和B的元素。 只要下限总是小于或等于上限,下限和上限都可以是负数或正数。 区间join当前仅执行内连接。 134 | 135 | 当一对元素传递给ProcessJoinFunction时,将为它们分配两个元素的较大的时间戳(可以通过ProcessJoinFunction.Context访问)。 136 | 137 | 注意区间join当前仅支持事件时间。 138 | 139 | ![image](../../pic/stream/IntervalJoin.png) 140 | 141 | 142 | 在上面的例子中,我们连接两个流'orange'和'green',下限为-2毫秒,上限为+1毫秒。 143 | 默认情况下,这些边界是包括在内的,但可以应用.lowerBoundExclusive()和.upperBoundExclusive来更改行为。 144 | 145 | 再次使用更正式的表示法,这将转化为 146 | 147 | orangeElem.ts + lowerBound <= greenElem.ts <= orangeElem.ts + upperBound 148 | 149 | 如三角形所示。 150 | 151 | ```java 152 | import org.apache.flink.api.java.functions.KeySelector; 153 | import org.apache.flink.streaming.api.functions.co.ProcessJoinFunction; 154 | import org.apache.flink.streaming.api.windowing.time.Time; 155 | 156 | ... 157 | 158 | DataStream orangeStream = ... 159 | DataStream greenStream = ... 160 | 161 | orangeStream 162 | .keyBy() 163 | .intervalJoin(greenStream.keyBy()) 164 | .between(Time.milliseconds(-2), Time.milliseconds(1)) 165 | .process (new ProcessJoinFunction out) { 169 | out.collect(first + "," + second); 170 | } 171 | }); 172 | ``` -------------------------------------------------------------------------------- /doc/State & Fault Tolerance/Savepoints.md: -------------------------------------------------------------------------------- 1 | # 保存点 2 | 3 | ## 概述 4 | 保存点是外部存储的自包含检查点,可用于停止、恢复、更新 Flink 程序。 用户通过 Flink 的检查点机制来创建流程序状态的(非增量)快照,并将检查点数据和元数据写入外部文件系统。 5 | 6 | 本小节介绍了保存点的触发、恢复和处理所涉及的步骤。 有关 Flink 如何处理状态和故障的更多详细信息,请查看 Streaming Programs 页面中的 State。 7 | 8 | 注意:为了能够在不同作业版本和 不同Flink版本之间顺利升级,请务必查看assigning IDs to your operators相关内容 (https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/state/savepoints.html#assigning-operator-ids)的部分。 9 | 10 | ## 给算子分配 ID 11 | 12 | 为了将来能够升级你的程序,强烈建议用户根据本节内容做一些修改。 必须更改是通过该uid(String)方法手动指定算子 ID从而确定每个算子的状态。 13 | ``` 14 | DataStream stream = env. 15 | // Stateful source (e.g. Kafka) with ID 16 | .addSource(new StatefulSource()) 17 | .uid("source-id") // ID for the source operator 18 | .shuffle() 19 | // Stateful mapper with ID 20 | .map(new StatefulMapper()) 21 | .uid("mapper-id") // ID for the mapper 22 | // Stateless printing sink 23 | .print(); // Auto-generated ID 24 | ``` 25 | 26 | 如果用户没有给个算子分配 ID,则Flik会根据程序的结构自动给每个算子生成一个ID。 只要这些 ID 不变,用户就可以从保存点自动恢复。因此,强烈建议手动分配这些 ID。 27 | 28 | ## 保存点状态 29 | 用户可以将保存点视为Operator ID 到 每个有状态算子的映射: 30 | ``` 31 | Operator ID | State 32 | ------------+------------------------ 33 | source-id | State of StatefulSource 34 | mapper-id | State of StatefulMapper 35 | 36 | ``` 37 | 38 | 在上面的例子中,打印的sink是无状态的,因此包含在保存点状态中。 默认情况下,会将每一个保存点映射回新程序。 39 | 40 | ## 算子 41 | 用户可以使用命令行客户端触发保存点,取消带有保存点的作业,从保存点恢复和处理保存点。 42 | 43 | Flink> = 1.2.0版本可以使用 WebUI 从保存点恢复作业 44 | ### 触发保存点 45 | 触发保存点时,会创建一个新的保存点目录来存储数据和元数据。 用户可以通过修改默认配置或使用触发器命令指定存储路径(请参阅:targetDirectory参数)。 46 | 47 | 注意:存储路径必须是 JobManager(s)和 TaskManager(例如分布式文件系统上的位置)可访问的位置。 48 | 例如,使用FsStateBackend或RocksDBStateBackend: 49 | ``` 50 | # Savepoint target directory 51 | /savepoints/ 52 | 53 | # Savepoint directory 54 | /savepoints/savepoint-:shortjobid-:savepointid/ 55 | 56 | # Savepoint file contains the checkpoint meta data 57 | /savepoints/savepoint-:shortjobid-:savepointid/_metadata 58 | 59 | # Savepoint state 60 | /savepoints/savepoint-:shortjobid-:savepointid/... 61 | ``` 62 | 63 | 注意: 虽然看起来好像可以移动保存点,但由于_metadata文件中的绝对路径,目前无法进行保存。 请按照 FLINK-5778 取消此限制。 64 | 请注意,如果使用MemoryStateBackend,则元数据和保存点状态将存储在_metadata文件中。所以用户可以移动文件并从任何位置恢复。 65 | 66 | #### 触发保存点 67 | ``` 68 | $ bin/flink savepoint :jobId [:targetDirectory] 69 | ``` 70 | 上述命令将会为jobid 触发一个保存点,并返回创建的保存点的路径。 用户需要此路径来还原和部署保存点。 71 | 72 | ### 使用 YARN 触发保存点 73 | ``` 74 | $ bin/flink savepoint :jobId [:targetDirectory] -yid :yarnAppId 75 | ``` 76 | 上述命令将会为ID为jobId的job 和ID为yarnAppId的YARN application 触发一个保存点,并返回创建的保存点的路径。 77 | 78 | #### 取消保存点的作业 79 | ``` 80 | $ bin/flink cancel -s [:targetDirectory] :jobId 81 | ``` 82 | 83 | 上述命令将会为触发具有 ID 的作业的保存点:jobid,并取消作业。 此外,用户可以指定存储保存点的路径。该路径必须是 JobManager 和 TaskManager 可访问的。 84 | 85 | ## 从保存点恢复 86 | ``` 87 | $ bin/flink run -s :savepointPath [:runArgs] 88 | ``` 89 | 90 | 上述命令将会提交作业并指定要从中恢复的保存点。 用户可以指定保存点目录或_metadata文件的路径。 91 | 92 | ### 允许未恢复状态 93 | 默认情况下,resume 操作将尝试将保存点的所有状态映射回要恢复的程序。 如果删除了算子,则可以通过--allowNonRestoredState(short -n:) 选项跳过无法映射到新程序的状态: 94 | ``` 95 | $ bin/flink run -s :savepointPath -n [:runArgs] 96 | ``` 97 | 98 | ## 处理保存点 99 | 100 | ``` 101 | $ bin/flink savepoint -d :savepointPath 102 | ``` 103 | 104 | 上述命令将会删除存储在savepointPath的保存点。 105 | 106 | 请注意,可以通过常规文件系统操作手动删除保存点,而不会影响其他保存点或检查点(请记住,每个保存点都是自包含的)。 Flink 1.2以上版本可以使用上面的 savepoint 命令执行。 107 | 108 | ## 配置 109 | 110 | 用户可以通过state.savepoints.dir密钥配置默认保存点存储路径。 触发保存点时,保存点的元数据信息将会保存到该路径。 用户可以通过使用下面的命令指定存储路径来(请参阅:targetDirectory参数)。 111 | ``` 112 | # Default savepoint target directory 113 | state.savepoints.dir: hdfs:///flink/savepoints 114 | ``` 115 | 如果既未配置缺省值也未指定保存点存储路径,则触发保存点将失败。 116 | 117 | 注意:保存点存储路径必须是 JobManager(s)和 TaskManager(例如分布式文件系统上的位置)可访问的位置。 118 | ## 常问问题 119 | 120 | ### 我应该为所有的算子分配 ID 吗? 121 | 122 | 根据经验,是这样的。 严格地说,使用uid方法将 ID 分配给作业中的有状态算子就足够了,这样保存点就会仅包含有状态的算子,无状态的算子不是保存点的一部分。 123 | 124 | 在实际中,建议给所有算子分配ID,因为 Flink 的一些内置算子(如 Window 算子)也是有状态的,并且不清楚哪些内置算子实际上是有状态的,哪些不是。 如果用户完全确定该算子是无状态的,则可以跳过该uid方法。 125 | 126 | ### 如果我在工作中添加一个需要状态的新算子会怎么样? 127 | 128 | 当用户向作业添加新算子时,它将初始化为没有保存任何状态。保存点包含每个有状态算子的状态。 无状态算子不在保存点的范围内。 新算子的类似于无状态算子。 129 | 130 | ### 如果我删除一个状态的算子会怎么样? 131 | 132 | 默认从保存点恢复的时候,会尝试恢复所有的状态。从一个包含了被删除算子的状态的保存点进行作业恢复将会失败。 133 | 用户可以通过使用 run 命令设置--allowNonRestoredState(short :) 跳过从保存点(savepoint)进行恢复作业: 134 | ``` 135 | $ bin/flink run -s :savepointPath -n [:runArgs] 136 | ``` 137 | 138 | ### 如果我在工作中重新排序有状态算子会怎么样? 139 | 140 | 如果用户为这些算子分配了 ID,它们将照常恢复。 141 | 如果用户没有分配 ID,则重新排序后,有状态算子的自动生成 ID 很可能会更改。 这将导致用户无法从以前的保存点恢复。 142 | 143 | ### 如果我添加,删除或重新排序在我的工作中没有状态的算子会怎么样? 144 | 145 | 如果为有状态算子分配了 ID,则无状态算子不会影响保存点还原。 146 | 如果用户没有分配 ID,则重新排序后,有状态算子的自动生成 ID 很可能会更改。 这将导致用户从保存点恢复失败。 147 | 148 | ### 当我在恢复时更改程序的并行性时会怎么样? 149 | 150 | 如果使用 Flink 1.2.0以上版本触发保存点,并且没有使用 Checkpointed 等以及过期的 API,则只需从保存点恢复程序并指定新的并行度即可。 151 | 152 | 如果从 低于1.2.0的版本触发的保存点或使用过期的 API,则首先必须将作业和保存点迁移到 Flink 1.2.0以上版本,才能更改并行度。 请参阅升级作业和 Flink 版本指南(http://flink.iteblog.com/ops/upgrading.html)。 -------------------------------------------------------------------------------- /doc/State & Fault Tolerance/State Backends.md: -------------------------------------------------------------------------------- 1 | ## 状态后端 2 | 3 | 用Data Stream API编写的程序状态通常以各种形式保存: 4 | 5 | * Windows 会在收集元素或聚合之前触发 6 | * 转换函数可以使用键值对状态接口来存储值 7 | * 转换函数可以通过CheckpointedFunction接口确保局部变量的容错性 8 | 9 | 详情请参阅流 API 指南中的[State & Fault Tolerance](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/index.html)。 10 | 11 | 激活检查点时,检查点会持续保持此类状态,以防止数据丢失并始终如一地恢复。这个状态如何在内部表示,如何保持以及在何处保持取决于所选择的状态后端。 12 | 13 | ### 可用的状态后端 14 | 15 | Flink 捆绑了这些状态后端: 16 | 17 | * MemoryStateBackend 18 | * FsStateBackend 19 | * RocksDBStateBackend 20 | 21 | 如果没有修改默认配置,系统将使用MemoryStateBackend。 22 | 23 | #### MemoryStateBackend 24 | 25 | MemoryStateBackend将数据作为Java堆栈栈的对象在内部保存。键/值对状态和窗口算子包含有存储值和触发器等的哈希表。 26 | 27 | 在检查点上,此状态后端将对状态进行快照,并将其作为检查点确认消息的一部分发送到 JobManager(master),JobManager也将其存储在其堆栈上。 28 | 29 | 内存状态后端可以被配置为使用异步快照。尽管我们强烈鼓励使用异步快照来避免阻塞管道,但是请注意,目前默认情况下启用了此功能。为了禁用该特性,用户可以在构造函数中将对应的布尔标志设置为 false(这应该只用于调试)来实例化 MemoryStateBackend,例如: 30 | ``` 31 | new MemoryStateBackend(MAX_MEM_STATE_SIZE, false); 32 | ``` 33 | 34 | MemoryStateBackend 的局限性: 35 | 36 | * 默认情况下,每个状态的大小限制为 5 MB。 可以在 MemoryStateBackend 的构造函数中增加此值。 37 | * 无论配置的最大状态大小如何,状态都不能大于akka帧的大小(请参阅[配置](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/config.html))。 38 | * 聚合状态必须适合 JobManager 内存。 39 | 40 | 建议MemoryStateBackend 用于: 41 | 42 | * 本地开发和调试 43 | * 状态很少的作业,例如仅包含一次记录功能的作业(Map,FlatMap,Filter,...),卡夫卡的消费者需要很少的状态。 44 | 45 | #### FsStateBackend 46 | 47 | 该FsStateBackend 配置有文件系统URL(类型,地址,路径),如 “hdfs://namenode:40010/flink/checkpoints” 或者 “file:///data/flink/checkpoints”. 48 | 49 | FsStateBackend 将正在运行的数据保存在 TaskManager 的内存中。 在检查点时,它将状态快照写入配置的文件系统目录中。 最小元数据存储在 JobManager 的内存中(另外在高可用性模式下,存储在元数据检查点中)。 50 | 51 | 默认情况下,FsStateBackend 使用异步快照,以避免在编写状态检查点时阻塞处理管道。要禁用该特性,用户可以在构造函数中将相应的布尔标志设置为 false 来实例化 FsStateBackend,例如: 52 | 53 | ``` 54 | new FsStateBackend(path, false); 55 | ``` 56 | 建议FsStateBackend: 57 | 58 | * 具有大状态,长窗口,大键 / 值状态的作业。 59 | * 所有高可用性设置。 60 | 61 | #### RocksDBStateBackend 62 | 63 | 该RocksDBStateBackend 配置有文件系统 URL(类型,地址,路径),如 “hdfs://namenode:40010/flink/checkpoints” 或者 “file:///data/flink/checkpoints”. 64 | 65 | RocksDBStateBackend 将 [RocksDB](https://rocksdb.org/) 数据库中正在运行的数据保存在(默认情况下) TaskManager 数据目录中。 在检查点上,整个RocksDB数据库将被存储到检查点配置的文件系统和目录中。最小元数据存储在 JobManager 的内存中(在高可用性模式下,存储在元数据检查点中)。 66 | 67 | RocksDBStateBackend 始终执行异步快照。 68 | 69 | RocksDBStateBackend 的局限性在于: 70 | 71 | * 由于RocksDB的JNI桥接API基于byte[],因此每个密钥和每个值的最大支持大小为2^31个字节。 72 | 73 | 重要提示:在RocksDB中使用合并操作的状态(例如ListState)可以静默地累积 > 2^31字节的值大小,然后在下次检索时失败。 这是目前RocksDB JNI的一个限制。 74 | 75 | 我们建议RocksDBStateBackend: 76 | 77 | * 具有非常大的状态,长窗口,大键 / 值状态的作业。 78 | * 所有高可用性设置。 79 | 80 | 请注意,可以保留的状态量仅受可用磁盘空间量的限制。与将状态保持在内存中的 FsStateBackend 相比,这允许保持非常大的状态。 但是,这也意味着使用此状态后端可以实现的最大吞吐量更低。 对此后端的所有 读/写 都必须通过反/序列化来检索/存储状态对象,这比像基于堆栈栈的后端那样始终使用堆栈栈表示更要好。。 81 | 82 | RocksDBStateBackend 是目前唯一提供增量检查点的后端(详情见[Tuning Checkpoints and Large State](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/state/large_state_tuning.html))。 83 | 84 | ### 配置状态后端 85 | 86 | 如果没有修改默认配置,则默认状态后端是作业管理器。 如果要为群集上的所有作业建立不同的默认值,可以通过在 flink-conf.yaml 中定义新的默认状态后端来实现。 可以基于每个作业覆盖默认状态后端,如下所示。 87 | 88 | #### 设置每个作业状态后端 89 | 90 | 每个作业状态后端StreamExecutionEnvironment在作业上设置,如下例所示: 91 | 92 | ```java 93 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 94 | env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints")); 95 | ``` 96 | ```scala 97 | val env = StreamExecutionEnvironment.getExecutionEnvironment() 98 | env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints")) 99 | ``` 100 | 101 | #### 设置默认状态后端 102 | 103 | 104 | 默认的状态后端可以在flink-conf.yaml中通过使用配置密钥state.backend来进行配置 105 | 106 | 配置内容的可能包括 jobmanager(MemoryStateBackend),filesystem(FsStateBackend),rocksdb(RocksDBStateBackend),或实现 [FsStateBackendFactory](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/state/checkpoints.html#directory-structure) 的类的完全限定类名,例如RocksDBStateBackend的org.apache.flink.contrib.streaming.state.RocksDBStateBackendFactory。 107 | 108 | 该state.checkpoints.dir选项定义所有后端写入检查点数据和元数据文件的目录。详情请见[目录结构](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/state/checkpoints.html#directory-structure)。 109 | 110 | 配置文件中的示例部分可能如下所示: 111 | ``` 112 | # The backend that will be used to store operator state checkpoints 113 | 114 | state.backend: filesystem 115 | 116 | # Directory for storing checkpoints 117 | 118 | state.checkpoints.dir: hdfs://namenode:40010/flink/checkpoints 119 | ``` -------------------------------------------------------------------------------- /doc/State & Fault Tolerance/Tuning Checkpoints and Large State.md: -------------------------------------------------------------------------------- 1 | ## 调整检查点和大状态 2 | 该部分介绍如何配置和调整使用大状态的应用程序。 3 | 4 | ### 概观 5 | 6 | 要使 Flink 应用程序可以大规模可靠运行,必须满足两个条件: 7 | 8 | * 应用程序需要能够可靠地获取检查点 9 | 10 | * 在发生故障后,资源需要足以赶上输入数据流 11 | 12 | 第一部分讨论如何大规模获得良好的检查点。 最后一节介绍了有关规划使用多少资源的一些最佳实践。 13 | 14 | ### 监测状态和检查点 15 | 16 | 监视检查点行为的最简单方法是通过 UI监视检查点部分。[检查点监视](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/checkpoint_monitoring.html)的文档介绍了如何访问可用的检查点度量标准。 17 | 18 | 扩大检查点时特别感兴趣的两个数字是: 19 | 20 | * 操作员启动检查点的时间:此时间目前尚未直接公开,但对应于: 21 | 22 | checkpoint_start_delay = end_to_end_duration - synchronous_duration - asynchronous_duration 23 | 24 | 当触发检查点的时间一直非常高时,这意味着checkpoint barriers需要很长时间才能从源头移动到算子。 这通常表明系统在constant backpressure下运行。 25 | 26 | * 在对齐期间缓冲的数据量。对于一次性语义,Flink 在接收多个输入流的算子处对齐流,为该对齐缓冲一些数据。 缓冲的数据量理想地是低的 - 较高的缓冲数据量意味着检查点屏障是在非常不同的时间从不同的输入流接收的。 27 | 28 | 请注意,当存在瞬态背压,数据倾斜或网络问题时,此处指示的数字偶尔会很高。 但是,如果数字一直很高,则意味着 Flink 将许多资源放入检查点。 29 | 30 | ### 调整检查点 31 | 32 | 应用程序可以定期触发检查点。当检查点的完成时间比检查点间隔更长时,在进行中的检查点完成之前不会触发下一个检查点。默认情况下,一旦正在进行的检查点完成,将立即触发下一个检查点。 33 | 34 | 当检查点最终频繁占用超过基准时间间隔时(例如因为状态增长超过计划,或者存储检查点的存储速度暂时变慢),系统会不断地检查点(新的一次启动后立即进行检查)。这可能意味着在检查点上经常捆绑太多资源,而且算子的进展太少。这种行为对使用异步检查点状态的流应用程序的影响较小,但可能仍会对整体应用程序性能产生影响。 35 | 36 | 为防止出现这种情况,应用程序可以定义检查点之间的最短持续时间: 37 | 38 | ``` 39 | StreamExecutionEnvironment.getCheckpointConfig().setMinPauseBetweenCheckpoints(milliseconds) 40 | ``` 41 | 42 | 此持续时间是在最新检查点结束和下一个检查点开始之间必须经过的最小时间间隔。下图说明了这对检查点的影响。 43 | 44 | ![image](../../pic/CEP/checkpoint_tuning.PNG) 45 | 46 | 注意:可以配置应用程序(通过CheckpointConfig)以允许多个检查点同时进行。对于Flink中具有大状态的应用程序,这通常会将太多资源绑定到检查点。当手动触发保存点时,它可能与正在进行的检查点同时进行。 47 | 48 | ###调整网络缓冲区 49 | 50 | 在Flink 1.3之前,网络缓冲区数量的增加也会导致检查点时间增加,因为保留更多的正在运行的数据意味着检查点障碍将会被延迟。 从 Flink 1.3 开始,每个输入/输出通道使用的网络缓冲区数量是有限的,因此可以配置网络缓冲区而不会影响检查点时间(请参阅[网络缓冲区配置](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/config.html#configuring-the-network-buffers))。 51 | 52 | ### 尽可能使状态检查点异步 53 | 当状态是异步快照时,检查点比同步快照状态时更好地扩展。特别是在具有多个连接,协同功能或窗口的更复杂的流应用程序中,这可能会产生深远的影响。 54 | 55 | 要异步创建状态,应用程序必须做两件事: 56 | 57 | * 使用[Flink管理](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/state.html)的状态:托管状态表示Flink提供存储状态的数据结构。目前,这是真正的键控状态,这就好比接口背后抽象ValueState,ListState,ReducingState,... 58 | 59 | * 使用支持异步快照的状态后端。 在Flink 1.2中,只有RocksDB状态后端使用完全异步快照。 从Flink1.3开始,基于堆栈栈的状态后端也支持异步快照。 60 | 61 | 以上两点意味着大状态通常应保持为键控状态,而不是算子状态。 62 | 63 | ### 调整 RocksDB 64 | 许多大型 Flink 流应用程序的状态存储主力要是RocksDB State 后端。后端远远超出主存储器,可靠地存储的[键控状态](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/state.html)。 65 | 66 | 不幸的是,RocksDB的性能可能因配置而异,并且几乎没有关于如何正确调整 RocksDB 的文档。 例如,默认配置是针对 SSD 定制的,并且在旋转磁盘上执行次优。 67 | 68 | #### 增量检查点 69 | 70 | 与完整检查点相比,增量检查点可以显著缩短检查点时间,但可能增加恢复时间。增量检查点的核心思想是仅记录对先前完成的检查点的所有更改,而不是生成完整的状态后端,自包含备份。 像这样,增量检查点建立在先前的检查点上。Flink 以一种随时间自我整合的方式利用 RocksDB 的内部备份机制。 因此,Flink 中的增量检查点历史记录不会无限增长,并且最终会将旧检查点保存并自动修剪。` 71 | 72 | 虽然我们强烈建议对大型状态使用增量检查点,但请注意,这是一项新功能,目前默认情况下未启用。 为了启用该特性,用户可以在构造函数中对应的布尔标志设置为 true 来实例化 RocksDBStateBackend,例如: 73 | 74 | ``` 75 | RocksDBStateBackend backend = new RocksDBStateBackend(filebackend, true); 76 | ``` 77 | #### RocksDB 计时器 78 | 79 | 对于 RocksDB,用户可以选择计时器是存储在堆栈上(默认)还是存储在 RocksDB 中。 基于堆栈的定时器可以为较少数量的定时器提供更好的性能,而在 RocksDB 中存储定时器可提供更高的可扩展性,因为 RocksDB 中的定时器数量可能超过可用的主内存(溢出到磁盘)。 80 | 81 | 当使用RockDB作为状态后端时,可以通过 Flink 的配置通过选项键选择定时器存储的类型state.backend.rocksdb.timer-service.factory。 可能的选择是heap(在堆栈上存储定时器,默认)和rocksdb(在RocksDB中存储定时器)。 82 | 83 | 注意RocksDB状态后端/增量检查点/基于堆栈的定时器的组合当前不支持定时器状态的异步快照。其他状态如键控状态仍然是异步快照。请注意,这不是以前版本的回归,将通过FLINK-10026解决。 84 | 85 | #### 将选项传递给RocksDB 86 | ```java 87 | RocksDBStateBackend.setOptions(new MyOptions()); 88 | 89 | public class MyOptions implements OptionsFactory { 90 | 91 | @Override 92 | public DBOptions createDBOptions() { 93 | return new DBOptions() 94 | .setIncreaseParallelism(4) 95 | .setUseFsync(false) 96 | .setDisableDataSync(true); 97 | } 98 | 99 | @Override 100 | public ColumnFamilyOptions createColumnOptions() { 101 | 102 | return new ColumnFamilyOptions() 103 | .setTableFormatConfig( 104 | new BlockBasedTableConfig() 105 | .setBlockCacheSize(256 * 1024 * 1024) // 256 MB 106 | .setBlockSize(128 * 1024)); // 128 KB 107 | } 108 | } 109 | ``` 110 | 111 | #### 预定义选项 112 | 113 | Flink 为 RocksDB 提供了一些预定义的选项集合,用于不同的设置,例如可以设置 RocksDBStateBacked.setPredefinedOptions(PredefinedOptions.SPINNING_DISK_OPTIMIZED_HIGH_MEM)。 114 | 115 | 我们希望随着时间的推移积累更多这样的配置文件 当您发现一组运行良好且对某些工作负载具有代表性的选项时,请提供此类预定义选项配置文件。 116 | 117 | 注意:RocksDB是一个本机库,它直接从进程分配内存,而不是从JVM分配内存。 必须考虑分配给 RocksDB 的任何内存,通常是将 TaskManagers 的 JVM 堆栈大小减少相同的量。不这样做可能导致YARN/Mesos/etc 终止 JVM 进程以分配比配置更多的内存。 118 | 119 | ### 容量规划 120 | 121 | 本节讨论如何确定应该使用多少资源来使 Flink 作业可靠地运行。容量规划的基本经验法则是: 122 | 123 | * 正常运行应具有足够的容量,以便在恒定的背压下不运行。 有关如何检查应用程序是否在背压下运行的详细信息,请参阅[背压监测](https://ci.apache.org/projects/flink/flink-docs-release-1.6/monitoring/back_pressure.html)。 124 | 125 | * 提供一些额外资源,以在无故障时间运行程序不背压所需的资源之上。这些资源需要 “追赶” 在应用程序恢复期间积累的输入数据。这应该取决于恢复操作通常需要多长时间(这取决于需要在故障转移中加载到新的任务管理器中的状态的大小),以及该方案要求故障恢复的速度。 126 | 127 | 重要事项:应该在激活检查点的情况下建立基线,因为检查点会占用一些资源(例如网络带宽)。 128 | 129 | * 临时背压通常是可以的,并且是在负载峰值期间,追赶阶段期间或外部系统(写入水槽中)表现出暂时减速时执行流控制的重要部分。 130 | 131 | * 某些操作(如大窗口)导致其下游操作符的负载出现峰值:对于窗口,在建立窗口时,下游操作符可能没有什么事可做,而在发出窗口时,则有事可做。对下游并行性的规划需要考虑窗口发射多少以及需要以多快的速度处理这样的峰值。 132 | 133 | #### 要点: 134 | 135 | 为了以后添加资源,请确保将数据流程序的最大并行度设置为合理的数字。 最大并行度定义了在重新缩放程序时(通过保存点)设置程序并行度的高度。 136 | 137 | Flink 的内部簿记以最大并行度 - 许多关键组的粒度跟踪并行状态。 即使执行低并行度的程序,Flink 的设计也力求使其具有非常高的最大并行度值。 138 | 139 | ### 压缩 140 | Flink 为所有检查点和保存点提供可选压缩(默认:关闭)。 目前,压缩总是使用 [snappy 压缩算法(版本 1.1.4)](https://github.com/xerial/snappy-java),但我们计划在将来支持自定义压缩算法。 压缩适用于键控状态下的键组的粒度,即每个键组可以单独解压缩,这对于重新缩放很重要。 141 | 142 | 压缩可以通过以下方式激活ExecutionConfig: 143 | ``` 144 | ExecutionConfig executionConfig = new ExecutionConfig(); 145 | executionConfig.setUseSnapshotCompression(true); 146 | ``` 147 | 148 | 注意:压缩选项对增量快照没有影响,因为它们使用的是RocksDB的内部格式,它始终使用开箱即用的快速压缩。 149 | 150 | ## 任务本地恢复 151 | 152 | ### 动机 153 | 154 | 在 Flink 的检查点中,每个任务都会生成其状态的快照,然后将其写入分布式存储。 每个任务通过发送描述分布式存储中状态位置的句柄来确认成功将状态写入作业管理器。 反过来,作业管理器从所有任务中收集句柄并将它们捆绑到检查点对象中。 155 | 156 | 在恢复的情况下,作业管理器打开最新的检查点对象并将句柄发送回相应的任务,然后可以从分布式存储中恢复其状态。 使用分布式存储来存储状态有两个重要的优点。 首先,存储是容错的,其次,分布式存储中的所有状态都可以被所有节点访问,并且可以容易地重新分配(例如,用于重新分级)。 157 | 158 | 但是,使用远程分布式存储也有一个很大的缺点:所有任务必须通过网络从远程位置读取其状态。 在许多情况下,恢复可以将失败的任务重新安排到与上一次运行相同的任务管理器(当然还有例如机器故障),但我们仍然必须读取远程状态。 即使单台机器上只有很小的故障,这也可能导致大型状态的恢复时间过长。 159 | 160 | ### 途径 161 | 任务本地状态恢复正是针对这种长恢复时间的问题,主要思想如下:对于每个检查点,每个任务不仅将任务状态写入分布式存储,而且将状态快照的辅助副本保存在任务本地的存储器中(例如,在本地磁盘或内存中)。 请注意,快照的主存储必须仍然是分布式存储,因为本地存储不能确保节点故障下的持久性,也不能为其他节点提供访问以重新分发状态,此功能仍需要主副本。 162 | 163 | 但是,对于可以重新安排到先前位置进行恢复的每个任务,我们可以从辅助本地副本恢复状态,并避免远程读取状态的成本。 鉴于许多故障不是节点故障,并且节点故障通常一次只影响一个或很少的节点,很可能在恢复中大多数任务可以返回到它们先前的位置并且找到它们的本地状态。 这使得本地恢复有效减少恢复时间。 164 | 165 | 请注意,根据所选的状态后端和检查点策略,每个检查点可能会产生一些额外成本,用于创建和存储辅助本地状态副本。 例如,在大多数情况下,实现简单地将对分布式存储的写入复制到本地文件。 166 | 167 | ![image](../../pic/CEP/local_recovery.PNG) 168 | 169 | ## 主(分布式存储)和辅助(任务 - 本地)状态快照的关系 170 | 任务本地状态始终被视为辅助副本,检查点状态的基本事实是分布式存储中的主副本。 这对于检查点和恢复期间本地状态的问题有影响: 171 | 172 | * 对于检查点,主副本必须成功,并且生成辅助本地副本的失败不会使检查点失败。如果无法创建主副本,则检查点将失败,即使已成功创建辅助副本也是如此。 173 | 174 | * 只有主副本由作业管理器确认和管理,辅助副本由任务管理器拥有,其生命周期可以独立于其主副本。 例如,可以将 3 个最新检查点的历史记录保留为主副本,并仅保留最新检查点的任务本地状态。 175 | 176 | * 对于恢复,如果匹配的辅助副本可用,Flink 将始终首先尝试从任务本地状态恢复。 如果从辅助副本恢复期间出现任何问题,Flink 将透明地重试从主副本恢复任务。 如果主要和(可选)辅助副本失败,则导致恢复失败。在这种情况下,根据配置,Flink仍然可以回退到较旧的检查点。 177 | 178 | * 任务本地副本可能仅包含完整任务状态的一部分(例如,在写入一个本地文件时出现异常)。 在这种情况下,Flink 将首先尝试在本地恢复本地部分,从主副本恢复非本地状态。 主状态必须始终完整,并且是任务本地状态的超集。 179 | 180 | * 任务本地状态可以具有与主状态不同的格式,它们不需要是字节相同的。 例如,任务本地状态甚至可能是由堆栈对象组成的内存,而不是存储在任何文件中。 181 | 182 | * 如果任务管理器丢失,则其所有任务的本地状态将丢失。 183 | 184 | ### 配置任务本地恢复 185 | 186 | 默认情况下,任务本地恢复已取消激活,可以通过 Flink 的配置使用state.backend.local-recovery指定的密钥激活CheckpointingOptions.LOCAL_RECOVERY。 此配置可以设置为 true,也可以为 false(默认值)以禁用本地恢复。 187 | 188 | ### 有关不同状态后端的任务本地恢复的详细信息 189 | 190 | #### 限制: 191 | 目前,任务本地恢复仅涵盖键控状态后端。 键控状态通常是该状态最大的部分。 在不久的将来,我们还将涵盖算子的状态和计时器。 192 | 193 | 以下状态后端可以支持任务本地恢复。 194 | 195 | * FsStateBackend:键控状态支持任务本地恢复。实现将状态复制到本地文件。 这可能会引入额外的写入成本并占用本地磁盘空间。 将来,我们还可能提供一种将任务本地状态保存在内存中的功能。 196 | 197 | * RocksDBStateBackend:键控状态支持任务本地恢复。对于完整检查点,状态将复制到本地文件。这可能会引入额外的写入成本并占用本地磁盘空间。对于增量快照,本地状态基于RocksDB的本机检查点机制。此机制也用作创建主副本的第一步,这意味着在这种情况下,不会引入额外的成本来创建辅助副本。我们只是保留原生检查点目录,而不是在上传到分布式商店后删除它。此本地副本可以与RocksDB的工作目录共享活动文件(通过硬链接),因此对于活动文件,也不会为使用增量快照的任务本地恢复消耗额外的磁盘空间。 198 | 199 | ### 分配保留调度 200 | 任务本地恢复假设在失败时保留分配任务调度,其工作如下。 每个任务都会记住其先前的分配,并请求完全相同的插槽在恢复时重新启动。如果此插槽不可用,则任务将从资源管理器请求新的新插槽。这样,如果任务管理器不再可用,则无法返回其先前位置的任务将不会驱动其先前插槽中的其他恢复任务。 我们的理由是,当任务管理器不再可用时,前一个插槽只能消失,在这种情况下是一些 任务必须要求新的插槽。 通过我们的调度策略,我们可以为最大数量的任务提供从本地状态恢复的机会,并避免任务窃取其先前插槽之间的级联效应。 201 | 202 | 分配保留调度不适用于Flink的传统模式。 -------------------------------------------------------------------------------- /doc/State & Fault Tolerance/checkpoints.md: -------------------------------------------------------------------------------- 1 | # 检查点 2 | 3 | ## 概述 4 | 5 | 检查点在允许其状态和对应的流位置可恢复时,可使状态在Flink中具有容错性,从而使应用程序具有与正常运行时相同的语义。 6 | 7 | 有关如何为程序启用和配置检查点的信息,请参阅[Checkpointing](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/checkpointing.html)。 8 | 9 | ## 保留检查点 10 | 11 | 默认情况下,检查点仅用于恢复失败的job,并且不会保留。程序运行结束时会删除它们。但是,可以配置要保留的定期检查点。通过配置配置文件,可以在job失败或程序运行结束时保留的检查点。通过保留的检查点恢复失败的job。 12 | 13 | ``` 14 | CheckpointConfig config = env.getCheckpointConfig(); 15 | config.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); 16 | ``` 17 | 该ExternalizedCheckpointCleanup模式配置取消job时检查点发生的情况: 18 | * ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION:取消job时保留检查点。请注意,在这种情况下,必须在取消后手动清理检查点状态。 19 | * ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION:取消job时删除检查点。只有在job失败时,检查点状态才可用。 20 | 21 | ## 目录结构 22 | 23 | 与[保存点](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/state/savepoints.html)类似,检查点由元数据文件和一些其他数据文件组成,具体取决于state backend。 元数据文件和数据文件存储在state.checkpoints.dir配置文件所指定的存储路径中,也可以在代码中为每个job指定存储路径。 24 | 25 | ### 通过配置文件全局配置 26 | ``` 27 | state.checkpoints.dir: hdfs:///checkpoints/ 28 | ``` 29 | ### 在构造state backend时为每个job配置 30 | ``` 31 | env.setStateBackend(new RocksDBStateBackend("hdfs:///checkpoints-data/"); 32 | ``` 33 | ## 与保存点的差异 34 | 35 | 检查点与[保存点](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/state/savepoints.html)有一些差异。 36 | * 使用state backend特定的(低级)数据格式,可以是增量式的。 37 | * 不支持 Flink 的某些特定功能,如rescaling。 38 | 39 | ## 从保留的检查点恢复 40 | 41 | 通过使用检查点的元数据文件,可以从检查点恢复失败的job,就像从保存点恢复一样(请参阅[保存点恢复指南](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/cli.html#restore-a-savepoint))。请注意,如果元数据文件不是存储在默认路径,则jobmanager需要访问state.checkpoints.dir配置文件所指定的元数据文件的存储路径(请参阅[目录结构](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/state/checkpoints.html#directory-structure) )。 42 | ``` 43 | $ bin/flink run -s :checkpointMetaDataPath [:runArgs] 44 | ``` 45 | -------------------------------------------------------------------------------- /doc/batch/ClusterExecution.md: -------------------------------------------------------------------------------- 1 | ## 集群执行 2 | Flink程序可以分布运行在许多机器的集群上,将程序发送到集群执行有两种方式: 3 | 4 | ### 1. 命令行界面 5 | 命令行界面允许您将打包的程序(jar)提交到集群(或单机设置)。有关详细信息,请参阅[命令行界面](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/cli.html)文档。 6 | 7 | ### 2. 远程环境 8 | 远程环境允许您直接在集群上执行Flink Java程序,远程环境指向您希望在其上执行程序的集群。 9 | 10 | ### 3. Maven Dependency 11 | 如果您正在将您的程序开发成Maven项目,那么您必须使用这个依赖项添加flink-clients模块: 12 | ```xml 13 | 14 | org.apache.flink 15 | flink-clients_2.11 16 | 1.6.0 17 | 18 | ``` 19 | 20 | 示例,下面说明了远程环境的使用: 21 | 22 | ```java 23 | public static void main(String[] args) throws Exception { 24 | ExecutionEnvironment env = ExecutionEnvironment 25 | .createRemoteEnvironment("flink-master", 8081, "/home/user/udfs.jar"); 26 | 27 | DataSet data = env.readTextFile("hdfs://path/to/file"); 28 | 29 | data 30 | .filter(new FilterFunction() { 31 | public boolean filter(String value) { 32 | return value.startsWith("http://"); 33 | } 34 | }) 35 | .writeAsText("hdfs://path/to/result"); 36 | 37 | env.execute(); 38 | } 39 | ``` 40 | 注意,该程序包含用户定义的代码,因此需要一个JAR文件,其中附带代码的类。远程环境的构造函数获取到JAR文件的路径。 -------------------------------------------------------------------------------- /doc/batch/localExecution.md: -------------------------------------------------------------------------------- 1 | ## 0. 本地执行 2 | Flink可以运行在单台机器,甚至单台Java虚拟机里面,这样方便用户在本地去测试和调试Flink程序。 3 | 本节概述了Flink的本地执行机制,本地环境和执行器允许您在本地Java虚拟机中运行Flink程序,或者在任何JVM中作为现有程序的一部分,大多数示例都可以通过单击IDE的“Run”按钮在本地启动。 4 | 5 | Flink支持两种不同类型的本地执行方式: 6 | LocalExecutionEnvironment启动在整个Flink运行时,它包括一个JobManager和一个TaskManager,以及内存管理和在集群模式下执行的所有内部算法。 7 | CollectionEnvironment可在Java集合上执行Flink程序,这种模式不会启动在完整的Flink运行时,因此执行的开销非常低,而且是轻量级的。 8 | 例如可以执行DataSet.map()-转换操作将会通过将map()函数应用于Java列表中的所有元素。 9 | 10 | 11 | 12 | ### 1. 调试 13 | 14 | 如果您在本地运行Flink程序,你也可以像调试其他Java程序一样调试你的程序。 15 | 可以使用System.out.println()写出一些内部变量,或者使用调试器。 16 | 还可以在map()、reduce()和所有其他方法中设置断点。有关Java API中测试和本地调试工具,请参阅Java API文档中的调试部分。 17 | 18 | ### 2. Maven依赖 19 | 如果你在Maven项目中开发你的程序,你必须使用这个依赖项添加flink-clients模块: 20 | ```xml 21 | 22 | org.apache.flink 23 | flink-clients_2.11 24 | 1.6.0 25 | 26 | ``` 27 | 28 | ### 3. 本地环境 29 | LocalEnvironment是Flink程序的本地执行句柄,使用它在本地JVM中运行程序——独立运行或嵌入到其他程序中。 30 | 本地环境通过方法ExecutionEnvironment.createLocalEnvironment()实例化。 31 | 默认情况下,它将使用与您的计算机具有CPU内核(硬件上下文)一样多的本地线程来执行。 32 | 您可以选择指定所需的并行度,本地环境也可以使用 enableLogging()或 disablelelogging()配置为是否输出日志到控制台。 33 | 在大多数情况下,调用ExecutionEnvironment.getExecutionEnvironment()是更好的方式。当程序在本地(命令行窗口之外)启动时,该方法返回LocalEnvironment。 34 | 当[命令行窗口](https://ci.apache.org/projects/flink/flink-docs-release-1.6/ops/cli.html)调用程序时,该方法返回预配置的集群执行环境。 35 | 36 | 37 | ```java 38 | public static void main(String[] args) throws Exception { 39 | ExecutionEnvironment env = ExecutionEnvironment.createLocalEnvironment(); 40 | 41 | DataSet data = env.readTextFile("file:///path/to/file"); 42 | 43 | data.filter(new FilterFunction() { 44 | public boolean filter(String value) { 45 | return value.startsWith("http://"); 46 | } 47 | }) 48 | .writeAsText("file:///path/to/result"); 49 | 50 | JobExecutionResult res = env.execute(); 51 | } 52 | ``` 53 | 执行完成后返回的JobExecutionResult对象包含程序运行时和累加器结果,LocalEnvironment还允许将自定义配置值传递给Flink。 54 | 55 | ``` 56 | Configuration conf = new Configuration(); 57 | conf.setFloat(ConfigConstants.TASK_MANAGER_MEMORY_FRACTION_KEY, 0.5f); 58 | final ExecutionEnvironment env = ExecutionEnvironment.createLocalEnvironment(conf); 59 | ``` 60 | 61 | _注意:本地执行环境不会启动任何web前端来监视执行。_ 62 | 63 | 64 | ### 4. 集合环境 65 | 使用CollectionEnvironment在Java集合上执行是运行Flink程序的一个低开销方法,这种模式的典型用例是自动测试、调试和代码重用。 66 | 用户可以使用为批处理而实现的算法,也可以使用更具交互性的情况。Flink程序稍作修改的变体可以在Java应用服务器中用于处理发来的请求。 67 | 68 | 69 | **4.1 基于集合执行的框架** 70 | 71 | 72 | ```java 73 | public static void main(String[] args) throws Exception { 74 | // initialize a new Collection-based execution environment 75 | final ExecutionEnvironment env = new CollectionEnvironment(); 76 | 77 | DataSet users = env.fromCollection( /* get elements from a Java Collection */); 78 | 79 | /* Data Set transformations ... */ 80 | 81 | // retrieve the resulting Tuple2 elements into a ArrayList. 82 | Collection<...> result = new ArrayList<...>(); 83 | resultDataSet.output(new LocalCollectionOutputFormat<...>(result)); 84 | 85 | // kick off execution. 86 | env.execute(); 87 | 88 | // Do some work with the resulting ArrayList (=Collection). 89 | for(... t : result) { 90 | System.err.println("Result = "+t); 91 | } 92 | } 93 | ``` 94 | flink-examples-batch模块包含一个完整的示例,称为CollectionExecutionExample。 95 | 请注意基于集合的Flink程序的执行只能在适合JVM堆的小数据集上进行,集合上的执行不是多线程的,仅使用一个线程。 96 | 97 | -------------------------------------------------------------------------------- /doc/quickstark/CreateJavaProject.md: -------------------------------------------------------------------------------- 1 | 2 | ## java project 3 | 4 | 本小结主要是教你如何构建一个java project。 5 | 6 | ## requirements 7 | 8 | 构建工程前要求:Maven 3.0.4 (or higher) 并且 Java 8.x 9 | 10 | ## 创建工程 11 | 12 | ``` 13 | $ mvn archetype:generate \ 14 | -DarchetypeGroupId=org.apache.flink \ 15 | -DarchetypeArtifactId=flink-quickstart-java \ 16 | -DarchetypeVersion=1.6.0 17 | ``` 18 | 19 | 也可以执行。 20 | 21 | ``` 22 | curl https://flink.apache.org/q/quickstart.sh | bash -s 1.6.0 23 | ``` 24 | ## 查看你的工程 25 | 26 | 执行完上面的命令之后,会在你的工作目录下生成一个新的目录。 27 | 28 | ``` 29 | $ tree quickstart/ 30 | quickstart/ 31 | ├── pom.xml 32 | └── src 33 | └── main 34 | ├── java 35 | │ └── org 36 | │ └── myorg 37 | │ └── quickstart 38 | │ ├── BatchJob.java 39 | │ └── StreamingJob.java 40 | └── resources 41 | └── log4j.properties 42 | 43 | ``` 44 | 45 | 该样例工程是官方给出的,内部包含一个BatchJob和一个StreamingJob。 46 | 47 | ## 编译打包 48 | 49 | ```scala 50 | mvn clean package 51 | ``` 52 | 53 | 打包提交的时候需要在pom.xml里修改你的mainClass。 -------------------------------------------------------------------------------- /doc/quickstark/FlinkDeploy.md: -------------------------------------------------------------------------------- 1 | 2 | ## 1. Standalone 3 | 4 | ### 1.1 环境要求 5 | 1. 软件要求 6 | Flink运行与所有的类unix环境,例如,Linux,Mac osx和Cygwin,同时希望集群有一个master节点和一个或多个warker节点组成。在安装配置集群之前,请先确认 7 | 下面的软件已经安装配置。 8 | * Java 1.8.x 或者更高版本 9 | * ssh(必须运行sshd才能使用管理远程组件的Flink脚本) 10 | 11 | 如果你的集群不满足上面软件要求,就需要安装或者升级。 12 | 13 | 在所有群集节点上使用无密码SSH和相同的目录结构将允许您使用我们的脚本来控制所有内容。 14 | 15 | 2. JAVA_HOME 16 | 17 | Flink要求在主节点和所有工作节点上设置JAVA_HOME环境变量,并指向Java安装的目录。 18 | 19 | 可以通过env.java.home键在conf/flink-conf.yaml中设置此变量。 20 | 21 | ### 1.2 Flink安装 22 | 23 | 首先根据你现有的hadoop集群下载[下载flink](https://flink.apache.org/downloads.html) 24 | 25 | 1. 配置flink 26 | 27 | 下载解压之后,就需要在 conf/flink-conf.yaml配置集群。 28 | 29 | 将jobmanager.rpc.address设置为指向master节点。 还应该通过设置jobmanager.heap.mb和taskmanager.heap.mb来定义允许JVM在每个节点上分配的最大主内存量。 30 | 这些值以MB为单位。 如果某些工作节点具有要分配给Flink系统的更多主内存,则可以通过在这些特定节点上设置环境变量FLINK_TM_HEAP来覆盖默认值。 31 | 32 | 最后,必须提供集群中所有节点的列表,这些节点将用作工作节点。 因此,与HDFS配置类似,编辑文件conf/slaves并输入每个工作节点的IP/host。 每个工作节点稍后将运行TaskManager。 33 | 34 | 以下示例说明了具有三个节点(IP地址从10.0.0.1到10.0.0.3以及主机名master,worker1,worker2)的设置,并显示了配置文件的内容(需要在所有计算机上的相同路径上访问)): 35 | 36 | ![image](../../pic/WhatIsFlink/flinkCluster.png) 37 | 38 | Flink目录必须在同一路径下的每个worker上都可用。 您可以使用共享NFS目录,也可以将整个Flink目录复制到每个工作节点。 39 | 40 | [更多配置项](https://ci.apache.org/projects/flink/flink-docs-release-1.5/ops/config.html) 41 | 42 | 特别注意,下面列出几个非常重要的配置项。 43 | 44 | * 每个JobManager的可用内存量(jobmanager.heap.mb), 45 | * 每个TaskManager的可用内存量(taskmanager.heap.mb), 46 | * 每台机器的可用CPU数量(taskmanager.numberOfTaskSlots), 47 | * 集群中的CPU总数(parallelism.default), 48 | * 临时目录(taskmanager.tmp.dirs)。 49 | 50 | ### 1.3 启动Flink 51 | 52 | 以下脚本在本地节点上启动JobManager,并通过SSH连接到从属文件中列出的所有工作节点,以在每个节点上启动TaskManager。 53 | 54 | 假如,你在master节点,并且在Flink的目录下,可以执行: 55 | 56 | ``` 57 | bin/start-cluster.sh 58 | ``` 59 | 60 | 运行`stop-cluster.sh`脚本,可以停止Flink集群。 61 | 62 | ### 1.4 添加新的实例 63 | 64 | 可以使用bin/jobmanager.sh和bin/taskmanager.sh脚本将JobManager和TaskManager实例添加到正在运行的集群中。 65 | 66 | 添加JobManager 67 | ``` 68 | bin/jobmanager.sh ((start|start-foreground) cluster)|stop|stop-all 69 | ``` 70 | 71 | 添加TaskManager 72 | ``` 73 | bin/taskmanager.sh start|start-foreground|stop|stop-all 74 | ``` 75 | 76 | 牢记,这些命令是需要在要添加到集群中的新机器上执行的。 77 | 78 | ## 2. yarn 79 | 80 | -------------------------------------------------------------------------------- /doc/quickstark/what-is-flink.md: -------------------------------------------------------------------------------- 1 | # 1. Architecture 2 | 3 | flink是一个架构和分布式处理引擎,设计目的是有状态的处理有界流和无界流。flink可以运行与所有通用的集群管理器,以内存的速度进行计算并且支持任何规模部署。 4 | 5 | 下面,我们解释一下Flink架构的重要方面。 6 | 7 | ## 1.1 处理无界数据和有界数据 8 | 9 | 任何种类的数据都是以事件流的形式产生。信用卡交易,传感器测量,机器日志或网站或移动应用程序上的用户交互,所有这些数据都作为流生成。 10 | 11 | 数据可以作为有界流和无界流被处理。 12 | 1. 无界流意思很明显,只有开始没有结束。必须连续的处理无界流数据,也即是在事件注入之后立即要对其进行处理。不能等待数据到达了再去全部处理,因为数据是无界的并且永远不会结束数据注入。 13 | 处理无界流数据往往要求事件注入的时候有一定的顺序性,例如可以以事件产生的顺序注入,这样会使得处理结果完整。 14 | 15 | 2. 有界流,也即是有明确的开始和结束的定义。有界流可以等待数据全部注入完成了再开始处理。注入的顺序不是必须的了,因为对于一个静态的数据集,我们是可以对其进行排序的。有界流的处理也可以称为批处理。 16 | 17 | ![imgae](../../pic/WhatIsFlink/有界流无界流图示.png) 18 | 19 | flink擅长处理有界流和无界流数据集。精确的事件和状态控制可以使得flink可以运行任何针对无界流处理的应用。有界流数据通过为固定数据集特殊设计的算子和数据结构的处理,也表现出很好的性能。 20 | 21 | ## 1.2 可以部署与任何地方 22 | 23 | Flink是分布式系统,为了处理应用程序需要计算资源。Flink可以整合所有通用的集群资源管理器,比如yarn,mesos,kubernetes,同时也可以单独运行。 24 | 25 | 当部署flink应用的时候,flink会根据应用程序配置的并行度自动识别需要的资源并且向资源管理器申请相应的资源。如过发生故障,Flink会通过申请新的容器来替换掉失败的容器。无论是提交app或者是控制app进行的通讯都是经过rest调用的形式进行的。这使得flink可以很轻松的整合到很多环境。 26 | 27 | 28 | ## 1.3 运行任意规模的应用 29 | 30 | Flink可以运行任意规模的流式应用程序。应用会并发成数以千计的task,这些task在集群中分布式并行运行。因此,应用程序可以利用几乎无限量的CPU,主内存,磁盘和网络IO。 31 | 另外,flink可以保存非常大规模的应用状态。 32 | 33 | 其异步和增量检查点算法确保对处理延迟的影响最小,同时保证恰一次的状态一致性。 34 | 35 | ## 1.4 利用内存性能 36 | 37 | 有状态Flink应用程序针对本地状态访问进行了优化。任务状态始终保留在内存中,或者,如果状态大小超过可用内存,则保存在访问高效的磁盘上。 38 | 39 | 因此,任务通过访问本地(通常是内存中)状态来执行所有计算,从而产生非常低的处理延迟。 Flink通过定期和异步checkpoint本地状态到持久存储来保证在出现故障时的恰一次的状态一致性。 40 | 41 | 状态访问和存储的过程如下图: 42 | 43 | ![image](../../pic/WhatIsFlink/local-state.png) 44 | 45 | # 2. application 46 | 47 | ## 2.1 flink应用组成block 48 | 49 | Apache Flink是一个用于对无界和有界数据流进行有状态计算的框架。 Flink提供在不同抽象级别的API,并为常见用例提供专用库。 50 | 51 | 在这里,我们介绍Flink易于使用和富有表现力的API和库。 52 | 53 | 可以用流处理框架构建和执行的应用程序的类型由框架控制流,状态和时间的成度来决定。下文中,我们会对流程序的组成部分进行介绍,并讲解flink处理他们的方法。 54 | 55 | ### 2.1.1 Streams 56 | 57 | 显然,流是流处理的根本。但是,流可以具有不同的特征,这些特征会影响流的处理方式。Flink是一个多功能的处理框架,可以处理任何类型的流。 58 | 59 | * 有界和无界流:流可以是无界的,也可以是有界的。Flink具有处理无界流的复杂功能,但也有专门的操作算子来有效地处理有界流。 60 | 61 | * 实时处理和离线处理: 所有的数据都是按照流的形式产生。有两种处理数据的方式,也即是实时处理和缓存下来在进行离线处理。 62 | 63 | ### 2.1.2 state 64 | 65 | 很多流都是由状态的,当然也有些流仅仅是单独的处理事件,这些流是无状态的。运行基本业务逻辑的任何应用程序都需要记住事件或中间结果,以便在以后的时间点访问它们,例如在收到下一个事件时或在特定持续时间之后。 66 | 67 | ![image](../../pic/WhatIsFlink/function-state.png) 68 | 69 | 应用的状态是flink的一等公民。您可以通过观察Flink在状态处理环境中提供的所有功能来查看。 70 | 71 | * Multiple State Primitives(多状态原语): 72 | 73 | Flink为不同的数据结构提供状态原语,例如atomic values, lists, or maps. 开发人员可以根据函数的访问模式选择最有效的状态原语。 74 | 75 | * Pluggable State Backends(可插拔状态后端): 76 | 77 | 应用程序状态由可插拔状态后端管理和checkpoint。 Flink具有不同的状态后端,可以在内存或RocksDB中存储状态,RocksDB是一种高效的嵌入式磁盘数据存储。 也可以插入自定义状态后端。 78 | 79 | * Exactly-once state consistency(恰一次状态一致性): 80 | 81 | flink的checkpoint和recovery算法保证了应用状态在失败的情况下的一致性。因此,故障是透明处理的,不会影响应用程序的正确性。 82 | 83 | * Very Large State(非常大的状态): 84 | 85 | 由于其异步和增量检查点算法,Flink能够维持几TB的应用程序状态。 86 | 87 | * Scalable Applications(可扩展的应用程序): 88 | 89 | Flink通过将状态重新分配给更多或更少的工作人员来支持有状态应用程序的扩展。 90 | 91 | 92 | ### 2.1.3 Time 93 | 94 | 事件是流程序的另一个重要组成部分。大多数事件流都具有固有的时间语义,因为每个事件都是在特定时间点生成的。此外,许多常见的流计算基于时间,例如窗口聚合,会话化(sessionization),模式检测(pattern detection)和基于时间的join。 95 | 96 | Flink提供了一组丰富的与时间相关的特征。 97 | 98 | * 事件时间模式: 99 | 100 | 使用事件时间语义处理流的应用程序根据事件的时间戳计算结果。因此,无论是否处理记录的或实时的事件,事件时间处理都允许准确和一致的结果。 101 | 102 | * 支持watermark: 103 | 104 | Flink使用watermark来推断基于事件时间的应用中的时间。watermark也是一种灵活的机制,可以权衡结果的延迟和完整性。 105 | 106 | * 延迟数据处理: 107 | 108 | 当使用watermark在事件时间模式下处理流时,可能会发生在所有相关事件到达之前已完成计算。这类事件被称为迟发事件。 Flink具有多种处理延迟事件的选项,例如通过侧输出重新路由它们以及更新以前完成的结果。 109 | 110 | * 处理时间模式: 111 | 112 | 除了事件时间模式之外,Flink还支持处理时间语义,该处理时间语义执行由处理机器的系统时间触发计算。处理时间模式适用于具有严格的低延迟要求的某些应用,这些要求可以容忍近似结果。 113 | 114 | ## 2.2 分层API 115 | 116 | Flink提供三层API。 每个API在简洁性和表达性之间提供不同的权衡,并针对不同的用例。 117 | 118 | ![image](./pic/WhatIsFlink/api-stack.png) 119 | 120 | 我们简要介绍每个API,讨论它的应用程序,并展示一个代码示例。 121 | ### 2.2.1 ProcessFunctions 122 | 123 | ProcessFunctions是Flink提供的最具表现力的功能接口。 Flink提供ProcessFunctions来处理来自窗口中分组的单个事件 亦或者一个或两个输入流的单个事件。 124 | ProcessFunctions提供对时间和状态的细粒度控制。 ProcessFunction可以任意修改其状态并注册将在未来触发回调函数的定时器。 125 | 因此,ProcessFunctions可以实现许多有状态事件驱动应用程序所需的复杂的单事件业务逻辑。 126 | 127 | 以下示例显示了一个KeyedProcessFunction,它对KeyedStream进行操作并匹配START和END事件。 收到START事件时,该函数会记住其状态的时间戳,并内注册一个在四小时内的计时器。 128 | 如果在计时器触发之前收到END事件,则该函数计算END和START事件之间的持续时间,清除状态并返回该值。 否则,计时器只会触发并清除状态。 129 | ```java 130 | /** 131 | * Matches keyed START and END events and computes the difference between 132 | * both elements' timestamps. The first String field is the key attribute, 133 | * the second String attribute marks START and END events. 134 | */ 135 | public static class StartEndDuration 136 | extends KeyedProcessFunction, Tuple2> { 137 | 138 | private ValueState startTime; 139 | 140 | @Override 141 | public void open(Configuration conf) { 142 | // obtain state handle 143 | startTime = getRuntimeContext() 144 | .getState(new ValueStateDescriptor("startTime", Long.class)); 145 | } 146 | 147 | /** Called for each processed event. */ 148 | @Override 149 | public void processElement( 150 | Tuple2 in, 151 | Context ctx, 152 | Collector> out) throws Exception { 153 | 154 | switch (in.f1) { 155 | case "START": 156 | // set the start time if we receive a start event. 157 | startTime.update(ctx.timestamp()); 158 | // register a timer in four hours from the start event. 159 | ctx.timerService() 160 | .registerEventTimeTimer(ctx.timestamp() + 4 * 60 * 60 * 1000); 161 | break; 162 | case "END": 163 | // emit the duration between start and end event 164 | Long sTime = startTime.value(); 165 | if (sTime != null) { 166 | out.collect(Tuple2.of(in.f0, ctx.timestamp() - sTime)); 167 | // clear the state 168 | startTime.clear(); 169 | } 170 | default: 171 | // do nothing 172 | } 173 | } 174 | 175 | /** Called when a timer fires. */ 176 | @Override 177 | public void onTimer( 178 | long timestamp, 179 | OnTimerContext ctx, 180 | Collector> out) { 181 | 182 | // Timeout interval exceeded. Cleaning up the state. 183 | startTime.clear(); 184 | } 185 | } 186 | ``` 187 | 该示例说明了KeyedProcessFunction的表达能力,但也强调了它是一个相当冗长的接口。 188 | 189 | ### 2.2.2 The DataStream API 190 | 191 | DataStream API为许多常见的流处理操作提供原语,例如窗口化,一次记录转换以及通过查询外部数据存储来丰富事件。 192 | DataStream API可用于Java和Scala,它基于函数编程,例如map(),reduce()和aggregate()。 可以通过扩展接口或 作为Java或Scala lambda函数来定义函数。 193 | 194 | 以下示例显示如何对点击流进行会话并计算每个会话的点击次数。 195 | 196 | ```java 197 | // a stream of website clicks 198 | DataStream clicks = ... 199 | 200 | DataStream> result = clicks 201 | // project clicks to userId and add a 1 for counting 202 | .map( 203 | // define function by implementing the MapFunction interface. 204 | new MapFunction>() { 205 | @Override 206 | public Tuple2 map(Click click) { 207 | return Tuple2.of(click.userId, 1L); 208 | } 209 | }) 210 | // key by userId (field 0) 211 | .keyBy(0) 212 | // define session window with 30 minute gap 213 | .window(EventTimeSessionWindows.withGap(Time.minutes(30L))) 214 | // count clicks per session. Define function as lambda function. 215 | .reduce((a, b) -> Tuple2.of(a.f0, a.f1 + b.f1)); 216 | SQL & Table API 217 | ``` 218 | 219 | ### 2.2.3 SQL & Table API 220 | 221 | Flink有两个关系API,Table API和SQL。 两个API都是用于批处理和流处理的统一API,即,在无界的实时流或有界的记录流上以相同的语义执行查询,并产生相同的结果。 222 | Table API和SQL利用Apache Calcite进行解析,验证和查询优化。 它们可以与DataStream和DataSet API无缝集成,并支持用户定义的标量,聚合和表值函数。 223 | 224 | Flink的关系API旨在简化数据分析,数据pipeline和ETL应用程序的定义。 225 | 226 | 以下示例显示了用于对点击流进行会话并计算每个会话的点击次数的SQL查询。 这与DataStream API示例中的用例相同。 227 | 228 | ```sql 229 | SELECT userId, COUNT(*) 230 | FROM clicks 231 | GROUP BY SESSION(clicktime, INTERVAL '30' MINUTE), userId 232 | ``` 233 | 234 | ## 3. Libraries 235 | 236 | Flink具有几个用于常见数据处理用例的库。这些库通常嵌入在API中,而不是完全独立的。因此,他们可以从API的所有功能中受益,并与其他库集成。 237 | 238 | * 复杂事件处理(CEP): 239 | 240 | 模式检测是事件流处理的一个非常常见的用例。 Flink的CEP库提供了一个API来指定事件模式(想想正则表达式或状态机)。 241 | CEP库与Flink的DataStream API集成,以便在DataStream上评估模式。 CEP库的应用包括网络入侵检测,业务流程监控和欺诈检测。 242 | 243 | * DataSet API: 244 | 245 | DataSet API是Flink用于批处理应用程序的核心API。 DataSet API的原语包括map,reduce,(outer)join,co-group和iterate。 246 | 所有操作均由算法和数据结构支持,这些算法和数据结构对内存中的序列化数据进行操作,并在数据大小超过内存预算时溢出到磁盘。 247 | Flink的DataSet API的数据处理算法受到传统数据库运算符的启发,例如混合散列 hash-join 或外部合并排序。 248 | 249 | * Gelly: 250 | 251 | Gelly是一个可扩展的图形处理和分析库。 Gelly是在DataSet API之上实现的,并与DataSet API集成在一起。因此,它受益于其可扩展且强大的操作算子。 252 | Gelly具有内置算法,如标签传播,三角枚举和页面排名,但也提供了一个简化自定义图算法实现的Graph API。 253 | 254 | 255 | # 3. operations 256 | 257 | Apache Flink是一个用于对无界和有界数据流进行有状态计算的框架。由于许多流应用程序设计为以最短的停机时间连续运行,因此流处理器必须提供出色的故障恢复,以及在应用程序运行时监视和维护应用程序的工具。 258 | 259 | 260 | Apache Flink非常关注流处理的操作方面。 在这里,我们将解释Flink的故障恢复机制,并介绍其管理和监督正在运行的应用程序的功能。 261 | 262 | ## 3.1 7*24小时运行 263 | 264 | 机器和过程故障在分布式系统中无处不在。 像Flink这样的分布式流处理器必须从故障中恢复,以便能够24/7全天候运行流应用程序。 显然,这不仅意味着在故障后重新启动应用程序,而且还要确保其内部状态保持一致,以便应用程序可以继续处理,就像从未发生过故障一样。 265 | 266 | Flink提供了多种功能,以确保应用程序报纸运行并保持一致: 267 | 268 | * 一致的Checkpoint 269 | 270 | Flink的恢复机制基于应用程序状态的一致检查点。如果发生故障,将重新启动应用程序并从最新检查点加载其状态。结合可重置流源,此功能可以保证一次性状态一致性。 271 | 高效的检查点:如果应用程序保持TB级的状态,则检查应用程序的状态可能非常昂贵。 Flink可以执行异步和增量检查点,以便将检查点对应用程序的延迟SLA的影响保持在非常小的水平。 272 | 273 | * 端到端地恰一次: 274 | 275 | Flink为特定存储系统提供事务接收器,保证数据只写出一次,即使出现故障。 276 | 277 | * 与集群管理器集成: 278 | 279 | Flink与集群管理器紧密集成,例如Hadoop YARN,Mesos或Kubernetes。当进程失败时,将自动启动一个新进程来接管其工作。 280 | 281 | * 高可用性设置: 282 | 283 | Flink具有高可用性模式,可消除所有单点故障。 HA模式基于Apache ZooKeeper,这是一种经过验证的可靠分布式协调服务。 284 | 285 | ## 3.2 更新,迁移,暂停和恢复您的应用程序 286 | 287 | 需要维护为关键业务服务提供支持的流应用程序。 需要修复错误,并且需要改进或实现新功能。 但是,更新有状态流应用程序并非易事。 288 | 通常,人们不能简单地停止应用程序并重新启动固定版本或改进版本,因为人们无法承受丢失应用程序的状态。 289 | 290 | Flink的Savepoints是一个独特而强大的功能,可以解决更新有状态应用程序和许多其他相关挑战的问题。 Savepoints是应用程序状态的一致快照,因此与检查点非常相似。 291 | 但是,与检查点相比,需要手动触发Savepoints,并且在应用程序停止时不会自动删除Savepoints。 Savepoints可用于启动状态兼容的应用程序并初始化其状态。 292 | Savepoints可启用以下功能: 293 | 294 | * 应用程序的演变 295 | 296 | Savepoints可用于发展应用程序。可以从从先前版本的应用程序中获取的Savepoints重新启动应用程序的固定或改进版本。也可以从较早的时间点(假设存在这样的保存点)启动应用程序,以修复由有缺陷的版本产生的错误结果。 297 | 298 | * 群集迁移: 299 | 300 | 使用Savepoints,可以将应用程序迁移(或克隆)到不同的群集。 301 | 302 | * Flink版本更新: 303 | 304 | 可以使用Savepoints迁移应用程序以在新的Flink版本上运行。 305 | 306 | * 应用程序扩展: 307 | 308 | Savepoints可用于增加或减少应用程序的并行性。 309 | 310 | * A/B测试和What-If情景: 311 | 312 | 可以通过启动同一Savepoints的所有版本来比较两个(或更多)不同版本的应用程序的性能或质量。 313 | 314 | * 暂停和恢复: 315 | 316 | 可以通过获取Savepoints并停止它来暂停应用程序。在以后的任何时间点,都可以从Savepoints恢复应用程序。 317 | 318 | * 存档: 319 | 320 | Savepoints可以存档,以便能够将应用程序的状态重置为较早的时间点。 321 | 322 | # 4. 监控和控制您的应用程序 323 | 324 | 与任何其他服务一样,需要监视连续运行的流应用程序并将其集成到企业的运营架构(即,监视和日志记录服务)中。 325 | 监控有助于预测问题并提前做出反应。日志记录可以根本原因分析来调查故障。最后,控制运行应用程序的易于访问的界面是一个重要特性。 326 | 327 | Flink可以与许多常见的日志系统和监视服务很好地集成,并提供REST API来控制应用程序和查询信息。 328 | 329 | * Web UI: 330 | 331 | Flink具有Web UI,可以检查,监视和调试正在运行的应用程序。 它还可用于提交执行执行或取消执行。 332 | * 日志记录: 333 | 334 | Flink实现了流行的slf4j日志记录界面,并与日志框架log4j或logback集成。 335 | * 度量标准: 336 | 337 | Flink具有复杂的度量标准系统,用于收集和报告系统和用户定义的度量标准。 度量标准可以导出到reporter,包括JMX,Ganglia,Graphite,Prometheus,StatsD,Datadog和Slf4j。 338 | * REST API: 339 | 340 | Flink暴露REST API以提交新应用程序,获取正在运行的应用程序的Savepoints或取消应用程序。 REST API还暴露元数据和收集的运行或已完成应用程序的指标。 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | -------------------------------------------------------------------------------- /doc/streaming/overview.md: -------------------------------------------------------------------------------- 1 | #Flink DataStream API 编程指南 2 | 3 | Flink中的DataStream程序是对数据流进行转换(例如,过滤、更新状态、定义窗口、聚合)的常用方式。数据流来源于多种数据源(例如,消息队列,socket流,文件)。通过sinks返回结果,例如将数据写入文件或标准输出(如命令行终端)。Flink程序可以运行在各种上下文环境中,独立或嵌入于其他程序中。 4 | 执行过程可以在本地JVM也可以在由许多机器组成的集群上。 5 | 6 | 关于Flink API的基本概念介绍请参阅[基本概念]。 7 | 8 | 为了创建你的Flink DataStream程序,我们鼓励你从[解构Flink程序]开始,并逐渐添加你自己的[transformations]。本节其余部分作为附加操作和高级功能的参考。 9 | 10 | 11 | 12 | ##示例程序 13 | 14 | 以下是基于流式窗口进行word count的一个完整可运行的程序示例,它从一个宽度为5秒的网络socket窗口中统计单词个数。你可以复制并粘贴代码用以本地运行。 15 | 16 | 17 | *Java* 18 | ``` 19 | import org.apache.flink.api.common.functions.FlatMapFunction; 20 | import org.apache.flink.api.java.tuple.Tuple2; 21 | import org.apache.flink.streaming.api.datastream.DataStream; 22 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 23 | import org.apache.flink.streaming.api.windowing.time.Time; 24 | import org.apache.flink.util.Collector; 25 | 26 | public class WindowWordCount { 27 | 28 | public static void main(String[] args) throws Exception { 29 | 30 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 31 | 32 | DataStream> dataStream = env 33 | .socketTextStream("localhost", 9999) 34 | .flatMap(new Splitter()) 35 | .keyBy(0) 36 | .timeWindow(Time.seconds(5)) 37 | .sum(1); 38 | 39 | dataStream.print(); 40 | 41 | env.execute("Window WordCount"); 42 | } 43 | 44 | public static class Splitter implements FlatMapFunction> { 45 | @Override 46 | public void flatMap(String sentence, Collector> out) throws Exception { 47 | for (String word: sentence.split(" ")) { 48 | out.collect(new Tuple2(word, 1)); 49 | } 50 | } 51 | } 52 | 53 | } 54 | ``` 55 | 56 | 57 | *Scala* 58 | 59 | ``` 60 | import org.apache.flink.streaming.api.scala._ 61 | import org.apache.flink.streaming.api.windowing.time.Time 62 | 63 | object WindowWordCount { 64 | def main(args: Array[String]) { 65 | 66 | val env = StreamExecutionEnvironment.getExecutionEnvironment 67 | val text = env.socketTextStream("localhost", 9999) 68 | 69 | val counts = text.flatMap { _.toLowerCase.split("\\W+") filter { _.nonEmpty } } 70 | .map { (_, 1) } 71 | .keyBy(0) 72 | .timeWindow(Time.seconds(5)) 73 | .sum(1) 74 | 75 | counts.print 76 | 77 | env.execute("Window Stream WordCount") 78 | } 79 | } 80 | ``` 81 | 82 | 要运行示例程序,首先在终端启动netcat作为输入流: 83 | 84 | ``` 85 | nc -lk 9999 86 | ``` 87 | 88 | 输入一些单词,回车换行输入新一行的单词。这些输入将作为示例程序的输入。如果要使得某个单词的计数大于1,请在5秒钟内重复输入相同的单词(如果5秒钟输入相同单词对你来说太快,可以把示例程序中的窗口时间调大)。 89 | 90 | 91 | ##Data Sources 92 | 93 | Sources是程序的输入数据来源。你可以使用StreamExecutionEnvironment.addSource(sourceFunction)将一个Source添加到程序中。 94 | Flink提供一系列预实现的数据源函数,用户可以通过实现SourceFunction接口来自定义非并行的Source,也可以通过实现ParallelSourceFunction接口或者继承RichParallelSourceFunction来自定义并行的Source。 95 | 96 | StreamExecutionEnvironment提供了几个预先定义好的流式数据源,如下: 97 | 98 | 基于文件: 99 | 100 | readTextFile(path) - 读取文本文件, 即符合TextInputFormat规范的文件,逐行地以字符串类型返回。 101 | 102 | readFile(fileInputFormat, path) - 以指定分文件格式读取文件(一次) 103 | 104 | readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo) - 以上两个方法实际上内部调用这个方法。 105 | 它根据给定的fileInputFormat和读取路径读取文件。 106 | 根据提供的watchType,这个source可以定期(每隔interval毫秒)监测给定路径的新数据(FileProcessingMode.PROCESS_CONTINUOUSLY),或者处理一次路径对应文件的数据并退出(FileProcessingMode.PROCESS_ONCE)。 107 | 你可以通过pathFilter进一步排除掉需要处理的文件。 108 | 109 | 实现: 110 | 111 | 在具体实现上,Flink文件读取过程分为两个子任务,目录监控和数据读取。 112 | 分别以一个独立实体执行。 113 | 目录监控任务是单线程的而数据读取任务是多线程并行的。后者的并行度等于job的并行度。 114 | 监控任务的作用是扫描目录(间歇或者一次,由watchType决定),查找需要处理的文件并进行分片(split),并将分片分配给下游reader。 115 | Reader负责实际读取数据,每一个分片指只会被一个reader读取,一个reader可以逐个读取多个分片。 116 | 117 | 重要提示: 118 | 119 | 如果watchType设置为FileProcessingMode.PROCESS_CONTINUOUSLY,当一个文件被修改,它的内容会被整个重新处理,这会打破"exactly-once"语义,因为在文件末尾附加数据将导致其所有内容被重新处理。 120 | 121 | 如果watchType设置为FileProcessingMode.PROCESS_ONCE,source只会扫描文件一次并退出,不去等待reader完成内容读取。 122 | 当然,reader会持续读取完整个文件,关闭source不会引起更多的检查点。这会导致如果一个节点失败需要更长的时间恢复,因为任务会从最后一个检查点开始重新执行。 123 | 124 | 基于Socket: 125 | 126 | socketTextStream - 从socket读取。元素可以用分隔符切分。 127 | 128 | 基于集合: 129 | 130 | fromCollection(Collection) - 从Java的Java.util.Collection创建数据流。集合中的所有元素类型必须相同。 131 | 132 | fromCollection(Iterator, Class) - 从一个迭代器中创建数据流。Class指定了该迭代器返回元素的类型。 133 | 134 | fromElements(T ...) - 从给定的对象序列中创建数据流。所有对象类型必须相同。 135 | 136 | fromParallelCollection(SplittableIterator, Class) - 从一个迭代器中创建并行数据流。Class指定了该迭代器返回元素的类型。 137 | 138 | generateSequence(from, to) - 创建一个生成指定区间范围内的数字序列的并行数据流。 139 | 140 | 自定义: 141 | 142 | addSource - 例如,你可以addSource(new FlinkKafkaConsumer08<>(...))以从Apache Kafka读取数据。 143 | 144 | 145 | ##DataStream Transformations 146 | 147 | 请参看[Operators]来了解流转化的概述。 148 | 149 | 150 | ##Data Sinks 151 | 152 | Data Sinks消费数据流并将它们推到文件,socket,外部系统或者打印。Flink有各种内置的输出格式,封装在数据流操作中。 153 | 154 | writeAsText() / TextOutputFormat - 将元素以字符串形式输出。字符串通过调用每个元素的toString()方法获得。 155 | 156 | writeAsCsv(...) / CsvOutputFormat - 将元素以逗号分割的csv文件输出,行和域的分割符可以配置。每个域的值通过调用每个元素的toString()方法获得。 157 | 158 | print() / printToErr() - 打印每个元素的toString()值到标准输出/错误输出流。可以配置前缀信息添加到输出,以区分不同print的结果。如果并行度大于1,则task id也会添加到输出前缀上。 159 | 160 | writeUsingOutputFormat() / FileOutputFormat - 自定义文件输出的方法/基类。支持自定义的对象到字节的转换。 161 | 162 | writeToSocket - 根据SerializationSchema把元素写到socket 163 | 164 | addSink - 调用自定义sink function。Flink自带了很多连接其他系统的连接器(connectors)(如Apache Kafka),这些连接器都实现了sink function。 165 | 166 | 请注意,write*()方法主要是用于调试的。它们不参与Flink的检查点机制,这意味着这些规范方法具有"at-least-once"语义。数据刷新到目标系统取决于OutputFormat的实现。这意味着并非所有发送到OutputFormat的元素都会立即在目标系统中可见。此外,在失败的情况下,这些记录可能会丢失。 167 | 168 | 为了可靠,在把流写到文件系统时,使用flink-connector-filesystem来实现exactly-once。此外,通过.addSink(...)方法自定义的实现可以参与Flink的检查点机制以实现exactly-once语义。 169 | 170 | 171 | ##迭代 172 | 173 | 迭代流程序实现一个step方法并将其嵌入到IterativeStream。因为一个数据流程序永远不会结束,因此没有最大迭代次数。 174 | 事实上,你需要指定流的哪一部分是用于回馈到迭代过程以及哪一部分会通过切片或者过滤向下游发送。这里我们展示一个使用过滤的例子。首先我们定义一个IterativeStream。 175 | 176 | 177 | ``` 178 | IterativeStream iteration = input.iterate(); 179 | ``` 180 | 181 | 然后,我们要使用一系列transformation来设定循环中会执行的逻辑。这里我们只是用一个map转换。 182 | 183 | ``` 184 | DataStream iterationBody = iteration.map(/* 这会执行多次 */); 185 | ``` 186 | 187 | 要关闭迭代并定义迭代尾部,需要调用IterativeStream的closeWith(feedbackStream)方法。 188 | 传给closeWith方法的数据流将被反馈给迭代的头部。 189 | 一种常见的模式是使用filter来分离流中需要反馈的部分和需要继续发往下游的部分。 190 | 这些filter可以定义“终止”逻辑,以控制元素是流向下游而不是反馈迭代。 191 | 192 | ``` 193 | iteration.closeWith(iterationBody.filter(/* 流的一部分 */)); 194 | DataStream output = iterationBody.filter(/* 流的其他部分 */); 195 | ``` 196 | 197 | 例如,下面的程序是持续的从一系列整数中减1直到0: 198 | 199 | ``` 200 | DataStream someIntegers = env.generateSequence(0, 1000); 201 | 202 | IterativeStream iteration = someIntegers.iterate(); 203 | 204 | DataStream minusOne = iteration.map(new MapFunction() { 205 | @Override 206 | public Long map(Long value) throws Exception { 207 | return value - 1 ; 208 | } 209 | }); 210 | 211 | DataStream stillGreaterThanZero = minusOne.filter(new FilterFunction() { 212 | @Override 213 | public boolean filter(Long value) throws Exception { 214 | return (value > 0); 215 | } 216 | }); 217 | 218 | iteration.closeWith(stillGreaterThanZero); 219 | 220 | DataStream lessThanZero = minusOne.filter(new FilterFunction() { 221 | @Override 222 | public boolean filter(Long value) throws Exception { 223 | return (value <= 0); 224 | } 225 | }); 226 | 227 | ``` 228 | 229 | 230 | 231 | ##执行参数 232 | 233 | StreamExecutionEnvironment包含一个ExecutionConfig属性用于给任务配置指定的运行时参数。 234 | 235 | 更多参数的解释请参阅[执行参数]。一些参数是只应用于DataStream API的: 236 | 237 | setAutoWatermarkInterval(long milliseconds): 设置自动发射水印的间隔。你可以通过getAutoWatermarkInterval()获取当前的发射间隔。 238 | 239 | 240 | ##容错 241 | 242 | 243 | [状态和检查点] 描述了如何开启和配置Flink的checkpointing机制。 244 | 245 | 246 | ##延迟控制 247 | 248 | 默认情况下,元素不会通过一个一个通过网络传输(这样会带来不必要的通信),而是缓冲起来。缓冲(实际是在机器之间传输)的大小可以在Flink配置文件中设置。 249 | 尽管这种方法有利于优化吞吐量,但在输入流不够快的情况下会造成延迟问题。为了控制吞吐量和延迟,你可以在execution environment(或单个operator)上使用env.setBufferTimeout(timeoutMillis)来设置缓冲区填满的最大等待时间。 250 | 如果超过该最大等待时间,即使缓冲区未满,也会被自动发送出去。该最大等待时间默认值为100 ms。 251 | 252 | 253 | 使用: 254 | 255 | *Java* 256 | ``` 257 | LocalStreamEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); 258 | env.setBufferTimeout(timeoutMillis); 259 | 260 | env.generateSequence(1,10).map(new MyMapper()).setBufferTimeout(timeoutMillis); 261 | ``` 262 | 263 | *Scala* 264 | ``` 265 | val env: LocalStreamEnvironment = StreamExecutionEnvironment.createLocalEnvironment 266 | env.setBufferTimeout(timeoutMillis) 267 | 268 | env.generateSequence(1,10).map(myMap).setBufferTimeout(timeoutMillis) 269 | ``` 270 | 271 | 为了最大化吞吐量,可以设置setBufferTimeout(-1),这样就没有了超时机制,缓冲区只有在满时才会发送出去。为了最小化延迟,可以把超时设置为接近0的值(例如5或10 ms)。应避免将该超时设置为0,因为这样可能导致性能严重下降。 272 | 273 | 274 | ##调试 275 | 276 | 在分布式集群在运行数据流程序之前,最好确保程序可以符合预期工作。因此,实现数据分析程序通常需要一个渐进的过程:检查结果,调试和改进。 277 | 278 | Flink提供了诸多特性来大幅简化数据分析程序的开发:你可以在IDE中进行本地调试,注入测试数据,收集结果数据。本节给出一些如何简化Flink程序开发的指导。 279 | 280 | ###本地执行环境 281 | 282 | LocalStreamEnvironment会在其所在的JVM进程中启动一个Flink引擎. 如果你在IDE中启动LocalEnvironment,你可以在你的代码中设置断点,轻松调试你的程序。 283 | 284 | 一个LocalEnvironment的创建和使用示例如下: 285 | 286 | *Java* 287 | ``` 288 | final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); 289 | 290 | DataStream lines = env.addSource(/* some source */); 291 | // build your program 292 | 293 | env.execute(); 294 | ``` 295 | 296 | 297 | *Scala* 298 | 299 | ``` 300 | valval envenv == StreamExecutionEnvironmentStrea .createLocalEnvironment() 301 | 302 | val lines = env.addSource(/* some source */) 303 | // build your program 304 | 305 | env.execute() 306 | ``` 307 | 308 | ###基于集合的数据Sources 309 | 310 | Flink提供了基于Java集合实现的特殊数据sources用于测试。一旦程序通过测试,它的sources和sinks可以方便的替换为从外部系统读写的sources和sinks。 311 | 312 | 基于集合的数据Sources可以像这样使用: 313 | 314 | *Java* 315 | ``` 316 | final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); 317 | 318 | // Create a DataStream from a list of elements 319 | DataStream myInts = env.fromElements(1, 2, 3, 4, 5); 320 | 321 | // Create a DataStream from any Java collection 322 | List> data = ... 323 | DataStream> myTuples = env.fromCollection(data); 324 | 325 | // Create a DataStream from an Iterator 326 | Iterator longIt = ... 327 | DataStream myLongs = env.fromCollection(longIt, Long.class); 328 | ``` 329 | 330 | *Scala* 331 | ``` 332 | valval envenv == StreamExecutionEnvironmentStrea .createLocalEnvironment() 333 | 334 | // Create a DataStream from a list of elements 335 | val myInts = env.fromElements(1, 2, 3, 4, 5) 336 | 337 | // Create a DataStream from any Collection 338 | val data: Seq[(String, Int)] = ... 339 | val myTuples = env.fromCollection(data) 340 | 341 | // Create a DataStream from an Iterator 342 | val longIt: Iterator[Long] = ... 343 | val myLongs = env.fromCollection(longIt) 344 | ``` 345 | *注意*: 346 | 目前,集合数据source要求数据类型和迭代器实现Serializable。并行度 = 1)。 347 | 348 | ###迭代的数据Sink 349 | 350 | Flink还提供了一个sink来收集DataStream的测试和调试结果。它可以这样使用: 351 | 352 | *Java* 353 | ``` 354 | import org.apache.flink.streaming.experimental.DataStreamUtils 355 | 356 | DataStream> myResult = ... 357 | Iterator> myOutput = DataStreamUtils.collect(myResult) 358 | ``` 359 | 360 | *Scala* 361 | ``` 362 | import org.apache.flink.streaming.experimental.DataStreamUtils 363 | import scala.collection.JavaConverters.asScalaIteratorConverter 364 | 365 | val myResult: DataStream[(String, Int)] = ... 366 | val myOutput: Iterator[(String, Int)] = DataStreamUtils.collect(myResult.javaStream).asScala 367 | ``` 368 | 369 | *注意:* 370 | Flink 1.5.0的flink-streaming-contrib模块已经移除,它的类被迁移到了flink-streaming-java和flink-streaming-scala。 371 | 372 | 373 | #下一步 374 | 375 | Operators 算子: 流式算子的专门介绍。 376 | Event Time 事件时间: Flink时间概念介绍。 377 | State & Fault Tolerance 状态和容错: 如何开发有状态的应用。 378 | Connectors 连接器: 可使用的输入输出连接器的描述。 379 | 380 | 381 | 382 | -------------------------------------------------------------------------------- /doc/table/Concept & Common API.md: -------------------------------------------------------------------------------- 1 | ## Concept & Common API 2 | Table API和SQL集成在一个联合的API中。这个API核心概念是Table, 3 | Table可以作为查询的输入和输出。这篇文章展示了使用Table API和SQL查询的通用结构, 4 | 如何去进行表的注册,如何去进行表的查询,并且展示如何去进行表的输出。 5 | 6 | 7 | 8 | ## 1. Structure of Table API and SQL Programs 9 | 10 | ​ 所有使用批量和流式相关的Table API和SQL的程序都有以下相同模式。下面的代码实例展示了Table API和SQL程序的通用结构。 11 | 12 | ```scala 13 | // 在批处理程序中使用ExecutionEnvironment代替StreamExecutionEnvironment 14 | val env = StreamExecutionEnvironment.getExecutionEnvironment 15 | 16 | // 创建TableEnvironment对象 17 | val tableEnv = TableEnvironment.getTableEnvironment(env) 18 | 19 | // 注册表 20 | tableEnv.registerTable("table1", ...) // or 21 | tableEnv.registerTableSource("table2", ...) // or 22 | tableEnv.registerExternalCatalog("extCat", ...) 23 | 24 | // 基于Table API的查询创建表 25 | val tapiResult = tableEnv.scan("table1").select(...) 26 | // 从SQL查询创建表 27 | val sqlResult = tableEnv.sqlQuery("SELECT ... FROM table2 ...") 28 | 29 | // 将表操作API查询到的结果表输出到TableSink,SQL查询到的结果一样如此 30 | tapiResult.writeToSink(...) 31 | 32 | // 执行 33 | env.execute() 34 | ``` 35 | 36 | 注意:Table API和SQL查询很容易集成并被嵌入到DataStream或者DataSet程序中。查看[将DataStream和DataSet API进行整合](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/common.html#integration-with-datastream-and-dataset-api)章节 37 | 学习DataSteams和DataSets是如何转换成Table以及Table是如何转换为DataStream或DataSet 38 | 39 | 40 | 41 | ## 2. Create a TableEnvironment 42 | TableEnvironment是Table API与SQL整合的核心概念之一,它主要有如下功能: 43 | - 在internal catalog注册表 44 | - 注册external catalog 45 | - 执行SQL查询 46 | - 注册UDF函数(user-defined function),例如 标量, 表或聚合 47 | - 将DataStream或者DataSet转换为表 48 | - 保持ExecutionEnvironment或者StreamExecutionEnvironment的引用指向 49 | 50 | 一个表总是与一个特定的TableEnvironment绑定在一块, 51 | 相同的查询不同的TableEnvironment是无法通过join、union合并在一起。 52 | 53 | 创建TableEnvironment的方法通常是通过StreamExecutionEnvironment,ExecutionEnvironment对象调用其中的静态方法TableEnvironment.getTableEnvironment(),或者是TableConfig来创建。 54 | TableConfig可以用作配置TableEnvironment或是对自定义查询优化器或者是编译过程进行优化(详情查看[查询优化](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/common.html#query-optimization)) 55 | 56 | ```scala 57 | // *************** 58 | // 流式查询 59 | // *************** 60 | val sEnv = StreamExecutionEnvironment.getExecutionEnvironment 61 | // 为流式查询创建一个TableEnvironment对象 62 | val sTableEnv = TableEnvironment.getTableEnvironment(sEnv) 63 | 64 | // *********** 65 | // 批量查询 66 | // *********** 67 | val bEnv = ExecutionEnvironment.getExecutionEnvironment 68 | // 为批量查询创建一个TableEnvironment对象 69 | val bTableEnv = TableEnvironment.getTableEnvironment(bEnv) 70 | ``` 71 | ## Register Tables in the Catalog 72 | TableEnvironment包含了通过名称注册表时的表的catalog信息。通常情况下有两种表,一种为输入表, 73 | 一种为输出表。输入表主要是在使用Table API和SQL查询时提供输入数据,输出表主要是将Table API和 74 | SQL查询的结果作为输出结果对接到外部系统。 75 | 76 | 输入表有多种不同的输入源进行注册: 77 | - 已经存在的Table对象,通常是是作为Table API和SQL查询的结果 78 | - TableSource,可以访问外部数据如文件,数据库或者是消息系统 79 | - 来自DataStream或是DataSet程序中的DataStream或DataSet,讨论DataStream或是DataSet 80 | 可以[整合DataStream和DataSet API](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/common.html#integration-with-datastream-and-dataset-api)了解到 81 | 82 | 输出表可使用TableSink进行注册 83 | 84 | ## Register a Table 85 | Table是如何注册到TableEnvironment中如下所示: 86 | ```scala 87 | // 获取(创建)TableEnvironment对象 88 | val tableEnv = TableEnvironment.getTableEnvironment(env) 89 | 90 | // 从简单的查询结果中作为表 91 | val projTable: Table = tableEnv.scan("X").select(...) 92 | 93 | // 将创建的表projTable命名为projectedTable注册到TableEnvironment中 94 | tableEnv.registerTable("projectedTable", projTable) 95 | ``` 96 | 注意:一张注册过的Table就跟关系型数据库中的视图性质相同,定义表的查询未进行优化,但在另一个查询引用已注册的表时将进行内联。 97 | 如果多表查询引用了相同的Table,它就会将每一个引用进行内联并且多次执行,已注册的Table的结果之间不会进行共享。 98 | 99 | ## Register a TableSource 100 | TableSource可以访问外部系统存储例如数据库(Mysql,HBase),特殊格式编码的文件(CSV, Apache [Parquet, Avro, ORC], …) 101 | 或者是消息系统 (Apache Kafka, RabbitMQ, …)中的数据。 102 | 103 | Flink旨在为通用数据格式和存储系统提供TableSource。请查看[此处](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/sourceSinks.html) 104 | 了解支持的TableSource类型与如何去自定义TableSour。 105 | 106 | TableSource是如何注册到TableEnvironment中如下所示: 107 | ```scala 108 | // 获取(创建)TableEnvironment对象 109 | val tableEnv = TableEnvironment.getTableEnvironment(env) 110 | 111 | // 创建TableSource对象 112 | val csvSource: TableSource = new CsvTableSource("/path/to/file", ...) 113 | 114 | // 将创建的TableSource作为表并命名为csvTable注册到TableEnvironment中 115 | tableEnv.registerTableSource("CsvTable", csvSource) 116 | ``` 117 | ## Register a TableSink 118 | 注册过的TableSink可以将SQL查询的结果以表的形式输出到外部的存储系统,例如关系型数据库, 119 | Key-Value数据库(Nosql),消息队列,或者是其他文件系统(使用不同的编码, 例如CSV, Apache [Parquet, Avro, ORC], …) 120 | 121 | Flink使用TableSink的目的是为了将常用的数据进行清洗转换然后存储到不同的存储介质中。详情请查看[此处](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/sourceSinks.html) 122 | 去深入了解哪些sinks是可用的,并且如何去自定义TableSink。 123 | ```scala 124 | // 获取(创建)TableEnvironment对象 125 | val tableEnv = TableEnvironment.getTableEnvironment(env) 126 | 127 | // 创建TableSink对象 128 | val csvSink: TableSink = new CsvTableSink("/path/to/file", ...) 129 | 130 | // 定义字段的名称和类型 131 | val fieldNames: Array[String] = Array("a", "b", "c") 132 | val fieldTypes: Array[TypeInformation[_]] = Array(Types.INT, Types.STRING, Types.LONG) 133 | 134 | // 将创建的TableSink作为表并命名为CsvSinkTable注册到TableEnvironment中 135 | tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, csvSink) 136 | ``` 137 | 138 | ## Register an External Catalog 139 | 外部目录可以提供有关外部数据库和表的信息, 140 | 例如其名称,模式,统计以及有关如何访问存储在外部数据库,表或文件中的数据的信息。 141 | 142 | 外部目录的创建方式可以通过实现ExternalCatalog接口,并且注册到TableEnvironment中,详情如下所示: 143 | ```scala 144 | // 获取(创建)TableEnvironment对象 145 | val tableEnv = TableEnvironment.getTableEnvironment(env) 146 | 147 | // 创建一个External Catalog目录对象 148 | val catalog: ExternalCatalog = new InMemoryExternalCatalog 149 | 150 | // 将ExternalCatalog注册到TableEnvironment中 151 | tableEnv.registerExternalCatalog("InMemCatalog", catalog) 152 | ``` 153 | 一旦将External Catalog注册到TableEnvironment中,所有在ExternalCatalog中 154 | 定义的表可以通过完整的路径如catalog.database.table进行Table API和SQL的查询操作 155 | 156 | 目前,Flink提供InMemoryExternalCatalog对象用来做demo和测试,然而, 157 | ExternalCatalog对象还可用作Table API来连接catalogs,例如HCatalog 或 Metastore 158 | 159 | ## Query a Table 160 | ### Table API 161 | Table API是Scala和Java语言集成查询的API,与SQL查询不同之处在于,它的查询不是像 162 | SQL一样使用字符串进行查询,而是在语言中使用语法进行逐步组合使用 163 | 164 | Table API是基于展示表(流或批处理)的Table类,它提供一些列操作应用相关的操作。 165 | 这些方法返回一个新的Table对象,该对象表示在输入表上关系运算的结果。一些关系运算是 166 | 由多个方法组合而成的,例如 table.groupBy(...).select(),其中groupBy()指定 167 | 表的分组,select()表示在分组的结果上进行查询。 168 | 169 | [Table API](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/tableApi.html) 170 | 描述了所有支持表的流式或者批处理相关的操作。 171 | 172 | 下面给出一个简单的实例去说明如何去使用Table API进行聚合查询: 173 | ```scala 174 | // 获取(创建)TableEnvironment对象 175 | val tableEnv = TableEnvironment.getTableEnvironment(env) 176 | 177 | // 注册Orders表 178 | 179 | // 扫描注册过的Orders表 180 | val orders = tableEnv.scan("Orders") 181 | 182 | // 计算表中所有来自法国的客户的收入 183 | val revenue = orders 184 | .filter('cCountry === "FRANCE") 185 | .groupBy('cID, 'cName) 186 | .select('cID, 'cName, 'revenue.sum AS 'revSum) 187 | 188 | // 将结果输出成一张表或者是转换表 189 | 190 | // 执行查询 191 | ``` 192 | 注意:Scala的Table API使用Scala符号,它使用单引号加字段('cID)来表示表的属性的引用, 193 | 如果使用Scala的隐式转换的话,确保引入了org.apache.flink.api.scala._ 和 org.apache.flink.table.api.scala._ 194 | 来确保它们之间的转换。 195 | 196 | ### SQL 197 | Flink的SQL操作基于实现了SQL标准的[Apache Calcite](https://calcite.apache.org/),SQL查询通常是使用特殊且有规律的字符串。 198 | [SQL](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/sql.html) 199 | 描述了所有支持表的流式或者批处理相关的SQL操作。 200 | ```scala 201 | // 获取(创建)TableEnvironment对象 202 | val tableEnv = TableEnvironment.getTableEnvironment(env) 203 | 204 | // 注册Orders表 205 | 206 | // 计算表中所有来自法国的客户的收入 207 | val revenue = tableEnv.sqlQuery(""" 208 | |SELECT cID, cName, SUM(revenue) AS revSum 209 | |FROM Orders 210 | |WHERE cCountry = 'FRANCE' 211 | |GROUP BY cID, cName 212 | """.stripMargin) 213 | 214 | // 将结果输出成一张表或者是转换表 215 | 216 | // 执行查询 217 | ``` 218 | 下面的例子展示了如何去使用更新查询去插入数据到已注册的表中 219 | ```scala 220 | // 获取(创建)TableEnvironment对象 221 | val tableEnv = TableEnvironment.getTableEnvironment(env) 222 | 223 | // 注册"Orders"表 224 | // 注册"RevenueFrance"输出表 225 | 226 | // 计算表中所有来自法国的客户的收入并且将结果作为结果输出到"RevenueFrance"中 227 | tableEnv.sqlUpdate(""" 228 | |INSERT INTO RevenueFrance 229 | |SELECT cID, cName, SUM(revenue) AS revSum 230 | |FROM Orders 231 | |WHERE cCountry = 'FRANCE' 232 | |GROUP BY cID, cName 233 | """.stripMargin) 234 | 235 | // 执行查询 236 | ``` 237 | 238 | ## Mixing Table API and SQL 239 | Table API和SQL可以很轻松的混合使用因为他们两者返回的结果都为Table对象: 240 | - 可以在SQL查询返回的Table对象上定义Table API查询 241 | - 通过在TableEnvironment中注册结果表并在SQL查询的FROM子句中引用它, 242 | 可以在Table API查询的结果上定义SQL查询。 243 | 244 | ## Emit a Table 245 | 通过将Table写入到TableSink来作为一张表的输出,TableSink是做为多种文件类型 (CSV, Apache Parquet, Apache Avro), 246 | 存储系统(JDBC, Apache HBase, Apache Cassandra, Elasticsearch), 或者是消息系统 (Apache Kafka, RabbitMQ).输出的通用接口, 247 | 248 | Batch Table只能通过BatchTableSink来进行数据写入,而Streaming Table可以 249 | 选择AppendStreamTableSink,RetractStreamTableSink,UpsertStreamTableSink 250 | 中的任意一个来进行。 251 | 252 | 请查看[Table Source & Sinks](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/sourceSinks.html) 253 | 来更详细的了解支持的Sinks并且如何去实现自定义的TableSink。 254 | 255 | 可以使用两种方式来输出一张表: 256 | 257 | - Table.writeToSink(TableSink sink)方法使用提供的TableSink自动配置的表的schema来 258 | 进行表的输出 259 | - Table.insertInto(String sinkTable)方法查找在TableEnvironment目录中提供的名称下使用特定模式注册的TableSink。 260 | 将输出表的模式将根据已注册的TableSink的模式进行验证 261 | 262 | 下面的例子展示了如何去查询结果作为一张表输出 263 | ```scala 264 | // 获取(创建)TableEnvironment对象 265 | val tableEnv = TableEnvironment.getTableEnvironment(env) 266 | 267 | // 使用Table API或者SQL 查询来查找结果 268 | val result: Table = ... 269 | // 创建TableSink对象 270 | val sink: TableSink = new CsvTableSink("/path/to/file", fieldDelim = "|") 271 | 272 | // 方法1: 使用TableSink的writeToSink()方法来将结果输出为一张表 273 | result.writeToSink(sink) 274 | 275 | // 方法2: 注册特殊schema的TableSink 276 | val fieldNames: Array[String] = Array("a", "b", "c") 277 | val fieldTypes: Array[TypeInformation] = Array(Types.INT, Types.STRING, Types.LONG) 278 | tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, sink) 279 | // 调用注册过的TableSink中insertInto() 方法来将结果输出为一张表 280 | result.insertInto("CsvSinkTable") 281 | 282 | // 执行 283 | ``` 284 | 285 | ## Translate and Execute a Query 286 | Table API和SQL查询的结果转换为[DataStream](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/datastream_api.html) 287 | 或是[DataSet](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/) 288 | 取决于它的输入是流式输入还是批处理输入。查询逻辑在内部表示为逻辑执行计划,并分为两个阶段进行转换: 289 | - 优化逻辑执行计划 290 | - 转换为DataStream或DataSet 291 | 292 | Table API或SQL查询在下面请看下进行转换: 293 | - 当调用Table.writeToSink() 或 Table.insertInto()进行查询结果表输出的时候 294 | - 当调用TableEnvironment.sqlUpdate()进行SQL更新查询时 295 | - 当表转换为DataSteam或DataSet时,详情查看[Integration with DataStream and DataSet API](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/common.html#integration-with-dataStream-and-dataSet-api) 296 | 297 | 一旦进行转换后,Table API或SQL查询的结果就会在StreamExecutionEnvironment.execute() 或 ExecutionEnvironment.execute() 298 | 被调用时被当做DataStream或DataSet一样被进行处理 299 | 300 | ## Integration with DataStream and DataSet API 301 | Table API或SQL查询的结果很容易被[DataStream](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/datastream_api.html) 302 | 或是[DataSet](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/)内嵌整合。举个例子, 303 | 我们会进行外部表的查询(像关系型数据库),然后做像过滤,映射,聚合或者是元数据关联的一些预处理。 304 | 然后使用DataStream或是DataSet API(或者是基于这些基础库开发的上层API库, 例如CEP或Gelly)进一步对数据进行处理。 305 | 同样,Table API或SQL查询也可以应用于DataStream或DataSet程序的结果。 306 | 307 | ##implicit Conversion for Scala 308 | Scala Table API具有DataSet,DataStream和Table Class之间的隐式转换,流式操作API中只要引入org.apache.flink.table.api.scala._ 309 | 和 org.apache.flink.api.scala._ 便可以进行相应的隐式转换 310 | 311 | ## Register a DataStream or DataSet as Table 312 | DataStream或DataSet也可以作为Table注册到TableEnvironment中。结果表的模式取决于已注册的DataStream或DataSet的数据类型, 313 | 详情请查看[mapping of data types to table schema](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/common.html#mapping-of-data-types-to-table-schema) 314 | 315 | ```scala 316 | // 获取(创建)TableEnvironment对象 317 | // 注册如表一样的DataSet 318 | 319 | val tableEnv = TableEnvironment.getTableEnvironment(env) 320 | 321 | val stream: DataStream[(Long, String)] = ... 322 | 323 | // 将DataStream作为具有"f0", "f1"字段的"myTable"表注册到TableEnvironment中 324 | tableEnv.registerDataStream("myTable", stream) 325 | 326 | // 将DataStream作为具有"myLong", "myString"字段的"myTable2"表注册到TableEnvironment中 327 | tableEnv.registerDataStream("myTable2", stream, 'myLong, 'myString) 328 | ``` 329 | 注意:DataStream表的名称必须与^ _DataStreamTable_ [0-9] +模式不匹配, 330 | 并且DataSet表的名称必须与^ _DataSetTable_ [0-9] +模式不匹配。 331 | 这些模式仅供内部使用。 332 | 333 | ## Convert a DataStream or DataSet into a Table 334 | 如果你使用Table API或是SQL查询,你可以直接将DataStream或DataSet直接转换为表而不需要 335 | 再将它们注册到TableEnvironment中。 336 | ```scala 337 | // 获取(创建)TableEnvironment对象 338 | // 注册如表一样的DataSet 339 | val tableEnv = TableEnvironment.getTableEnvironment(env) 340 | 341 | val stream: DataStream[(Long, String)] = ... 342 | 343 | // 使用默认的字段'_1, '_2将DataStram转换为Table 344 | val table1: Table = tableEnv.fromDataStream(stream) 345 | 346 | // 使用默认的字段'myLong, 'myString将DataStram转换为Table 347 | 348 | val table2: Table = tableEnv.fromDataStream(stream, 'myLong, 'myString) 349 | ``` 350 | 351 | ## Convert a Table into a DataStream or DataSet 352 | 表可以转换为DataStream或DataSet,通过这种方式,自定义DataStream或DataSet 353 | 同样也可以作为Table API或SQL查询结果的结果。 354 | 当把表转换为DataStream或DataSet时,你需要指定生成的DataStream或DataSet的数据类型。 355 | 例如,表格行所需转换的数据类型,通常最方便的转换类型也最常用的是Row。 356 | 以下列表概述了不同选项的功能: 357 | - Row:字段按位置,任意数量的字段映射,支持空值,无类型安全访问。 358 | - POJO:字段按名称(POJO字段必须与Table字段保持一致),任意数量的字段映射,支持空值,类型安全访问。 359 | - Case Class:字段按位置,任意数量的字段映射,不支持空值,类型安全访问。 360 | - Tuple:字段按位置,Scala支持22个字段,Java 25个字段映射,不支持空值,类型安全访问。 361 | - Atomic Type:表必须具有单个字段,不支持空值,类型安全访问。 362 | ### Convert a Table into a DataStream 363 | 作为流式查询结果的表将动态更新,它随着新记录到达查询的输入流而改变,于是,转换到这样的动态查询DataStream 364 | 需要对表的更新进行编码。 365 | 将表转换为DataStream有两种模式: 366 | - Append Mode:这种模式仅用于动态表仅仅通过INSERT来进行表的更新,它是仅可追加模式, 367 | 并且之前输出的表不会进行更改 368 | - Retract Mode:这种模式经常用到。它使用布尔值的变量来对INSERT和DELETE对表的更新做标记 369 | ```scala 370 | // 获取(创建)TableEnvironment对象 371 | // 注册如表一样的DataSet 372 | val tableEnv = TableEnvironment.getTableEnvironment(env) 373 | 374 | // 表中有两个字段(String name, Integet age) 375 | val table: Table = ... 376 | 377 | // 将表转换为列的 append DataStream 378 | val dsRow: DataStream[Row] = tableEnv.toAppendStream[Row](table) 379 | 380 | // 将表转换为Tubple2[String,Int]的 append DataStream 381 | // convert the Table into an append DataStream of Tuple2[String, Int] 382 | val dsTuple: DataStream[(String, Int)] dsTuple = 383 | tableEnv.toAppendStream[(String, Int)](table) 384 | 385 | // convert the Table into a retract DataStream of Row. 386 | // Retract Mode下将表转换为列的 append DataStream 387 | // 判断A retract stream X是否为DataStream[(Boolean, X)] 388 | // 布尔只表示数据类型的变化,True代表为INSERT,false表示为删除 389 | val retractStream: DataStream[(Boolean, Row)] = tableEnv.toRetractStream[Row](table) 390 | ``` 391 | 注意:关于动态表和它的属性详情参考[Streaming Queries](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/streaming.html) 392 | 393 | ### Convert a Table into a DataSet 394 | 表转换为DataSet如下所示: 395 | ```scala 396 | // 获取(创建)TableEnvironment对象 397 | // 注册如表一样的DataSet 398 | val tableEnv = TableEnvironment.getTableEnvironment(env) 399 | 400 | // 表中有两个字段(String name, Integet age) 401 | val table: Table = ... 402 | 403 | // 将表转换为列的DataSet 404 | val dsRow: DataSet[Row] = tableEnv.toDataSet[Row](table) 405 | 406 | // 将表转换为Tubple2[String,Int]的DataSet 407 | val dsTuple: DataSet[(String, Int)] = tableEnv.toDataSet[(String, Int)](table) 408 | ``` 409 | ### Mapping of Data Types to Table Schema 410 | Flink的DataStream和DataSet API支持多种类型。组合类型像Tuple(内置Scala元组和Flink Java元组), 411 | POJOs,Scala case classes和Flink中具有可在表表达式中访问的多个字段允许嵌套数据结构的Row类型, 412 | 其他类型都被视为原子类型。接下来,我们将会描述Table API是如何将这些类型转换为内部的列展现并且 413 | 举例说明如何将DataStream转换为Table 414 | 415 | #### Position-based Mapping 416 | 基于位置的映射通常在保持顺序的情况下给字段一个更有意义的名称,这种映射可用于有固定顺序的组合数据类型, 417 | 也可用于原子类型。复合数据类型(如元组,行和Case Class)具有此类字段顺序.然而,POJO的字段必须与映射的 418 | 表的字段名相同。 419 | 420 | 当定义基于位置的映射,输入的数据类型不得存在指定的名称,不然API会认为这些映射应该按名称来进行映射。 421 | 如果未指定字段名称,则使用复合类型的默认字段名称和字段顺序,或者使用f0作为原子类型。 422 | ```scala 423 | // 获取(创建)TableEnvironment对象 424 | val tableEnv = TableEnvironment.getTableEnvironment(env) 425 | 426 | val stream: DataStream[(Long, Int)] = ... 427 | 428 | // 使用默认的字段'_1, '_2将DataStram转换为Table 429 | val table1: Table = tableEnv.fromDataStream(stream) 430 | 431 | // 使用默认的字段'myLong, 'myInt将DataStram转换为Table 432 | val table: Table = tableEnv.fromDataStream(stream, 'myLong 'myInt) 433 | ``` 434 | #### Name-based Mapping 435 | 基于名称的映射可用于一切数据类型包括POJOs,它是定义表模式映射最灵活的一种方式。虽然查询结果的字段可能会使用别名,但 436 | 这种模式下所有的字段都是使用名称进行映射的。使用别名的情况下会进行重排序。 437 | 如果未指定字段名称,则使用复合类型的默认字段名称和字段顺序,或者使用f0作为原子类型。 438 | ```scala 439 | // 获取(创建)TableEnvironment对象 440 | val tableEnv = TableEnvironment.getTableEnvironment(env) 441 | 442 | val stream: DataStream[(Long, Int)] = ... 443 | 444 | // 使用默认的字段'_1 和 '_2将DataStram转换为Table 445 | val table: Table = tableEnv.fromDataStream(stream) 446 | 447 | // 只使用'_2字段将DataStream转换为Table 448 | val table: Table = tableEnv.fromDataStream(stream, '_2) 449 | 450 | // 交换字段将DataStream转换为Table 451 | val table: Table = tableEnv.fromDataStream(stream, '_2, '_1) 452 | 453 | // 交换后的字段给予别名'myInt, 'myLong将DataStream转换为Table 454 | val table: Table = tableEnv.fromDataStream(stream, '_2 as 'myInt, '_1 as 'myLong) 455 | ``` 456 | #### Atomic Types 457 | Flink将基础类型(Integer, Double, String)和通用类型(不能被分析和拆分的类型)视为原子类型。 458 | 原子类型的DataStream或DataSet转换为只有单个属性的表。从原子类型推断属性的类型,并且可以指定属性的名称。 459 | ```scala 460 | // 获取(创建)TableEnvironment对象 461 | val tableEnv = TableEnvironment.getTableEnvironment(env) 462 | 463 | val stream: DataStream[Long] = ... 464 | 465 | // 将DataStream转换为带默认字段"f0"的表 466 | val table: Table = tableEnv.fromDataStream(stream) 467 | 468 | // 将DataStream转换为带字段"myLong"的表 469 | val table: Table = tableEnv.fromDataStream(stream, 'myLong) 470 | ``` 471 | #### Tuples (Scala and Java) and Case Classes (Scala only) 472 | Flink支持内建的Tuples并且提供了自己的Tuple类给Java进行使用。DataStreams和DataSet这两种 473 | Tuple都可以转换为表。提供所有字段的名称(基于位置的映射)字段可以被重命名。如果没有指定字段的名称, 474 | 就使用默认的字段名称。如果原始字段名(f0, f1, … for Flink Tuples and _1, _2, … for Scala Tuples)被引用了的话, 475 | API就会使用基于名称的映射来代替位置的映射。基于名称的映射可以起别名并且会进行重排序。 476 | ```scala 477 | // 获取(创建)TableEnvironment对象 478 | val tableEnv = TableEnvironment.getTableEnvironment(env) 479 | 480 | val stream: DataStream[(Long, String)] = ... 481 | 482 | // 将默认的字段重命名为'_1,'_2的DataStream转换为Table 483 | val table: Table = tableEnv.fromDataStream(stream) 484 | 485 | // 将字段名为'myLong,'myString的DataStream转换为Table(基于位置) 486 | val table: Table = tableEnv.fromDataStream(stream, 'myLong, 'myString) 487 | 488 | // 将重排序后字段为'_2,'_1 的DataStream转换为Table(基于名称) 489 | val table: Table = tableEnv.fromDataStream(stream, '_2, '_1) 490 | 491 | // 将映射字段'_2的DataStream转换为Table(基于名称) 492 | val table: Table = tableEnv.fromDataStream(stream, '_2) 493 | 494 | // 将重排序后字段为'_2给出别名'myString,'_1给出别名'myLong 的DataStream转换为Table(基于名称) 495 | val table: Table = tableEnv.fromDataStream(stream, '_2 as 'myString, '_1 as 'myLong) 496 | 497 | // 定义 case class 498 | case class Person(name: String, age: Int) 499 | val streamCC: DataStream[Person] = ... 500 | 501 | // 将默认字段'name, 'age的DataStream转换为Table 502 | val table = tableEnv.fromDataStream(streamCC) 503 | 504 | // 将字段名为'myName,'myAge的DataStream转换为Table(基于位置) 505 | val table = tableEnv.fromDataStream(streamCC, 'myName, 'myAge) 506 | 507 | 将重排序后字段为'_age给出别名'myAge,'_name给出别名'myName 的DataStream转换为Table(基于名称) 508 | val table: Table = tableEnv.fromDataStream(stream, 'age as 'myAge, 'name as 'myName) 509 | ``` 510 | #### POJO (Java and Scala) 511 | Flink支持POJO作为符合类型。决定POJO规则的文档请参考[这里](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/api_concepts.html#pojos) 512 | 513 | 当将一个POJO类型的DataStream或者DataSet转换为Table而不指定字段名称时,Table的字段名称将采用JOPO原生的字段名称作为字段名称。 514 | 重命名原始的POJO字段需要关键字AS,因为POJO没有固定的顺序,名称映射需要原始名称并且不能通过位置来完成。 515 | ```scala 516 | // 获取(创建)TableEnvironment对象 517 | val tableEnv = TableEnvironment.getTableEnvironment(env) 518 | 519 | // Person 是一个有两个字段"name"和"age"的POJO 520 | val stream: DataStream[Person] = ... 521 | 522 | // 将 DataStream 转换为带字段 "age", "name" 的Table(字段通过名称进行排序) 523 | val table: Table = tableEnv.fromDataStream(stream) 524 | 525 | // 将DataStream转换为重命名为"myAge", "myName"的Table(基于名称) 526 | val table: Table = tableEnv.fromDataStream(stream, 'age as 'myAge, 'name as 'myName) 527 | 528 | // 将带映射字段'name的DataStream转换为Table(基于名称) 529 | val table: Table = tableEnv.fromDataStream(stream, 'name) 530 | 531 | // 将带映射字段'name并重命名为'myName的DataStream转换为Table(基于名称) 532 | val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName) 533 | ``` 534 | #### Row 535 | Row数据类型可以支持任意数量的字段,并且这些字段支持null值。当进行Row DataStream或Row DataSet 536 | 转换为Table时可以通过RowTypeInfo来指定字段的名称。Row Type支持基于位置和名称的两种映射方式。 537 | 通过提供所有字段的名称可以进行字段的重命名(基于位置),或者是单独选择列来进行映射/重排序/重命名(基于名称) 538 | ```scala 539 | // 获取(创建)TableEnvironment对象 540 | val tableEnv = TableEnvironment.getTableEnvironment(env) 541 | 542 | // 在`RowTypeInfo`中指定字段"name" 和 "age"的Row类型DataStream 543 | val stream: DataStream[Row] = ... 544 | 545 | // 将 DataStream 转换为带默认字段 "age", "name" 的Table 546 | val table: Table = tableEnv.fromDataStream(stream) 547 | 548 | // 将 DataStream 转换为重命名字段 'myName, 'myAge 的Table(基于位置) 549 | val table: Table = tableEnv.fromDataStream(stream, 'myName, 'myAge) 550 | 551 | // 将 DataStream 转换为重命名字段 'myName, 'myAge 的Table(基于名称) 552 | val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName, 'age as 'myAge) 553 | 554 | // 将 DataStream 转换为映射字段 'name的Table(基于名称) 555 | val table: Table = tableEnv.fromDataStream(stream, 'name) 556 | 557 | // 将 DataStream 转换为映射字段 'name并重命名为'myName的Table(基于名称) 558 | val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName) 559 | ``` 560 | #### Query Optimization 561 | Apache Flink 基于 Apache Calcite 来做转换和查询优化。当前的查询优化包括投影、过滤下推、 562 | 相关子查询和各种相关的查询重写。Flink不去做join优化,但是会让他们去顺序执行(FROM子句中表的顺序或者WHERE子句中连接谓词的顺序) 563 | 564 | 可以通过提供一个CalciteConfig对象来调整在不同阶段应用的优化规则集, 565 | 这个可以通过调用CalciteConfig.createBuilder())获得的builder来创建, 566 | 并且可以通过调用tableEnv.getConfig.setCalciteConfig(calciteConfig)来提供给TableEnvironment。 567 | 568 | #### Explaining a Table 569 | Table API为计算Table提供了一个机制来解析逻辑和优化查询计划,这个可以通过TableEnvironment.explain(table) 570 | 来完成。它返回描述三个计划的字符串信息: 571 | - 关联查询抽象语法树,即未优化过的逻辑执行计划 572 | - 优化过的逻辑执行计划 573 | - 物理执行计划 574 | 575 | 下面的实例展示了相应的输出: 576 | ```scala 577 | val env = StreamExecutionEnvironment.getExecutionEnvironment 578 | val tEnv = TableEnvironment.getTableEnvironment(env) 579 | 580 | val table1 = env.fromElements((1, "hello")).toTable(tEnv, 'count, 'word) 581 | val table2 = env.fromElements((1, "hello")).toTable(tEnv, 'count, 'word) 582 | val table = table1 583 | .where('word.like("F%")) 584 | .unionAll(table2) 585 | 586 | val explanation: String = tEnv.explain(table) 587 | println(explanation) 588 | ``` 589 | 对应的输出如下: 590 | ``` 591 | == 抽象语法树 == 592 | LogicalUnion(all=[true]) 593 | LogicalFilter(condition=[LIKE($1, 'F%')]) 594 | LogicalTableScan(table=[[_DataStreamTable_0]]) 595 | LogicalTableScan(table=[[_DataStreamTable_1]]) 596 | 597 | == 优化后的逻辑执行计划 == 598 | DataStreamUnion(union=[count, word]) 599 | DataStreamCalc(select=[count, word], where=[LIKE(word, 'F%')]) 600 | DataStreamScan(table=[[_DataStreamTable_0]]) 601 | DataStreamScan(table=[[_DataStreamTable_1]]) 602 | 603 | == 物理执行计划 == 604 | Stage 1 : Data Source 605 | content : collect elements with CollectionInputFormat 606 | 607 | Stage 2 : Data Source 608 | content : collect elements with CollectionInputFormat 609 | 610 | Stage 3 : Operator 611 | content : from: (count, word) 612 | ship_strategy : REBALANCE 613 | 614 | Stage 4 : Operator 615 | content : where: (LIKE(word, 'F%')), select: (count, word) 616 | ship_strategy : FORWARD 617 | 618 | Stage 5 : Operator 619 | content : from: (count, word) 620 | ship_strategy : REBALANCE 621 | ``` -------------------------------------------------------------------------------- /doc/table/Overview.md: -------------------------------------------------------------------------------- 1 | 2 | # Table API和SQL 3 | 4 | Apache Flink 有两个关系API--Table API 和 SQL--统一流和批量处理。 5 | Table API是由Scala和Java集成的查询API,它以一种非常直观的方式组合来实现关系操作(例如选择、筛选和链接)。Flink's SQL是基于实现了SQL标准的Apache Calcite实现的。 6 | 无论输入是batch input或者stream input,查询任何借口都会有相同的语义和相同的结果。 7 | 8 | Table API和SQL接口结合就像Flink DataStream和DataSet API一样紧密在一起。你可以基于这些API,很容易的替换所有的API和库。例如,你可以使用CEP库以流的形式抽取,然后 9 | 使用Table API 来分析模式,或者你可能在运行Gelly graph算法预处理数据时用SQL查询来扫描、过滤、聚合批量table。 10 | 11 | 12 | 请注意Table API和SQL特性还不完全,处于积极开发中。不是所有操作都支持Table API、SQL和Stream、batch输入。 13 | 14 | 15 | ## Setup设置 16 | Table API和SQL捆绑在flink-table Maven artifact。为了使用table API和SQL,你必须在你的工程中添加下面依赖。 17 | ```java 18 | 19 | org.apache.flink 20 | flink-table_2.11 21 | 1.6.0 22 | 23 | 24 | 另外,你需要添加Flink’s Scala batch or streaming API的依赖。支持批查询你需要添加: 25 | 26 | 27 | org.apache.flink 28 | flink-scala_2.11 29 | 1.6.0 30 | 31 | 32 | 流查询你需要添加: 33 | 34 | 35 | org.apache.flink 36 | flink-streaming-scala_2.11 37 | 1.6.0 38 | 39 | ``` 40 | 注意:由于Apache Calcite的一个问题,它组织用户加载器被垃圾收集,我们不建议构建一个包含flink-table以来的fat-jar。替代的,我们建议 41 | 在系统类加载器中配置Flink来包括flink-table依赖。这能通过复制flink-table.jar从./opt文件到./lib文件来实现。请参照后面的详细说明。 42 | 43 | 44 | 回到顶部 45 | 接下来 46 | 概念&通用API:Table API的API集与SQL的共享概念 47 | Stream Table API和SQL:Table Api特定流文档或SQL,例如配置时间属性和更新结果。 48 | Table API:Table API支持的API操作 49 | SQL:SQL支持的操作和语法 50 | Table Sources&Sinks:读表并将表发到外部存储系统 51 | UDF:用户定义函数的定义和使用 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /doc/table/TableOverview.md: -------------------------------------------------------------------------------- 1 | # 表相关概述 2 | ## 1. Table API & SQL 3 | 4 | 为了统一流式处理和批处理,Apache Flink提供两种API操作,Table API和SQL。Table API是Scala和Java语言集成查询的API,它以一种更直观的方式来组合复杂的关系操作查询,例如选择,过滤和连接。Flink的SQL操作基于实现了SQL标准的[Apache Calcite](https://calcite.apache.org/)。Flink中指定的查询在这两个接口中所使用的语法和取得的结果都是一样的,无论它的输入来自于批量输入(DataSet)或者是流输入(DataStream) 5 | 6 | 7 | 8 | Table API和SQL接口彼此都是紧密结合在一起的,就像Flink中的DataStream和DataSet API一样。你可以在这些API接口上和建立在这些库上的API中任意进行切换。例如,你先可以使用[ CEP library ](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/libs/cep.html)来提取DataStream中的patterns,再使用Table API来分析这些patterns,或者可以用[ Gelly graph algorithm ](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/libs/gelly/)对数据进行预处理之前使用SQL扫描,过滤和聚合多表批处理。 9 | 10 | 11 | 12 | 请注意:Table API和SQL功能尚未完成,正处于开发阶段中。目前并不是所有的[Table API,SQL]和[流式,批量]功能的组合都支持所有操作。 13 | 14 | 15 | 16 | ## 2. Setup 17 | 18 | Table API和SQL目前都绑定在Maven仓库中的flink-table包中。如果你要使用Table API和SQL你必须在你的项目中添加如下依赖: 19 | 20 | ``` 21 | 22 | org.apache.flink 23 | flink-table_2.11 24 | 1.6.0 25 | 26 | ``` 27 | 28 | 除此之外,若要使用scala批处理或流式处理API,你需要添加依赖。 29 | 30 | 如使用批量查询的话需要添加如下依赖: 31 | 32 | ``` 33 | 34 | org.apache.flink 35 | flink-scala_2.11 36 | 1.6.0 37 | 44 | org.apache.flink 45 | flink-streaming-scala_2.11 46 | 1.6.0 47 | 48 | ``` 49 | 50 | 注意:由于Apache Calcite存在一个issue,它主要是防止用户类加载器被GC收集,我们不推荐使用包含flink-table的这种臃肿jar包依赖方式。反而我们推荐的是将flink-table依赖配置到系统加载器中。也就是说,你可以将flink-table.jar 从./opt目录下拷贝到./lib目录下。详情请见[ 此处 ](https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/linking.html) 51 | 52 | -------------------------------------------------------------------------------- /doc/table/UDF.md: -------------------------------------------------------------------------------- 1 | 2 | # Flink用户自定义函数 3 | 4 | 用户自定义函数是非常重要的一个特征,因为他极大地扩展了查询的表达能力。 5 | 6 | 在大多数场景下,用户自定义函数在使用之前是必须要注册的。对于Scala的Table API,udf是不需要注册的。 7 | 调用TableEnvironment的registerFunction()方法来实现注册。Udf注册成功之后,会被插入TableEnvironment的function catalog,这样table API和sql就能解析他了。 8 | 本文会主要讲三种udf: 9 | * ScalarFunction 10 | * TableFunction 11 | * AggregateFunction 12 | 13 | ## 1. Scalar Functions 标量函数 14 | 15 | 标量函数,是指指返回一个值的函数。标量函数是实现讲0,1,或者多个标量值转化为一个新值。 16 | 17 | 实现一个标量函数需要继承ScalarFunction,并且实现一个或者多个evaluation方法。标量函数的行为就是通过evaluation方法来实现的。evaluation方法必须定义为public,命名为eval。evaluation方法的输入参数类型和返回值类型决定着标量函数的输入参数类型和返回值类型。evaluation方法也可以被重载实现多个eval。同时evaluation方法支持变参数,例如:eval(String... strs)。 18 | 19 | 下面给出一个标量函数的例子。例子实现的事一个hashcode方法。 20 | ```java 21 | public class HashCode extends ScalarFunction { 22 | private int factor = 12; 23 | 24 | public HashCode(int factor) { 25 | this.factor = factor; 26 | } 27 | 28 | public int eval(String s) { 29 | return s.hashCode() * factor; 30 | } 31 | } 32 | 33 | BatchTableEnvironment tableEnv = TableEnvironment.getTableEnvironment(env); 34 | 35 | // register the function 36 | tableEnv.registerFunction("hashCode", new HashCode(10)); 37 | 38 | // use the function in Java Table API 39 | myTable.select("string, string.hashCode(), hashCode(string)"); 40 | 41 | // use the function in SQL API 42 | tableEnv.sqlQuery("SELECT string, HASHCODE(string) FROM MyTable"); 43 | 44 | ```` 45 | 46 | 默认情况下evaluation方法的返回值类型是由flink类型抽取工具决定。对于基础类型,简单的POJOS是足够的,但是更复杂的类型,自定义类型,组合类型,会报错。这种情况下,返回值类型的TypeInformation,需要手动指定,方法是重载 47 | ScalarFunction#getResultType()。 48 | 49 | 下面给一个例子,通过复写ScalarFunction#getResultType(),将long型的返回值在代码生成的时候翻译成Types.TIMESTAMP。 50 | 51 | ```java 52 | public static class TimestampModifier extends ScalarFunction { 53 | public long eval(long t) { 54 | return t % 1000; 55 | } 56 | 57 | public TypeInformation getResultType(signature: Class[]) { 58 | return Types.TIMESTAMP; 59 | } 60 | } 61 | ``` 62 | 63 | ## 2. Table Functions 表函数 64 | 65 | 与标量函数相似之处是输入可以0,1,或者多个参数,但是不同之处可以输出任意数目的行数。返回的行也可以包含一个或者多个列。 66 | 67 | 为了自定义表函数,需要继承TableFunction,实现一个或者多个evaluation方法。表函数的行为定义在这些evaluation方法内部,函数名为eval并且必须是public。TableFunction可以重载多个eval方法。Evaluation方法的输入参数类型,决定着表函数的输入类型。Evaluation方法也支持变参,例如:eval(String... strs)。返回表的类型取决于TableFunction的基本类型。Evaluation方法使用collect(T)发射输出的rows。 68 | 69 | 在Table API中,表函数在scala语言中使用方法如下:.join(Expression) 或者 .leftOuterJoin(Expression),在java语言中使用方法如下:.join(String) 或者.leftOuterJoin(String)。 70 | 71 | Join操作算子会使用表值函数(操作算子右边的表)产生的所有行进行(cross) join 外部表(操作算子左边的表)的每一行。 72 | 73 | leftOuterJoin操作算子会使用表值函数(操作算子右边的表)产生的所有行进行(cross) join 外部表(操作算子左边的表)的每一行,并且在表函数返回一个空表的情况下会保留所有的outer rows。 74 | 75 | 在sql语法中稍微有点区别: 76 | cross join用法是LATERAL TABLE()。 77 | LEFT JOIN用法是在join条件中加入ON TRUE。 78 | 79 | 下面的理智讲的是如何使用表值函数。 80 | ```java 81 | // The generic type "Tuple2" determines the schema of the returned table as (String, Integer). 82 | public class Split extends TableFunction> { 83 | private String separator = " "; 84 | 85 | public Split(String separator) { 86 | this.separator = separator; 87 | } 88 | 89 | public void eval(String str) { 90 | for (String s : str.split(separator)) { 91 | // use collect(...) to emit a row 92 | collect(new Tuple2(s, s.length())); 93 | } 94 | } 95 | } 96 | 97 | BatchTableEnvironment tableEnv = TableEnvironment.getTableEnvironment(env); 98 | Table myTable = ... // table schema: [a: String] 99 | 100 | // Register the function. 101 | tableEnv.registerFunction("split", new Split("#")); 102 | 103 | // Use the table function in the Java Table API. "as" specifies the field names of the table. 104 | myTable.join("split(a) as (word, length)").select("a, word, length"); 105 | myTable.leftOuterJoin("split(a) as (word, length)").select("a, word, length"); 106 | 107 | // Use the table function in SQL with LATERAL and TABLE keywords. 108 | join.md 109 | tableEnv.sqlQuery("SELECT a, word, length FROM MyTable, LATERAL TABLE(split(a)) as T(word, length)"); 110 | // LEFT JOIN a table function (equivalent to "leftOuterJoin" in Table API). 111 | tableEnv.sqlQuery("SELECT a, word, length FROM MyTable LEFT JOIN LATERAL TABLE(split(a)) as T(word, length) ON TRUE"); 112 | 113 | ``` 114 | 115 | 需要注意的是PROJO类型不需要一个确定的字段顺序。意味着你不能使用as修改表函数返回的pojo的字段的名字。 116 | 117 | 默认情况下TableFunction返回值类型是由flink类型抽取工具决定。对于基础类型,简单的POJOS是足够的,但是更复杂的类型,自定义类型,组合类型,会报错。这种情况下,返回值类型的TypeInformation,需要手动指定,方法是重载 118 | TableFunction#getResultType()。 119 | 120 | 下面的例子,我们通过复写TableFunction#getResultType()方法使得表返回类型是RowTypeInfo(String, Integer)。 121 | ```java 122 | public class CustomTypeSplit extends TableFunction { 123 | public void eval(String str) { 124 | for (String s : str.split(" ")) { 125 | Row row = new Row(2); 126 | row.setField(0, s); 127 | row.setField(1, s.length); 128 | collect(row); 129 | } 130 | } 131 | 132 | @Override 133 | public TypeInformation getResultType() { 134 | return Types.ROW(Types.STRING(), Types.INT()); 135 | } 136 | } 137 | ``` 138 | ## 3. Aggregation Functions 聚合函数 139 | 140 | 用户自定义聚合函数聚合一张表(一行或者多行,一行有一个或者多个属性)为一个标量的值。 141 | 142 | 143 | 144 | 上图中是讲的一张饮料的表这个表有是那个字段五行数据,现在要做的事求出所有饮料的最高价。 145 | 146 | 聚合函数需要继承AggregateFunction。聚合函数工作方式如下: 147 | 首先,需要一个accumulator,这个是保存聚合中间结果的数据结构。调用AggregateFunction函数的createAccumulator()方法来创建一个空的accumulator. 148 | 随后,每个输入行都会调用accumulate()方法来更新accumulator。一旦所有的行被处理了,getValue()方法就会被调用,计算和返回最终的结果。 149 | 150 | 对于每个AggregateFunction,下面三个方法都是比不可少的: 151 | createAccumulator() 152 | accumulate() 153 | getValue() 154 | 155 | flink的类型抽取机制不能识别复杂的数据类型,比如,数据类型不是基础类型或者简单的pojos类型。所以,类似于ScalarFunction 和TableFunction,AggregateFunction提供了方法去指定返回结果类型的TypeInformation,用的是AggregateFunction#getResultType()。Accumulator类型用的是AggregateFunction#getAccumulatorType()。 156 | 157 | 除了上面的方法,这里有一些可选的方法。尽管有些方法是让系统更加高效的执行查询,另外的一些在特定的场景下是必须的。例如,merge()方法在会话组窗口上下文中是必须的。当一行数据是被视为跟两个回话窗口相关的时候,两个会话窗口的accumulators需要被join。 158 | 159 | AggregateFunction的下面几个方法,根据使用场景的不同需要被实现: 160 | retract():在bounded OVER窗口的聚合方法中是需要实现的。 161 | merge():在很多batch 聚合和会话窗口聚合是必须的。 162 | resetAccumulator(): 在大多数batch聚合是必须的。 163 | 164 | AggregateFunction的所有方法都是需要被声明为public,而不是static。定义聚合函数需要实现org.apache.flink.table.functions.AggregateFunction同时需要实现一个或者多个accumulate方法。该方法可以被重载为不同的数据类型,并且支持变参。 165 | 166 | 在这里就不贴出来AggregateFunction的源码了。 167 | 168 | 下面举个求加权平均的栗子 169 | 为了计算加权平均值,累加器需要存储已累积的所有数据的加权和及计数。在栗子中定义一个WeightedAvgAccum类作为accumulator。尽管,retract(), merge(), 和resetAccumulator()方法在很多聚合类型是不需要的,这里也给出了栗子。 170 | ```java 171 | 172 | /** 173 | * Accumulator for WeightedAvg. 174 | */ 175 | public static class WeightedAvgAccum { 176 | public long sum = 0; 177 | public int count = 0; 178 | } 179 | 180 | /** 181 | * Weighted Average user-defined aggregate function. 182 | */ 183 | public static class WeightedAvg extends AggregateFunction { 184 | 185 | @Override 186 | public WeightedAvgAccum createAccumulator() { 187 | return new WeightedAvgAccum(); 188 | } 189 | 190 | @Override 191 | public Long getValue(WeightedAvgAccum acc) { 192 | if (acc.count == 0) { 193 | return null; 194 | } else { 195 | return acc.sum / acc.count; 196 | } 197 | } 198 | 199 | public void accumulate(WeightedAvgAccum acc, long iValue, int iWeight) { 200 | acc.sum += iValue * iWeight; 201 | acc.count += iWeight; 202 | } 203 | 204 | public void retract(WeightedAvgAccum acc, long iValue, int iWeight) { 205 | acc.sum -= iValue * iWeight; 206 | acc.count -= iWeight; 207 | } 208 | 209 | public void merge(WeightedAvgAccum acc, Iterable it) { 210 | Iterator iter = it.iterator(); 211 | while (iter.hasNext()) { 212 | WeightedAvgAccum a = iter.next(); 213 | acc.count += a.count; 214 | acc.sum += a.sum; 215 | } 216 | } 217 | 218 | public void resetAccumulator(WeightedAvgAccum acc) { 219 | acc.count = 0; 220 | acc.sum = 0L; 221 | } 222 | } 223 | 224 | // register function 225 | StreamTableEnvironment tEnv = ... 226 | tEnv.registerFunction("wAvg", new WeightedAvg()); 227 | 228 | // use function 229 | tEnv.sqlQuery("SELECT user, wAvg(points, level) AS avgPoints FROM userScores GROUP BY user"); 230 | 231 | ``` 232 | ## 4. 实现udf的最佳实践经验 233 | 234 | Table API和SQL 代码生成器内部会尽可能多的尝试使用原生值。用户定义的函数可能通过对象创建、强制转换(casting)和拆装箱((un)boxing)引入大量开销。因此,强烈推荐参数和返回值的类型定义为原生类型而不是他们包装类型(boxing class)。Types.DATE 和Types.TIME可以用int代替。Types.TIMESTAMP可以用long代替。 235 | 236 | 我们建议用户自定义函数使用java编写而不是scala编写,因为scala的类型可能会有不被flink类型抽取器兼容。 237 | 238 | 用Runtime集成UDFs 239 | 240 | 有时候udf需要获取全局runtime信息或者在进行实际工作之前做一些设置和清除工作。Udf提供了open()和close()方法,可以被复写,功能类似Dataset和DataStream API的RichFunction方法。 241 | 242 | Open()方法是在evaluation方法调用前调用一次。Close()是在evaluation方法最后一次调用后调用。 243 | 244 | Open()方法提共一个FunctionContext,FunctionContext包含了udf执行环境的上下文,比如,metric group,分布式缓存文件,全局的job参数。 245 | 246 | 通过调用FunctionContext的相关方法,可以获取到相关的信息: 247 | 248 | 方法描述 249 | * getMetricGroup() - 并行子任务的指标组 250 | * getCachedFile(name) -分布式缓存文件的本地副本 251 | * getJobParameter(name, defaultValue) - 给定key全局job参数。 252 | 253 | 下面,给出的例子就是通过FunctionContext在一个标量函数中获取全局job的参数。 254 | ```java 255 | public class HashCode extends ScalarFunction { 256 | 257 | private int factor = 0; 258 | 259 | @Override 260 | public void open(FunctionContext context) throws Exception { 261 | // access "hashcode_factor" parameter 262 | // "12" would be the default value if parameter does not exist 263 | factor = Integer.valueOf(context.getJobParameter("hashcode_factor", "12")); 264 | } 265 | 266 | public int eval(String s) { 267 | return s.hashCode() * factor; 268 | } 269 | } 270 | 271 | ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); 272 | BatchTableEnvironment tableEnv = TableEnvironment.getTableEnvironment(env); 273 | 274 | // set job parameter 275 | Configuration conf = new Configuration(); 276 | conf.setString("hashcode_factor", "31"); 277 | env.getConfig().setGlobalJobParameters(conf); 278 | 279 | // register the function 280 | tableEnv.registerFunction("hashCode", new HashCode()); 281 | 282 | // use the function in Java Table API 283 | myTable.select("string, string.hashCode(), hashCode(string)"); 284 | 285 | // use the function in SQL 286 | tableEnv.sqlQuery("SELECT string, HASHCODE(string) FROM MyTable"); 287 | ``` -------------------------------------------------------------------------------- /doc/table/sql.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pic/CEP/checkpoint_tuning.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/CEP/checkpoint_tuning.PNG -------------------------------------------------------------------------------- /pic/CEP/checkpoint_tuning.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 31 | 33 | 35 | 36 | 38 | image/svg+xml 39 | 41 | 42 | 43 | 44 | 45 | 48 | 51 | 55 | time 61 | 65 | 69 | 73 | 77 | 81 | 85 | 89 | time 95 | 99 | 103 | 107 | 111 | time 117 | 121 | 125 | 129 | 133 | 137 | Regular Operation 143 | (some 149 | checkpointing 155 | - 161 | free time) 167 | 171 | 175 | 179 | 183 | 187 | 191 | 195 | Checkpoint Interval 201 | 205 | 209 | 213 | 217 | 221 | 225 | 229 | 233 | 237 | 241 | 245 | Duration of Checkpoint 251 | Processing without 257 | a checkpoint in progress 263 | 267 | 271 | 275 | 279 | Checkpoint Interval 285 | Checkpoint completes late, 291 | Immediately triggers next 297 | 301 | Base Checkpoint 307 | Interval 313 | 317 | 321 | 325 | 329 | 333 | 337 | 341 | 345 | 349 | 353 | 357 | 361 | 365 | 369 | 373 | 377 | 381 | 385 | 389 | 393 | 397 | 401 | 405 | 409 | 413 | 417 | 421 | Checkpoint completes late 427 | 431 | Min. 437 | t 443 | ime between checkpoints 449 | 453 | Degraded Operation 459 | (constantly 465 | checkpointing 471 | ) 477 | Guaranteeing Progress 483 | (ensuring some checkpoint 489 | - 495 | free time) 501 | 505 | 506 | 507 | 508 | -------------------------------------------------------------------------------- /pic/CEP/local_recovery.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/CEP/local_recovery.PNG -------------------------------------------------------------------------------- /pic/CEP/创建跳过策略.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/CEP/创建跳过策略.png -------------------------------------------------------------------------------- /pic/CEP/跳过策略.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/CEP/跳过策略.png -------------------------------------------------------------------------------- /pic/ManagingExecution/plan_visualizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/ManagingExecution/plan_visualizer.png -------------------------------------------------------------------------------- /pic/WhatIsFlink/flinkCluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/WhatIsFlink/flinkCluster.png -------------------------------------------------------------------------------- /pic/WhatIsFlink/function-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/WhatIsFlink/function-state.png -------------------------------------------------------------------------------- /pic/WhatIsFlink/local-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/WhatIsFlink/local-state.png -------------------------------------------------------------------------------- /pic/WhatIsFlink/有界流无界流图示.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/WhatIsFlink/有界流无界流图示.png -------------------------------------------------------------------------------- /pic/stream/IntervalJoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/stream/IntervalJoin.png -------------------------------------------------------------------------------- /pic/stream/SessionWindowsJoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/stream/SessionWindowsJoin.png -------------------------------------------------------------------------------- /pic/stream/SlideWindowsJoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/stream/SlideWindowsJoin.png -------------------------------------------------------------------------------- /pic/stream/TumbleWindowsJoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/stream/TumbleWindowsJoin.png -------------------------------------------------------------------------------- /pic/stream/needAsyncIO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/stream/needAsyncIO.png -------------------------------------------------------------------------------- /pic/table/udf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crestofwave1/oneFlink/805590e4b5d61664cfa9d742075b775be59b47df/pic/table/udf.png --------------------------------------------------------------------------------