「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个时间窗口,计算结果会发生重叠

image

流式处理的设计模式

单个事件处理

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


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


使用本地状态

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


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

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


多阶段处理和重分区

有些时候,咱们要经过全部可用的数据来得到结果。例如:要发布天天的“前10支”股票,这10支股票须要从天天的交易股票中挑选出来。若是仅仅在单个实例上处理是不够的,由于10支股票分布在多个实例上。


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

一、计算每支股票当天的涨跌。这个计算能够在每一个实例上执行

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

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


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


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

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

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


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

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

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

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

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


流与流的链接

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

image


乱序事件

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

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


从新处理

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

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

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

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

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


参考文献:

《Kafka全文指南》

相关文章
相关标签/搜索