【Flink 流处理框架 1.7.2 入门】Flink 入门 2019_12_22

Flink

在这里插入图片描述

2014年4月Stratosphere代码捐赠给了Apache,同年12月,以Flink (德语:快速灵巧) 为名,一跃成为基金会顶级项目。

许多系统都会产生连续的事件流,如行驶中的汽车发射出GPS信号,金融交易,移动通信基站与繁忙的智能手机进行信号交换,网络流量,机器日志,工业传感器和可穿戴设备的测量结果,等等。 但现实中,企业常见的数据架构仍旧假设数据是有头有尾的有限集,而不是连续产生无穷无尽的无限集。 Flink为无界数据提供流处理,并用同一种技术实现批处理

在这里插入图片描述

为何而生?

实时数据流处理的需求性能欠佳 ——> Flink, 开源、分布式、高性能、随时可用以及准确的流处理框架。
用于对无界有界数据流进行有状态计算。被设计在所有常见的集群环境中运行,以内存和任意规模来并行执行计算。

现实挑战?

传感器、金融、航空、网站、轨道交通、银行、电信 面临大量实时数据挑战(拥有高时效性的信息即是金钱)
如果使用传统数据库,那么获取数据/信息的速度将会非常慢,甚至会因时间延迟而失去准确性,从而诞生 流处理框架
但,这不代表流处理框架会替代数据库(远远不能替代),而是说在数据库处理不好时,流处理器提供了更好的解决方案。这样做也使数据库只需专注于 数据的持久化存储,不用再参与对当前业务状态的实时分析。

满足哪些期望?

超低延迟、高吞吐,处理中断,低开销,基于事件时间,可处理乱序事件流,精准替换流数据
处理中断:能使系统在崩溃之后重新启动,并且产出准确的结果;换句话说,可以容错,能保证 exactly-once 精确一致

流处理技术演变

急先锋: Apache Storm

Storm 提供了低延迟的流处理,但是它为实时性付出了一些代价: 很难实现高吞吐,并且其正确性没能达到通常所需的水平。换句话说,它并不能保证exactly-once; 即便是它能够保证的正确性级别,其开销也相当大(保存计算状态)。
不正确的原因在于: 结果会取决于事件到达的时间和顺序。即使事件没有丢失,也可能不止一次地处理它们。

改良:Apache Spark

在低延迟和高吞吐的流处理系统中维持良好的容错性是非常困难的,但为了得到有保障的准确的计算状态,产生一种替代方法:将连续的事件流数据分割成一系列微小的批量作业。如果分割得足够小(即所谓的微批处理作业),计算就可近乎实现真正的流处理。但所谓微批,即攒多个事件为一个批次,故此一定存在攒批延迟,所以不可能做到完全实时。
但这样的方法实现了令每个简单的应用程序都可以仅有几秒甚至几亚秒的计算延迟。这就是在Spark批处理引擎上运行的 SparkStreaming 所使用的方法。

更重要的是,使用微批处理方法,可以实现 exactly-once 精确一致,从而保障状态的一致性。如果一个微批处理作业失败了,它可以重新运行。

然而,通过批处理作业来模拟流处理,会导致开发和运维相互交错。完成间歇性的批处理作业所需的时间和数据到达的时间紧密耦合,任何一个物理节点的延迟都可能导致不一致(或者说错误)的结果。 另外,使用这种方法的计算有着糟糕的用户体验,尤其是那些对延迟比较敏感的作业,而且人们需要在写业务代码时花费大量精力来提升性能。

终结:Apache Flink

与Storm和SparkStreaming类似的其他流处理技术同样可以提供一些有用的功能,但是没有一个像Flink那样功能如此齐全
真正实现基于事件流处理的实时流计算框架,低延迟,高吞吐,基于事件时间,准确性,开发运维简单。

Flink 拥有诸多重要的流式计算功能。其他框架为了实现这些功能,都不得不付出代价。
Storm为实现低延迟,做不到高吞吐,也不能在硬件故障时准确地处理计算状态;
SparkStreaming通过采用微批处理方法实现了高吞吐和容错性,但是牺牲了低延迟和实时处理能力,也不能使窗口与自然时间相匹配,基于机器时间,并且表现力欠佳需要大量调优。

在这里插入图片描述

第一代流处理技术( lambda架构 ):低延迟。结果取决于事件到达的时间和顺序。

第二代流处理技术( Batch处理 ):高吞吐, 故障保证 exactly -once。 结果仍然取决于事件到达时间和顺序。

第三代流处理技术( Stream处理 ): 解决结果对事件到达时间和顺序的依赖性,解决了延迟/吞吐量无法同时保证问题。

Flink VS Spark 区别

(1)流 与 微批

Micro Batching 模式(Spark)

Micro-Batching 计算模式认为"流是批的特例",流计算就是将连续不断的 批 进行持续计算,如果批足够小那么就有足够小的延时,在一定程度上满足了99%的实时计算场景。 剩下的1% 就是无法避免的 “自然流数据流入系统进行攒批延迟” 。

Native Streaming 模式(Flink)

Native Streaming 计算模式认为 “批是流的特例” ,该模式对到来的每条数据都直接进行计算不攒批,延时性能达到更低。
当然 Native Streaming 模式框架 很容易实现 Micro-Batching和Batching 模式的计算,Flink 就是 Native Streaming 计算模式的 流批统一计算引擎

(2)RDD 与 事件

Spark采用 RDD 模型,达到比 MapReduce 计算快100倍的显著优势,是对Hadoop生态大幅升级换代。
RDD弹性数据集是分割为固定大小的批数据,并提供了丰富的底层API对数据集做操作。为持续降低使用门槛,Spark社区开始开发高阶API(DataFrame DataSet),Spark SQL作为统一的API,掩盖了底层,同时针对性地做SQL逻辑优化和物理优化,非堆存储优化也大幅提升了性能。

SparkStreaming,采用 DStream (Discretized 离散化的 Stream) 模型, DStream和RDD模型类似,把一个实时进来的无限数据分割为一个个小批数据集合DStream, 定时器定时通知处理系统去处理这些微批数据。

SparkStreaming 劣势非常明显,API少、难胜任复杂的流计算业务,调大吞吐量而不触发背压是个体力活。不支持乱序处理,或者说很难处理乱序的问题——Spark Streaming仅适合简单的流处理。近来,Spark 推出了Structured Streaming 为解决这个早期没有被重视的 流处理问题, 不如Flink做的完美。当然现在还在优化阶段。

Flink 的基本数据模型 是 数据流,及事件(Event)的序列。 数据流与 表或者数据块 是完全等效的。流可以是无边界的无限流,即一般意义上的流处理。也可以是有边界的有限流,这样就是批处理。

(3)运行时架构

Spark批计算 是把 DAG 划分为不同 stage,DAG节点之间有血缘关系,在运行期间一个stage的task任务列表执行完毕,销毁再去执行下一个stage;Spark Streaming 则是对持续流入的数据划分一个批次,定时去执行批次的数据运算。Structured Streaming将无限输入流保存在状态存储中,对流数据做微批或实时的计算,跟Dataflow模型比较像。
需要注意的, Spark Core写的逻辑,使用Spark Streaming需要再写一遍,无法复用,这是致命缺陷。

Flink有统一的 runtime,在此之上可以是Batch API、Stream API、ML、Graph、CEP等,DAG中的节点上执行上述模块的功能函数,DAG会一步步转化成ExecutionGraph,即物理可执行的图,最终交给调度系统。节点中的逻辑在资源池中的 task 上被 apply 执行,task和Spark中的task类似,都对应线程池中的一个线程。在流计算的运行时架构方面,Flink明显更为统一且优雅一些。

特点

有状态流处理

日常生活中,几乎没有场景可以一次性生成全部(有限)事件集,都是不断的,无限事件流。 有状态的流处理就是用于处理这种无限事件流的应用程序设计模式。事件通常从事件日志中提取输入。

什么叫做有状态:状态==中间数据(相对于最终的计算结果),准确的来说: 能够存储和访问中间数据

当应用程序收到一个新事件时,它可以从状态中读取数据,或者向该状态写入数据,总之可以执行任何计算。原则上讲,我们可以在各种不同的地方存储和访问状态,包括程序变量(内存)、本地文件(磁盘),或嵌入式或外部数据库。

Flink 将应用程序状态,存储在内存或者嵌入式数据库。由于Flink是一个分布式系统,因此需要保护本地状态以防止在应用程序或计算机故障时数据丢失。Flink通过定期将应用程序状态的一致性检查点(check point)写入远程且持久的存储。 如果发生故障,Flink将从先前的检查点(check point)恢复其状态,并重置事件日志上的读取位置,这样就可以恢复整个应用。应用程序将重放(并快进)事件日志中的输入事件,直到它到达流的尾部。

有状态的流处理是一种通用且灵活的设计架构,可用于许多不同的场景,三类通常使用有状态流处理实现的应用程序:(1)事件驱动应用程序,(2)数据管道应用程序,以及(3)数据分析应用程序。将应用程序分类描述,是为了强调有状态流处理适用于多种业务场景;而实际的应用中,往往会具有多种特征。

事件驱动 Event-Driven

事件驱动型应用是一类具有状态的应用,它从一个或多个事件流提取数据,并根据到来的事件触发计算、状态更新或其他外部动作。比较典型的就是以Kafka为代表的消息队列几乎都是事件驱动型应用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YJClMbey-1577001811449)(https://confucianzuoyuan.github.io/flink-tutorial/images/usecases-eventdrivenapps.png)]

流与批的世界观

批处理 特点是 有界、持久化、大量,非常适合需要访问全量记录才能完成的计算工作,一般用于离线统计。
流处理 特点是 无界、实时,不针对数据集合执行操作,而是对每一个接收到的数据执行操作,一般用于实时统计。

在Spark的世界观中,一切都是由批次组成的,离线数据是一个大批次,而实时数据是由一个一个无限的小批次组成的。

而在Flink的世界观中,一切都是由流组成的,离线数据是有界限的流,实时数据是一个没有界限的流。

以流为世界观的架构,获得的最大好处就是具有极低延迟

三层封装API

在这里插入图片描述

Flink 提供的最高层级的抽象是SQL。这一层抽象在语法与表达能力上与Table API类似,但是是以SQL查询表达式的形式表现程序。SQL抽象与Table API交互密切,同时SQL查询可以直接在Table API定义的表上执行。

Table API 是以表为中心的声明式编程,其中表可能会动态变化(在表达流数据时)。Table API遵循(扩展的)关系模型:表有二维数据结构(schema)(类似于关系数据库中的表),同时API提供与RDBMS相似的操作。Table API程序在执行之前会经过内置优化器进行优化。

核心API (Core APIs):DataStream API(有界或无界流数据),及 DataSet API(有界数据集)。这些API为数据处理提供了通用的构建模块,比如由用户定义的多种形式的转换(transformations),连接(joins),聚合(aggregations),窗口操作(window)等等。DataSet API为有界数据集提供了额外的支持,例如循环与迭代。这些API处理的数据类型以类(classes)的形式由各自的编程语言所表示。

底层API: ProcessFunction 与 DataStream API 相集成,使其可以对某些特定的操作进行底层的抽象,它允许用户可以自由地处理来自一个或多个数据流的事件,并使用一致的容错的状态。除此之外,用户可以注册事件时间并处理时间回调,从而使程序可以处理复杂的计算。

不同层级的API 具有不同的权衡表现力和易用性。

其他重要特质
  • 基于事件时间(event-time)和处理时间(processing-time)语义。
    即是对于无序事件流,基于事件时间(event-time)语义仍然能提供一致且准确的结果。而基于处理时间(processing-time)语义可用于具有极低延迟要求的应用程序。
  • 精确一次(exactly-once)的状态一致性保证
  • 低延迟,高吞吐(毫秒级延迟,每秒处理数百万个事件)
  • 高扩展性,高可用性(Yarn集群,Mesos集群;无单点故障,故障恢复,动态扩展)
  • 无缝连接分布式系统(Kafka,Elasticsearch,HDFS,JDBC)
  • 不仅是优秀的流处理器,也是成熟的 批 处理器
  • 拥有嵌入式执行模式,可在单个JVM进程中启动应用程序和整个Flink系统,一般用于在IDE中运行和调试Flink作业

概念

计算状态: 若依赖连续的流事件来计算结果,必须将数据从上一个事件接收保留到下一个事件接收。这些保存的数据叫作计算状态。准确处理计算状态对计算结果的一致性至关重要。在故障或中断之后能够继续准确地更新状态是容错的关键。

无界数据流:无界数据流有一个开始但是没有结束,它们不会在生成时终止并提供数据,必须连续处理无界流,也就是说必须在获取后立即处理event。对于无界数据流我们无法等待所有数据都到达,因为输入是无界的,并且在任何时间点都不会完成。处理无界数据通常要求以特定顺序(例如事件发生的顺序)获取event,以便能够推断结果完整性。

有界数据流:有界数据流有明确定义的开始和结束,可以在执行任何计算之前通过获取所有数据来处理有界流,处理有界流不需要有序获取,因为可以始终对有界数据集进行排序,有界流的处理也称为批处理。

三层 API:SQL/Table API,DataStream/DataSet API,ProcessFunction

Exactly-once 故障语义:确保在发生故障时,每个输入记录仅对结果产生一次影响(exactly -once)

Lambda模型:两套数据处理系统,快速层保证低延迟、数据的实时性,批处理层保证数据的准确性。应用程序会合并快速表中的近似结果和批处理表中的准确结果,然后消费最终的结果。
缺点:对一个应用程序,做出两个语义上等效的逻辑实现,用于两个独立的、具有不同API的处理系统。其次,流处理器计算的结果只是近似的。最后,lambda架构很难建立和维护。

在这里插入图片描述

传统数据处理架构事务处理(OLTP),分析处理(OLAP)

事务处理: 企业资源规划(ERP)系统,客户关系管理(CRM)软件和基于Web的应用程序。通常拥有DAO层设计, 用于数据处理(应用程序本身)和数据存储(事务数据库系统)。 通常多个应用程序会同时用到相同的数据表示,共享相同的数据存储,所以当应用程序扩展时,事务处理架构更改表的结构或扩展数据库,需要仔细的规划和大量的更改工作。

当然,现在对于事务处理架构的应用程序 紧耦合 问题,采用 微服务设计模式。微服务被设计为小型、完备且独立的应用程序。通过将几个微服务相互连接来构建更复杂的应用程序,这些微服务仅通过标准化接口(例如Restful HTTP连接)进行通信。由于微服务严格地彼此分离并且仅通过明确定义的接口进行通信,因此每个微服务都可以用不同技术栈来实现,包括编程语言、类库和数据存储。

分析处理:大量数据存储在各种事务数据库系统中,它们可为业务运营提供高价值/降低成本的参考意见,通常汇总起来联合分析时更有价值。一般情况下不会直接在事务数据库上运行分析查询,而是复制数据到数据仓库,并且数据通常要转换为通用格式——这个过程称为extract-transform-load(ETL)。

数据仓库并不是将所有数据插入关系数据库系统,而是将所有数据写入Hadoop的分布式文件系统(HDFS)、或其他大数据存储库,如Apache HBase,以较低的成本提供大容量存储容量。驻留在此类存储系统中的数据可通过SQL-on-Hadoop引擎查询和处理,例如Apache Hive,Spark 或 Impala。但是基础结构与传统数据仓库架构基本相同。

事件驱动应用程序

事件驱动的应用程序是有状态的流应用程序,它们使用特定的业务逻辑提取事件流并处理事件。处理方法比如: 触发诸如发送警报、或电子邮件,或者 将事件写入向外发送的事件流以供另一个应用程序使用。
常见场景: 实时推荐,行为模式检测或复杂事件处理,异常检测。

事件驱动应用程序是微服务的演变。它们通过事件日志通道(Kafka)而不是REST HTTPAPI调用进行通信,将应用程序数据保存为本地状态,而不是将其写入外部数据存储区(例如关系数据库或键值数据库)。

与事务性应用程序或微服务相比,事件驱动的应用程序具有多种优势。与读写远程数据库相比,本地状态访问提供了非常好的性能。扩展性和容错性都由流处理器来保证,并且以事件日志作为输入源,应用程序的整个输入数据可以可靠地存储,并且可以确定性地重放。此外,Flink可以将应用程序的状态重置为先前的保存点(save point),从而可以在不丢失状态的情况下更新或重新扩展应用程序。

数据管道应用程序

存在多种的数据存储系统,如 RDBSM、事件日志、分布式文件系统,内存缓存(Redis)和搜索索引(ES)。这些系统以不同的格式和数据结构存储数据,为特定的访问模式提供最佳性能。公司通常将相同的数据存储在多个不同的系统中,以提高数据访问的性能。 不过为了统计分析,常见做法是通过 定期ETL作业 将不同存储系统中数据同步。 但是,它们不能满足当今许多场景的延迟要求,所以,以较低的延迟,来提取、转换和插入数据是有状态流处理应用程序的另一个常见应用场景。数据管道实现低延迟的ETL,所以在某种程度上,通过使用数据管道应用程序将数据导入存储区来减少延迟。

操作数据管道的流处理器还应具有许多源(source)和接收器(sink)的连接器,以便从各种存储系统读取数据并将数据写入各种存储系统。 Flink完整的实现了这些功能。

实时流分析应用程序

过去通过 ETL作业 定期将数据导入数据存储区,数据分析是由即席查询(用户自定义查询)或设定好的一般查询完成。 定期将数据加载到数据分析系统是多年来最优选择,但这也给分析管道带来了相当大的延迟,而且无法避免。但现在, 通常要求必须能够实时收集数据,并立即对其进行统计分析等。

流式分析应用程序不是等待定期触发,而是连续地提取事件流,并且通过纳入最新事件来更新其计算结果,这个过程是低延迟的。流分析应用程序最大的优势就是,将每个事件纳入到分析结果所需的时间短得多。

除此之外,流分析应用程序还有另一个不太明显的优势。 传统大数据分析管道,拥有各自独立的组件,如分布式存储系统HDFS,资源调度系统Yarn,作业定时调度系统Azkaban/Oozie/Kettle,关系数据库,数据仓库(Hive/HBase),消息系统Kafka。 相比之下流处理器就会统一负责所有这些处理步骤,包括事件提取、带有状态维护的连续计算以及更新结果。

流处理基础

流图

数据流图(dataflow graph)

数据流程序描述了数据如何在算子之间流动。数据流程序通常表示为有向图,其中节点称为算子,用来表示计算,边表示数据之间的依赖性。一个数据流图必须至少有一个数据源和一个数据接收器。

逻辑流图

逻辑流图,表示计算逻辑的高级(详细)视图。

物理流图

如果运行一个流处理应用程序,Flink会将逻辑流图转换为物理数据流图,节点代表任务,并详细说明程序的执行方式。

数据与任务并行

数据并行:对输入数据进行分区,并在数据的子集上并行执行具有相同算子的任务并行。
任务并行:将不同的算子在相同或不同的数据上并行执行(map与filter算子在同一个节点的内存中不间断执行)。

数据交换策略

数据交换策略可由执行引擎自动选择,具体取决于算子的语义或我们明确指定的语义。

  • 前向策略:将数据从一个任务发送到接收任务。如果两个任务都位于同一台物理计算机上(这通常由任务调度器确保),这种交换策略可以避免网络通信。
  • 广播策略:将所有数据发送到算子的所有的并行任务上面去。这种策略会复制数据及网络通信,所以代价相当昂贵
  • 基于键控策略:通过Key值(键)对数据进行分区保证具有相同Key的数据将由同一任务处理。
  • 随机策略:均匀地将数据负载分配到算子的各个计算任务。

在这里插入图片描述

并行处理流数据

流数据:数据流指代的是一个可能无限的事件序列。

延迟:延迟表示处理事件所需的时间。从接收事件和看到在输出此事件结果之间的时间间隔

吞吐量:吞吐量是衡量系统处理能力的指标,也就是处理速率。每个单位时间内系统可以处理多少事件。 值得注意的是,事件处理速率取决于事件到达的速率,低吞吐量并不一定表示性能不佳。也就是说,主要关注点在于确定峰值吞吐量是多少,当系统处于最大负载时性能怎么样。

一旦达到使系统资源被完全使用的事件传入速率,将不得不开始缓冲事件。进一步增加 事件传入的速率 只会导致更糟糕的延迟。这种情况称为背压,有不同的策略来处理它。 通常情况使用并行处理多个流,在同时处理更多事件来降低延迟

流上操作

流处理引擎通常提供一组内置操作:摄取(ingest),转换(transform)和输出流(output)。

操作分2类,无状态,有状态。无状态操作不保持任何内部状态。

无状态操作:事件的处理不依赖于过去看到的任何事件,也没有保留历史。 无状态操作很容易并行化,因为事件可以彼此独立地处理,也独立于事件到达的顺序(和事件到达顺序没有关系)。 在失败的情况下,无状态操作可以是简单的重新启动并从中断处继续处理。

有状态操作: 根据需求可能会维护之前收到的事件的计算状态。此状态可以通过传入事件更新,也可以用于未来事件的处理逻辑。有状态的流处理应用程序更难以并行化和以容错的方式来运行,因为状态需要有效的进行分区和在发生故障的情况下可靠地恢复。

摄取与输出

数据摄取是操作从外部源获取原始数据并将其转换为其他格式(ETL)。实现数据提取逻辑的对象被称为数据源。数据源可以从TCP Socket,文件,Kafka Topic或传感器数据接口中提取数据。

数据输出是以适合消费的形式产出到外部系统。执行数据输出的对象称为数据接收器,包括文件,数据库,消息队列和监控接口。

转换算子

在使用后会消费一个事件,然后对事件数据做一些转换,产生一个新的输出流。接受多个输入流并产生多个输出流。还可以将一个流分成多个流,或者将多个流合并为一条流。

滚动聚合

滚动聚合是一种聚合,例如sum,minimum和maximum,为每个输入事件不断更新。聚合操作是有状态的,并将当前状态与传入事件一起计算以产生更新的聚合值。

窗口

转换和滚动聚合处理的单位是一个事件,产生输出事件并可能更新状态。但是,有些操作必须收集并缓冲多个事件以计算其结果。需要注意的是,为了在无界流上高效运行窗口函数,需要限制 这些操作缓存的事件量。窗口还可以在语义上实现关于流的比较复杂的查询。如将流历史缩减为单一聚合值(过去一小时多少辆车),虽然将丢失这段时间内数据的变化。

窗口操作不断从无限事件流中创建有限的事件集,以来执行有限集的计算。窗口的行为一组策略定义。窗口策略决定何时创建新的窗口以及要分配的事件属于哪个窗口,以及何时对窗口中的事件进行求值。 而窗口的求值基于触发条件。一旦触发条件得到满足,窗口积攒的事件将会被发送到求值函数,求值函数会将计算逻辑应用于窗口中的事件。求值策略可以根据时间或者事件属性计算(例如,在过去五秒内收到的事件或者最近的一百个事件等等)。

窗口可分为 滚动窗口 、 滑动窗口 、 会话窗口 三种

  • 滚动窗口: 是将事件分配到多个固定大小且相互不重叠的窗口,可以想象倒垃圾进入垃圾桶,第一个满了倒进第二个
    特性是,每个事件只会被一个且仅有一个特定窗口长度的窗口收集。
  • 滑动窗口:是将事件分配到多个固定大小范围可重叠的窗口,想象一下,最近一小时每隔5分钟,就是一个滑动窗口
    滑动距离定义了创建新窗口的间隔。特性是,每个事件可能会被多个(拥有特定长度)窗口收集。
  • 会话窗口:一些场景既不能使用滚动窗口也不能使用滑动窗口。 如一个用户在一段时间内使用APP产生的一系列事件
    以这个用户这一段时间内产生的事件作为一个分组,但不知道每一个用户的会话何时停止,也就是不同的
    会话可能窗口长度不一样。会话窗口如何停止,通过定义一个间隙时间,用户一段时间内不活动就停止

在流处理中,窗口操作与两个主要概念密切相关:时间语义状态管理。状态管理是定义在 流处理应用可能发生故障的语境下;而时间语义则定义在 真实世界网络和通信渠道远非完美,流数据经常被推迟或无序/乱序到达。

时间语义

流处理器在乱序事件流的情况下如何提供准确的计算结果,以及如何处理历史事件流,如何在流中进行时间旅行。

应用程序的准确的计算结果应该取决于事件实际发生的时间,而不是应用程序接收到事件的时间。

ProcessTime处理时间

处理时间:是指 流处理应用程序所在机器的本地时钟的时间。
如下图,基于处理时间的窗口包含一个窗口长度内被流处理应用程序接收到的所有事件。

spaf 0212

EventTime事件时间

事件时间:是事件实际发生的时间。通常情况下,在事件进入流处理程序前,事件数据就已经包含事件发生的时间戳

在这里插入图片描述

基于事件时间的流处理计算结果 无关流处理程序处理流数据的速度快或是慢,无关事件到达流处理程序的速度快或是慢,基于事件时间窗口的计算结果都是一样的。基于事件时间计算,即使碰到了事件乱序到达的情况,也可以保证结果的正确性。甚至可以重播一条流,然后分析流历史事件,就好像流中的事件是实时发生一样,以及 快进历史数据来使我们的应用程序追上现在的事件(故障重启),此时的流处理应用程序仍然是一个实时处理程序,而且业务逻辑不需要改变。

Watermarks水位线

何时触发事件时间窗口的计算?我们需要等待多久?我们如何知道事件是迟到的?
在分布式流处理程序无法准确预测用户行为的现实条件下,以及流处理程序外部环境所引发的事件延迟,以上问题并没有准确的答案。所以,引入 水位线 概念。

水位线是全局进度的度量标准。系统确信在一个时间点之后,不会有早于这个时间点发生的事件到来了。本质上,水位线提供了一个逻辑时钟,这个逻辑时钟告诉系统当前的事件时间。当一个运算符接收到含有时间T的水位线时,这个运算符会认为早于时间T的发生的事件已经全部到达

对于基于事件时间窗口和乱序事件的处理,水位线非常重要。运算符一旦接收到水位线,运算符会认为一段时间内发生的所有事件都已经积攒完成,可以触发针对这段时间内所有事件的计算。水位线提供了一种结果可信度和延时之间的妥协。激进的水位线设置可以保证低延迟,但结果的准确性不够。如果水位线设置的过于宽松,计算的结果准确性会很高,但可能会增加流处理程序不必要的延时。

水位线无论是由程序员自定义的或者是自动生成的,在一个流处理程序中追踪全局的时间进度都不是很容易,所以仅仅依靠水位线可能并不是一个很好的主意。流处理系统还需要提供一些机制来处理迟到的元素(在水位线之后到达的事件)。根据应用场景,我们可能需要把迟到事件丢弃掉,或者写到日志里,或者使用迟到事件来更新之前已经计算好的结果。

事件时间 VS 处理时间

事件时间已经可以解决所有问题,但处理时间将会带来理论上最低的延迟。 因为不需要考虑迟到事件以及乱序事件,所以一个窗口只需要简单的缓存窗口内的数据即可,一旦机器时间超过指定的处理时间窗口的结束时间,就会触发窗口的计算

处理时间窗口可以提供数据本身的忠实表达,以真实数据产生周期性事件报表,通过观察真实事件数据的变化来判断现实

状态与持久化模型

在流数据处理中,状态是普遍存在的。为了产生计算结果,一个函数在一段时间内的一定数量的事件上来累加状态

在现代流处理器兴起之前,处理无界数据集的一个通常做法是将输入的事件攒成微批,然后交由批处理器来处理。当一个任务结束时,计算结果将被持久化,而所有的运算符状态就丢失了。一旦一个任务在计算下一个微批次的数据时,这个任务是无法访问上一个任务的状态(都丢掉了)。计算结果通常使用将状态代理到外部系统(例如数据库)的方法来解决。

相反,在一个连续不间断运行的流处理任务中,事件的状态是一直存在的,我们可以将状态暴露出来作为编程模型中的一等公民。当然,我们的确可以使用外部系统来管理流的状态,即使这个解决方案会带来额外的延迟。由于流处理运算符默认处理的是无界数据流,内部状态有可能无限增长 ,所以有必要对中间计算状态进行限制,通常情况下,内部状态只是保存之前所接收到的事件流的总结或者概要(计数,累加和,抽样,窗口缓存,自定义数据结构等等)

状态管理:确保高效的对于状态的并发更新,不会产生竞争条件(race condition)

状态分区:大多数情况下,可以使用Key来对状态进行分区,然后独立的管理每一个分区,降低并行会带来复杂性。

状态恢复:有状态的运算符如何保证状态可以恢复,即使出现任务失败的情况,计算也是正确的?引出持久化模型

任务失败

(1)接收事件,并将事件存储在本地的缓存中;(2)可能会更新内部状态;(3)产生输出记录。这些步骤都能失败

结果保证

AT-MOST-ONCE一个事件只处理一次。当任务故障时,什么都不干,既不恢复丢失状态,也不重播丢失事件。没有保障听起来是一个糟糕的主意,但如果能接受近似的结果,且希望尽可能低的延迟,那么这样也挺好。

AT-LEAST-ONCE一个事件至少处理一次。当任务故障的时候,对丢失的状态所涉及到的所由事件进行重播,包括已计算过的,及还未计算的事件——所有的事件都得到了处理,一些事件还可能被处理多次。如果计算结果的正确性仅仅依赖于数据的完整性,而不依赖于事件出现的次数,那么重复处理是可以接受的。

EXACTLY-ONCE恰好处理一次。恰好处理一次语义不仅仅意味着没有事件丢失,还意味着针对每一个事件,对内部状态仅仅更新一次。 恰好一次 == 至少一次 ++ 事件重放机制 ++ 保证内部状态的一致性。Flink使用了一种轻量级快照机制来保证恰好处理一次语义。

端到端一致性:流处理应用除了流处理器以外还包含了数据源(例如Kafka)和持久化系统。端到端的一致性保证意味着结果的正确性贯穿了整个流处理应用的始终。每一个组件都保证了它自己的一致性。而整个端到端的一致性级别取决于所有组件中一致性最弱的组件。

Flink-HelloWord

Flink-Application

使用 IDEA Maven工程,通过Flink官方提供的 flink-quickstart-scala archeType原型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCjamAF5-1577001811451)(i/mavenFlink.png)]

项目文件结构

quickstart/
├── pom.xml
└── src
    └── main
        ├── resources
        │   └── log4j.properties           [日志配置文件]
        └── scala
            └── com
                └── inbreeze
                    ├── BatchJob.scala     [批处理应用程序模板]
                    └── StreamingJob.scala [流处理应用程序模板]
HelloWord_Job
package com.inbreeze

import org.apache.flink.streaming.api.scala._              //导入隐式类型转换[必写]
import org.apache.flink.streaming.api.windowing.time.Time  //窗口函数传入参数Time的封装类

/**
 * Skeleton for a Flink Streaming Job.  
 * Flink流作业骨架
 *
 * For a tutorial how to write a Flink streaming application, check the
 * tutorials and examples on the <a href="http://flink.apache.org/docs/stable/">Flink Website</a>.
 * 关于如何写 Flink流作业 更多指导请参考 上述官方网址
 *
 * To package your application into a JAR file for execution, run
 * 'mvn clean package' on the command line.
 * 打包你的应用程序为Jar包,执行 Maven package 命令
 *
 * If you change the name of the main class (with the public static void main(String[] args))
 * method, change the respective entry in the POM.xml file (simply search for 'mainClass').
 * 如果你更改了运行主类,请在 pom.xml 文件中进行同步更改。
 */
object StreamingJob {

  case class WordCount(word: String, count: Long)

  def main(args: Array[String]) {
    // set up the streaming execution environment. 设置流作业执行的环境变量
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    /*
     * Here, you can start creating your execution plan for Flink. 
     * 从这里可以开始编写 执行函数
     *
     * Start with getting some data from the environment, like
     *  env.readTextFile(textPath);
     * 第一步,从env环境变量中获取 输入源,通过 env.readTextFile() 函数
     *
     * then, transform the resulting DataStream[String] using operations
     * like
     *   .filter()
     *   .flatMap()
     *   .join()
     *   .group()
     * 第二步,使用 转换算子 对 输入源 执行运算,如 .flter() .group()
     *
     * and many more.
     * 更多,你想做的任何事情,比如 输出运算结果 至 持久化层。
     *
     * Have a look at the programming guide:
     * http://flink.apache.org/docs/latest/apis/streaming/index.html
     * 查看 更多有关 最新的算子信息,在如上官方网址
     */

    //并行度设置为1,禁止乱序打印
    env.setParallelism(1)
    //设置输入源
    val textSource: DataStream[String] = env.socketTextStream("localhost", 9999, '\n')

    
    /* 命令行nc输入数据:
    	hello word
    	hello word
    	hello word
    */
    //flink.window 无整数倍限制,而 SparkStreaming.window 最小为 500ms
    val wordCount = textSource.flatMap(_.split("\\s")) //以空格切割 nc端口传入的字符串
      .map(WordCount(_, 1)) //将 字符串数组 装载成 WordCount 样例类对象("?",1)
      .keyBy("word")        //以 WordCount.word 属性 进行 key 分组
      .timeWindow(Time.seconds(10)) //设置 流数据收集 滚动窗口 窗口长度为 10s 
      .sum("count")         //对 WordCount.count 属性 进行 求和

    /* Idea控制台输出数据:
      	WordCount(hello,3)
	    WordCount(word,3)
	*/
    wordCount.print()

    // execute program
    env.execute("Flink Streaming Scala API Skeleton")
  }
}

Linux部署资源

Hadoop-Flink-1.7.2-Scala-2.11

部署

StandAlone模式
1. Alt+P 通过 SecureCRT 8.5 上传至 /opt/software目录
$ cd /opt/software #拖拽文件至窗口

2. 解压部署
$ cd /opt/software
$ tar -zxvf flink-1.7.2-bin-scala_2.11.tgz -C /opt/module

3. 修改配置文件
$ cd /opt/module/flink-1.7.2/conf
$ vim flink-conf.yaml
>>
jobmanager.rpc.address: hadoop102
<<
$ vim slave
>>
hadoop102
hadoop103
hadoop104
<<

4. 分发Flink完整目录至 hadoop103,104
$ xsync /opt/module/flink-1.7.2

5. 启动Flink集群模式(standalone-session)
$ /opt/module/flink-1.7.2/bin/start-cluster.sh
Starting cluster.
Starting standalonesession daemon on host hadoop102
starting taskexecutor daemon on host hadoop102
starting taskexecutor daemon on host hadoop103
starting taskexecutor daemon on host hadoop104

6. 查看 WebUI http://localhost:8081

7. 运行Flink应用程序
$ /opt/module/flink-1.7.2/bin/flink run -c com.inbreeze.flink.app.BatchWcApp  /ext/flinkWordCount-1.0-SNAPSHOT.jar  --input /applog/flink/input.txt --output /applog/flink/output.csv

注1:输入数据,必须在taskexecutor/taskmanage存在
注2:计算结果根据会保存到taskexecutor/taskmanage的机器下,不会在standalonesession/jobmanage下

8. 停止Flink集群
$ ./bin/stop-cluster.sh
Yarn模式

要求:Flink是有Hadoop支持的版本,Hadoop环境需要保证版本在2.2以上

1. 启动hadoop集群
$ start-hadoop.sh

2. 启动Flink集群模式(yarn-session)
$ /opt/module/flink-1.7.2/bin/yarn-session.sh -n 2 -s 2 -jm 1024 -tm 1024 -nm test -d

注:命令选项解析
	-n(--container):TaskManager的数量。
	-s(--slots):每个TaskManager的slot数量,默认一个slot一个core,默认每个taskmanager的slot的个数为1。
	-jm:JobManager的内存(单位MB)。
	-tm:每个taskmanager的内存(单位MB)。
	-nm:yarn 的appName(现在yarn的ui上的名字)。 
	-d:后台执行。

3. 运行Flink应用程序
$ /opt/module/flink-1.7.2/bin/flink run -c com.inbreeze.flink.app.BatchWcApp  /ext/flinkWordCount-1.0-SNAPSHOT.jar  --input /applog/flink/input.txt --output /applog/flink/output.csv
Kubernates 部署 略

见官网

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值