1. 程序和数据流
Flink程序构建的基本单元是stream和transformation(请注意,DataSet实质上也是stream)。一个stream是一个中间结果,一个transformation是一个操作,该操作以一个或多个stream为输入,计算输出一个或多个stream为结果。
在运行时,Flink上运行的程序会被映射成streaming dataflows,它包含了streams和transformations操作。每个dataflow以一个或者多个source开始,以一个或多个sink结束。dataflow类似于有向无环图(DAG),特殊形式的环也允许通过iteration构建。
在大多数情况下,程序中的transformation和dataflow中的操作是一一对应关系,但有时候一个transformation可能对应了多个操作。
1.1 并行数据流 Parallel Dataflows
Flink程序与生俱来的就是并行和分布式的。Streams被分割成stream patition, Operators被被分割成operator subtasks。这些subtasks在不同的机器(容器)上的不同的线程中运行,彼此独立,互不干扰。 一个操作的operator subtask的数目,被称为parallelism(并行度)。一个stream的并行度,总是等于生成它的(operator)操作的并行度。一个Flink程序中,不同的operator可能具有不同的并行度。
- One-to-One Streams(例如source和map()之间)维护着分区和元素的顺序。这意味着map操作看到的元素个数和顺序跟source操作看到的元素个数和顺序是相同的。
- Redistributing Streams(例如map()和keyBy、Window之间,还有keyBy、Window和sink之间)的分区发生改变。每个operator subtask把数据发送到不同的目标subtask上,其发送的依据是选择何种的transformation。例如keyBy操作(基于Hash重新分区),broadcast()或者 rebalance() (随机重新分区)。在一个redistributing 交换中,元素之间的顺序仅仅在每一个发送-接受task对中才会被维持。
1.2 任务和操作链 Tasks & Operator Chains
为了达到分布式执行的目的,Flink把subtasks链在一起形成tasks。每一个任务(task)被一个线程执行。将操作链在一起形成task是非常有效的优化,它能减少线程之间的切换,提高吞吐量,降低延时。操作链的行为可以同API配置指定。下面的图展示了5个sub task,以5个并行的线程来执行。
2. 分布式执行 Distributed Execution
Flink是一个主结构的分布式系统,其Master被成为JobManager,其Slave(worker)被成为TaskManager;Flink管理分配资源的单位是Slot。
2.1 集群角色 Master,Worker,Client
Flink集群启动后,会有两种进程,一种是JobManager(Master),一种是TaskManager(Worker),我们可以通过jps
或者ps -ef | grep java
命令来查看Flink进程。
- Master进程(JobManager),用于分布式执行,调度任务,协调检查点(checkpoint),协调失败恢复等。Flink集群中至少有一个Master进程;为了高可用性,通常会有多个Master节点,选举其中一个作为leader,其余作为standby。
- Worker进程(TaskManager),用于执行dataflow上的task(subtask),缓存和交换数据流。TaskManager至少有一个。
Flink集群的Master进程和Worker进程可以通过多种方式启动,既可以在物理机上部署启动,也可以通过容器技术、或者像YARN这样的资源管理框架启动。Worker连接到Master,告知自身可用,并等待分配任务。
Client不是Flink集群运行时的一部分,它作为客户端,用来准备和发送数据流到Master,在这之后,客户端可以断开,或者保持连接接受结果数据。客户端程序可以是java或者Scala程序,也可以通过命令行的方式(bin/flink run...)来触发Flink集群执行。
2.2 资源管理 Workers, Slots, Resources
每个Worker都是一个JVM进程,可以在不同的线程里执行一个或者多个subtasks。Worker通过task slots来管理接受处理多少个任务。每个task slot代表了固定额度的资源,是TaskManager拥有的资源的子集。例如,一个TaskManager有3个slot,那么每个slot占据其1/3的资源。 采用slot来分配资源,避免了任务之间的资源竞争。需要注意的是,Flink的slot仅仅描述和隔离了内存资源,并不包括CPU资源的隔离。
通过调整slot的数量,我们可以调节subtask之间的资源隔离情况。如果每个TaskManager上只有一个slot,意味着该任务将会独占资源;如果有多个slot,意味着更多的任务共享JVM资源。同一个JVM进程中的任务将会共享TCP连接和心跳信息。它们也可能共享数据集和数据结构,因此减少了每个任务的负载。
默认情况下,如果subtask来自于不同的task,但来自于同一个job,Flink允许这些subtask共享slot。这样可能致使一个slot持有该job的整个pipeline。允许共享slot有两个主要的好处:
- Flink集群需要许多slot来让job达到最高的并行度,不用计算一个程序需要多少task。
- 更容易提高资源利用率。如果没有slot共享,那些非密集型的任务(source、map)将会阻塞和密集的window subtask一样多的资源。正是因为了有了slot共享,可以提高2-6倍的并发度,同时仍然保证subtask之间合理的共享slot。
slot共享行为可以通过API控制,以防止不合理的共享,这个机制称为 resource groups,它定义了哪些subtask可以共享的slot。
一个约定俗成的规则是,task slot推荐的默认值是cpu的核数。对于超线程技术,每个slot占用两个或者更多的线程上下文。
3. 时间和窗口 Time and Window
聚合事件(例如count、sum)的工作在流计算上和批处理有些不同。流计算中,不可能一次性统计所有的元素并且返回统计结果;因为流通常是无界的。取而代之的是,在流上做count/sum等聚合计算,可以限定window(窗口),例如统计最近5分钟的数量,或对最近的100个元素求和。
窗口可以是时间驱动的(比如,每30秒),也可以数据驱动的(比如,每100个元素)。通常窗口可以区分为:tumbing windows(不重叠),sliding windows(有重叠)和session window(有空隙的活动)。
3.1 时间 Time
在流计算编程过程中,当我们提到时间(Time),可能有不同的含义:
- Event Time 是事件的创建时间,通常用时间戳来描述,例如由传感器或者生产服务来附加。Flink通过timestamp assigners访问事件时间。
- Ingestion Time 指事件从source operator进入Flink dataflow的时间。
- Processing Time 指执行一个基于时间的操作的本地时间。
更多关于处理时间的细节,可以参考event time docs。
4. 状态和容错 State and Fault Tolerance
4.1 状态 State
在dataflow中的某一时刻,许多操作仅仅关注一个独立的事件(例如一个事件解析器),有的操作能记住多个独立的事件(例如window操作)。这些操作被成为是有状态的(stateful)。
这些有状态的操作的状态是由一个可以被认为是key/value的存储维护的。这些状态是分区和分布式的,和流一起被有状态的操作(stateful operator)读取。因此,访问key/value的状态仅能在keyed Streams(执行keyBy()函数之后产生)中进行,并且只能通过当前事件的key来访问其值。对齐stream的key和状态,可以确保所有状态的更新都是本地操作,在不需要事务开销的情况下保证一致性。这个对齐也允许flink重新分步状态,并显示的调整stream的分区。
4.2 检查点 Checkpoints for Fault Tolerance
Flink实现了失败容忍机制,采用流重放(Stream replay)和检查点(checkpoint)结合的方式。一个检查点定义了流和状态的一致点,在该点streaming dataflow可以恢复并维持一致性(exactly-once的处理语义)。最新的检查点之后的事件和状态更新,将会在输入流中重放。
4.3 状态的存储 State Backends
为key/value构建索引的数据结构最终存储的地方取决于存储的选择,可以是内存中基于hash的map,也可以是RocksDB。为了定义持有状态的数据结构,状态的存储也实现了基于时间点的快照机制,即对key/value的状态做快照,并将快照作为检查点的一部分来存储。
5. 基于流的批处理 Batch on Streaming
Flink把批处理程序当作一种特殊的流处理程序,把批处理看作是有界限的流(有限数量的元素)。一个DataSet在内部被当作是一个流。因此上面的这些适用于流处理的这些概念在批处理中同样适用,只有很少的几个例外:
DataSet API不适用检查点。恢复机制是完整重放流数据,这是合理的,因为输入的数据是有限的。它将开销更多的引入在恢复操作上,但另一方面也使得运行时的常规流程的代价更低,因为它避免了检查点机制。
有状态的操作使用了简单的in-memory/out-of-core的数据结构,而不是基于key/value的索引机制。
DataSet API引进了独特的同步迭代机制(superstep-based),仅限于用在有界的流。更多的内容,可以查看这篇文档iteration docs。
原文地址:https://ci.apache.org/projects/flink/flink-docs-release-1.1/concepts/concepts.html
(完)