「Flink」理解流式处理重要概念

什么是流式处理呢?

这个问题其实我们大部分时候是没有考虑过的,大多数,我们是把流式处理和实时计算放在一起来说的。我们先来了解下,什么是数据流。

数据流(事件流)

  • 数据流是无边界数据集的抽象
    • 我们之前接触的数据处理,大多都都是有界的。例如:处理某天的数据、某个季度的数据等
    • 无界意味着数据是无限地、持续增长的
    • 数据流会随着时间的推移,源源不断地加入进来
  • 数据流无处不再
    • 信息卡交易
    • 电商购物
    • 快递
    • 网络交换机的流向数据
    • 设备传感器发出的数据
    • 这些数据都是无穷无尽的
    • 每一件事情,都可以看成事件序列
  • 数据流是有序的
    • 数据的到来总是有个先后顺序
  • 数据流是不可变的
    • 事件一旦发生,就不能被改变
    • 它陈述了某一个时刻的事实
  • 数据流是可以重播的
    • 为了处理的一些问题、纠正过去的错误,可以重跑数据流
    • 借助于Kafka,我们可以重新消费几个月之前的原始数据流

流式处理

流式处理就是指实时地处理一个或多个事件流。它是一种编程范式。其他编程领域,主要有3种编程范式:

  1. 请求与响应
    • 延迟最小的一种方式,响应时间要求亚毫秒级到毫秒之间
    • 响应时间一般分稳定
    • 发出请求,等待响应(大部分的JavaEE同学,都是开发这一类编程范式的应用),其实就是OLTP
  2. 批处理
    • 特点:高延迟、高吞吐
    • 一般是固定某个时刻开始启动执行,读取所有的数据,然后输出接口
    • 每次读取到的都是旧数据
    • 主要应用在DWH或BI中
  3. 流式处理
    • 特点:介于上述两者之间
    • 流式处理可以让业务报告保持更新,持续响应

流的定义不依赖某个框架,只要储蓄从一个无边界数据集中读取数据,并对它们进行处理生成结果,就是进行流式处理。重点是:整个过程必须是持续的。

流式处理中的时间

上述我们已经说过了,数据流都是有序的。某一时刻的数据是确定的。时间是流式处理中非常重要的概念。大部分流式应用的操作都是基于时间窗口的。

流式系统一般包含以下几个时间概念(熟悉Flink的同学应该会很熟悉):

  • 事件时间(Eventtime)
    • 事件实际发生的时间
    • 用户一般只对事件发生时间感兴趣
  • 日志追加时间
    • 日志追加时间是指事件保存到事件存储源的时间
    • 例如:数据是什么到达Kafka的(Kafka是可以启用自动添加时间戳功能的)
  • 处理时间
    • 流式处理应用接收到事件后,要对齐进行处理的时间
    • 处理时间取决于流式处理应用何时读取到这个时间
    • 如果应用程序使用了两个线程来读取同一个事件,这个时间戳可能会不一样
    • 这个时间戳非常不可靠,应该避免使用它

状态

如果流式处理是来一个事件就处理一个事件,那么流式处理就很简单。但如果操作中包含了多个事件,流式处理就有意思了。例如:我们想在流式处理中统计北京用户的订单数量、消费金额等等。此时,就不能光处理单个事件了,我们需要获取更多的事件。事件与事件之间的信息就称之为状态。例如简单的,求某个类型的订单数等。

这些状态一般就保存在流式处理程序本地变量(本地内存)中,例如:使用HashMap来保存计数。但这种做法是很不可靠的,流式处理处理的是无界数据集,一旦应用程序出现异常,就会出现状态丢失,这是我们说不能接受的。所以,每一种流式计算框架都会很小心地持久化状态。如果应用程序重启,需要将这些数据恢复。

流式处理一般包含两种状态:

  • 本地状态
    • 这种状态只能被应用程序实例访问(不过Flink 1.9版本是可以外部来访问本地状态的)
    • 内嵌到应用程序的数据库中进行维护和管理
    • 特点:速度快,但受内存大小的限制,所以,很多流式处理系统都将数据拆分到多个子流中处理
  • 外部状态
    • 用外部存储来处理,一般使用NoSQL系统,例如:Cassadra
    • 特点:没有大小限制,可以被应用程序多个实例访问、甚至外部应用访问,但引入额外的系统会造成延迟、复杂性(例如:要维护内部和外部状态一致性问题)

时间窗口

大部分针对流的操作都是基于时间窗口的。例如:计算一周内销量最好的产品。两个流的合并也是基于时间窗口的。流式系统会合并发生在相同时间段上的事件。窗口是有类型的。以下几点是我们设计窗口需要考虑的:

  • 窗口的大小
    • 是基于5分钟计算还是基于15分钟、甚至是一天
    • 窗口越小,就能越快地发现变更,不过噪声也就越多
    • 窗口越大,变更就跟平滑,不过延迟也越严重
  • 窗口的移动频率(移动间隔)
    • 5分钟的窗口,可以1分钟计算一次,或者每秒钟计算一次,或者每当有新事件到达时计算一次
    • 如果“移动频率”与窗口大小相等,这种称为滚动窗口(tumbling window)
    • 如果窗口随着每一条记录移动,这种情况称为滑动窗口(sliding window)
  • 窗口的可更新时长
    • 假设:计算了 00:00 – 00:05 之间的订单总数,一个小时后,又得到了一些“事件时间”是 00:02的事件(例如:因为网络通信故障,这个消息晚到了一段时间),这种情况,是否需要更新 00:00 – 00:05 这个窗口的结果呢?或者就不处理了?
    • 理想情况下,可以定义一个时间段,只要在这个时间段内,事件可以被添加到对应的时间片段里。例如:如果事件处于4个小时以内,就更新,否则,就忽略掉。
  • 窗口时间对齐
    • 窗口可以与时间对齐,例如:5分钟的窗口如果每分钟移动一次,那么第一个分片可以是:00:00 – 00:05,第二个就是 00:01 – 00:06
    • 窗口也可以不与时间对齐,例如:应用可以在任何时间启动,那么第一个分片有可能是03:17 – 03:22
    • 滑动窗口永远不会与时间对齐,只要有新的记录到达,就会发生移动

下面这张图,说明了滚动窗口与滑动窗口的区别。

滚动窗口:假设窗口的大小为5分钟,这里确定的3个时间窗口

滑动窗口:假设每分钟滑动一次,那么这个时候会有5个时间窗口,计算结果会发生重叠

流式处理的设计模式

单个事件处理

这是流式处理最基本的模式。这种模式也叫:map或filter模式。经常被用来过滤无用的事件或者用于转换事件。

这种模式,应用程序读取流中的数据,修改数据,然后把事件生成到另一个流上。这一类应用程序无需在程序内部维护状态,每一个事件都是独立处理的。这种错误恢复和进行负载均衡都很容易。因为无需进行状态恢复操作。

使用本地状态

大部分流式处理应用关系如何聚合数据。特别是:基于时间窗口进行聚合。例如:找到每天最低、最高的交易价格。要实现这种操作,就需要维护流的状态。例如:我们需要将最小值、最大值保存下来,用它们与每一个新值对比。这类操作,可以通过本地状态来实现。例如:每一个分组都维护自己分组的状态。

一旦流式处理中包含了本地状态,就需要解决以下问题。

  • 内存使用
    • 必须要有足够的内存来保存本地状态
  • 持久化
    • 确保应用程序关闭时,不会丢失状态
    • 例如:我们可以使用RocksDB将本地状态保存到内存里、同时持久化到磁盘上,以便重启后恢复。而且需要将本地状态的变更发送到Kafka的主题上
  • 重新负载均衡
    • 有时候,分区被重新分配给不同的消费者。这种情况,失去分区的实例必须把最后的状态保存下来,或得分区的实例必须要知道如何恢复到正确的状态

多阶段处理和重分区

有些时候,我们要通过所有可用的数据来获得结果。例如:要发布每天的“前10支”股票,这10支股票需要从每天的交易股票中挑选出来。如果仅仅在单个实例上处理是不够的,因为10支股票分布在多个实例上。

此种,我们分为多个阶段来处理。

1、计算每支股票当天的涨跌。这个计算可以在每个实例上执行

2、将结果写入到单个分区

3、再用一个实例找出当天的前10支股票

这一类操作就与MapReduce很像了。

使用外部查找——流和表的连接

有时候,流式处理需要将外部数据和流集成在一日。例如:外部数据中保存了一些规则、或者将完整完整地用户信息拉取到流中。

这种case最大的问题,外部查找会带来严重的延迟,一般在 5-15 ms之间,这在很多情况下是不可行的。而且,外部系统也无法承受这种额外的负载——流式处理系统每秒可以处理10-50W个事件,而数据库正常情况下每秒只能处理1W个事件,所以需要伸缩性更强的解决方案。

为了获取更好的性能和更强的伸缩性,需要将外部数据库的信息缓存到流式处理应用中。但考虑以下问题:

如何保证缓存里的数据是最新的?

如果刷新太频繁,仍然会对数据库造成很大压力,缓存也就无用了。

如果刷新不及时,那么流式处理中所用的数据就会过时。

如果能够捕捉数据库的变更事件,并形成事件流,流式处理作业就可以监听事件流,并及时更新缓存。捕捉数据库的变更事件并形成数据流,这个过程称为CDC(Change Data Capture)。例如:我们可以通过Canal来捕获MySQL数据库的变化、可以通过ogg来捕获Oracle数据库的变化

流与流的连接

有时候需要连接两个真实的事件流。要连接两个流,就是连接所有的历史事件(将两个妞中具有相同键、发生在相同时间窗口内的事件匹配起来),这种流和流的连接称为:基于时间窗口的连接(windowed-join)。连接两个流,通常包含一个滑动时间窗口

乱序事件

不管对于流式处理、还是传统的ETL系统,处理乱序事件都是一个挑战。物联网领域经常发生乱序事件:一个移动设备断开Wifi连接几个小时,在重新连上WiFi后,将几个小时堆积的事件一并发出去。要让流式处理应用处理好这些场景,需要做到几下:

  • 识别乱序事件
    • 应用程序需要检查事件的时间,并将其与当前时间进行比较
  • 规定一个时间段用于重排乱序事件
    • 例如:3个小时以内的事件可以重排,但3个小时以外的事件就可以直接扔掉
  • 具有一定时间段内重排事件的能力
    • 这是流式处理应用和批处理的重要不同点
    • 假设有一个每天运行的作业,一些事件在作业结束之后才到达,那么可以重新运行昨天的作业来更新
    • 而在流式处理中,重新运行昨天的作业是不存在的,乱序事件和新到达的事件必须一起处理
  • 具备更新结果的能力
    • 如果处理的结果保存在数据库你,那么可以通过put或update对结果进行更新

重新处理

该重要模式是重新处理事件:

  • 流式处理应用更新了,要使用新版本应用处理同一个事件流,生成新的结果,并比较两种版本的结果,然后某个时间点将客户端切换到新的结果流
  • 现有的流式处理出现了缺陷,修复后,需要重新处理并重新计算结果

第一种情况,需要Kafka将事件流长时间地保存在可伸缩的数据存储中

  • 将新版本的应用作为一个新的消费者组
  • 新的版本从输入主题的第一个偏移量开始读取数据
  • 检查结果流,在新版本的处理作业赶上进度时,将客户端应用程序切换到新的结果流上

第二种情况,需要应用程序回到输入流的起始位置开始处理,同时重置本地状态,还要清理之前的输出流。这种方式处理起来比较困难。建议还是使用第一种方案。

参考文献:

《Kafka全文指南》

原文地址:https://www.cnblogs.com/ilovezihan/p/12254479.html

时间: 2024-08-27 20:04:12

「Flink」理解流式处理重要概念的相关文章

Flink系列之流式

本文仅是自己看书.学习过程中的个人总结,刚接触流式,视野面比较窄,不喜勿喷,欢迎评论交流. 1.为什么是流式? 为什么是流式而不是流式系统这样的词语?流式系统在我的印象中是相对批处理系统而言的,用来处理流数据,实现数据处理功能的一个系统,而流式一词提醒我要以数据产生的方式去看待数据和以及处理过程,即在现实生活中,数据是以流的形式不断产生的,处理的过程应贴近数据产生的方式. 2.流与批 在处理数据时,对数据而言有:无界和有界之分.无界可以理解为不知道数据产生的停止时间,在数学上可以用前闭后开( [

Demo:基于 Flink SQL 构建流式应用

Flink 1.10.0 于近期刚发布,释放了许多令人激动的新特性.尤其是 Flink SQL 模块,发展速度非常快,因此本文特意从实践的角度出发,带领大家一起探索使用 Flink SQL 如何快速构建流式应用. 本文将基于 Kafka, MySQL, Elasticsearch, Kibana,使用 Flink SQL 构建一个电商用户行为的实时分析应用.本文所有的实战演练都将在 Flink SQL CLI 上执行,全程只涉及 SQL 纯文本,无需一行 Java/Scala 代码,无需安装 I

「Flink」Flink中的时间类型

Flink中的时间类型和窗口是非常重要概念,是学习Flink必须要掌握的两个知识点. Flink中的时间类型 时间类型介绍 Flink流式处理中支持不同类型的时间.分为以下几种: 处理时间 Flink程序执行对应操作的系统时间.所有基于时间的操作(例如:时间窗口)都将使用运行相应operator的系统时间.例如:每个小时的处理时间窗口包括在系统时间范围内所有operator接收到的记录.例如:如果应用程序在09:15开始运行,则第一个滚动时间窗口将包括:09:15 – 10:00 之间的处理事件

「Flink」事件时间与水印

我们先来以滚动时间窗口为例,来看一下窗口的几个时间参数与Flink流处理系统时间特性的关系. 获取窗口开始时间Flink源代码 获取窗口的开始时间为以下代码: org.apache.flink.streaming.api.windowing.windows.TimeWindow /** * Method to get the window start for a timestamp. * * @param timestamp epoch millisecond to get the window

「Flink」使用Managed Keyed State实现计数窗口功能

先上代码: public class WordCountKeyedState { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 初始化测试单词数据流 DataStreamSource<String> lineDS = env.addSource(n

如何优雅地「蜗居」?

如果我们把「蜗居」理解为小户型.小空间居住,包括合租.大开间等,如何才能让「蜗居」丝毫不尴尬,所谓「优雅」,就是排除客观限制,最大限度的提升居住品质. 王珦,室内设计师,文字编辑 蜗居要看“蜗”到了到什么程度,其实在帝都,有很多怀揣梦想的年轻人所住的“胶囊公寓”,就是在最极端的“蜗居”环境下被逼迫出的最朴素的空间解决方案.虽然看起来有点伤感,但其实这其中基本已经把紧凑型空间的利用方式与技巧揭示的七七八八了.当空间较为珍贵时,或利用效率非常重要时,能够展示活动性质与家具设备布置之间密切匹配关系的布

所有 Python 程序员必须要学会的「日志」记录。

本文字数:3840 字 阅读本文大概需要:10 分钟 写在之前 在我们的现实生活中,「日志记录」其实是一件非常重要的事情,比如银行的转账记录,汽车的行车记录仪记录行驶过程中的一切,如果出现了什么问题,我们可以通过「日志记录」来搞清楚到底发生了什么事情. 除了在生活中,在日常的系统开发以及调试等过程中,记录日志同样是一件很重要的事情.很多编程初学者并没有「记录日志」的习惯,认为记录日志是一件可有可无的事情,出现问题的时候只要使用 print 函数打印一下程序的中间结果即可,真是 too young

流式计算(五)-Flink核心概念

一手资料,完全来自官网,直接参考英文过来的,并加了一些自己的理解,希望能让看官君了解点什么,足矣. 环境:Flink1.9.1 难度:新手--战士--老兵--大师 目标: 理解Flink的计算模型 认识各重要组件 说明: 本篇作为前两篇的补充内容,算是理论篇 步骤: 01-Flink编程模型 Flink的流计算整体来看都是按照Source -> Transformation -> Sink三步走,即获取流源 -> 进行转换 -> 汇聚(Sink),但“转换 (Transformat

「译」JavaScript 的怪癖 1:隐式类型转换

原文:JavaScript quirk 1: implicit conversion of values 译文:「译」JavaScript 的怪癖 1:隐式类型转换 译者:justjavac 零:提要 [此贴子是 javascript 的 12 个怪癖(quirks) 系列的第一篇.] JavaScript 是非常宽容的,「来者不拒」,不在乎什么类型. 例如,它如果想要接受数字,它并不拒绝其他类型的值,而是试图把它们转换成数字: > '5' - '2' 3 > '5' * '2' 10 自动转