白话说流——什么是流,从批认识流(二)

用户头像
KAMI
关注
发布于: 2020 年 06 月 13 日
白话说流——什么是流,从批认识流(二)

二. 流与批,从批到流

 

我们用点击数据作为例子。每条点击数据,由点击的用户、时间和链接构成。

  • 批处理的数据是已结束的、有界的数据,总共 6 条,不再随时间变化,我们可以视作为有限的“表”。

  • 流处理的数据是未结束的、无界的数据,当前 6 条,会随着时间增长,我们可以视作为无限的“流”。

 



1.无状态查询

 

首先我们来看一个简单查询,逻辑如下面的 SQL 所示,要做的是过滤掉 12:00:00 前的数据并给点击时间拼接上日期。

 

SELECT  user,  CONCAT(‘2019-07-11 ’, cTime) AS t_when

FROM info

WHERE cTime > ’12:00:00’





对于批的处理,输入数据是有限的。处理方法是获取完整的输入数据,过滤掉 12:00:00 前的点击数据,然后给余下的数据做时间字段的拼接。数据处理完成,输出结果仍然是有限的。

 

对于流的处理,情况有所不同。流式计算的前置条件是输入无限,永不停止,无法获取完整的输入数据后再进行处理。

 

然而,这个查询显然是一个无状态的查询,每行数据的处理和上下文没有关系。流可以逐行处理,来一个数据就处理一个数据。

 

每来一条数据,判断其是否是 12:00:00 前的数据,若是就过滤掉不进行处理,若不是则进行时间字段的拼接,然后输出。数据处理完成,输出结果是无限的,与输入数据是无限地为对偶关系。

 

同样的用标准 SQL 描述的逻辑,批和流用不同的模式,分别实现对有限“表”和无限“流”的数据处理。

 

2.窗口查询

 

前面的查询没有状态,比较直观和简单。下面我们再来看一个更复杂查询:带状态的查询。

 

逻辑同下面 SQL 所示,查询截止到该用户的当前点击,该用户一共点击了多少次,同时过滤掉黑名单用户("Liz")。这是个典型的窗口查询。

 

SELECT user, COUNT(1) OVER (PARTITION BY user ) AS cnt

FROM info

WHERE user != ’Liz’

 



对于批的处理,同样是先获取所有的输入数据,过滤掉黑名单用户,然后在余下的数据里,利用缓存,按用户进行分组计数,逐行加 1。数据处理完成,输出结果仍然是有限的。

 

对于流的处理,同样输入数据是永不停止的,无法像批一样能难道所有的输入数据再进行处理。尽管是有上下文关联的有状态查询,但是对于每一行数据的处理,只和“过去”的数据有关,统计范围是从最开始到当前行,本质上还是有限数据集的计算。

 

具体处理方法也很直观:逐行处理输入数据,先判断是否黑名单,若是就丢掉,若不是,利用缓存按用户分组计数,每来一条就加 1,输出计数值。数据处理完成,输出结果仍然是无限的。

 

3.统计查询

 

最后,我们来看聚合统计,流式计算的核心本质,体现在聚合统计中。

 

逻辑如下面 SQL ,统计每一个用户的点击次数,过滤掉黑名单。

 

SELECT user, COUNT(1) AS cnt

FROM info WHERE user != ’Liz’

GROUP BY user

 



对于批的处理,过程很简单,这里不再累赘,过程就是过滤黑名单然后分组统计。

 

对于流的处理,显然这里有个矛盾!要得到最终的统计结果,显然需要先得到所有的输入数据,但是输入数据是无限的永不停止!

 

我们可以想一想一个有趣的关系。无限的输入对应无限的输出,对于无限输入的聚合统计,输出的自然也应该是无限的聚合结果。

 

联想前面的窗口统计,我们在流里可以这么处理:计算截止到当前输入行的统计值并输出,其本质是窗口统计;当新的输入数据到达,改变了聚合统计值,那就对输出结果进行“修正”:先指出之前的输出旧值,即“回撤”,然后更新输出值,即“更新”。

 

这就是流式计算的聚合统计的回撤流,其通过“回撤”,实现聚合统计。

 

Flink,就是利用回撤流实现聚合统计。

 

4.回撤和流表对偶

 

有人会疑惑,回撤是必须的吗? 可以输出更新后的统计值吗?

如果没有回撤,聚合统计就等价于窗口统计。在一些情形下,例如下游是幂等的写入操作,输入数据没有维度变更,那么结果是正确的。

但是,在大部分情况,没有回撤会导致计算错误。如下所示,下游有另外一个聚合操作,没有回撤,统计结果显然是错误的。

 

SELECT

SUM(cnt) AS total_cnt

FROM

        SELECT

           user, COUNT(1) AS cnt

        FROM info WHERE user != ’Liz’

        GROUP BY user

)t

 



流/表对偶

 



同样以前面的聚合统计为例,观察流的输出,我们可以发现:

  • 回撤流的规约结果,我们得到的是一个动态变化的表

  • 动态表的变化记录,实际上就是回撤流

 

这就是所谓的“流/表对偶”。Flink 提出“流批合一”,没有为流实现专门的 SQL,而是基于 ANSI SQL 实现对流的查询,就是以此为其理论依据。

 

批,对应的是有限的输入数据集,有限表,静态表。

流,对应的是无限的输入数据集,无限流,动态表。

 

小结

 

总结一下,流的关键是:输入永不结束,查询永不结束,输出也永不结束。

 

流式计算,如果是不受未来值影响的操作,例如无状态查询,窗口查询,并不产生回撤数据。如果是关注未来值的操作,例如聚合统计,联合操作,必须要有回撤数据以保证计算正确。

 

定义在批上的操作语义,可同样应用在流上,只是两者的输入输出不一样,但两者是对偶关系,一定程度上等价。

 

另外,我们在应用时,也要明白流的代价。例如,聚合统计,每个输入数据,都会生成生回撤和更新两个输出结果。联合操作,需要的缓存空间更是会不断增长。我们要理解流的方式,才能对这些开销和代价有认知。

同时,在业务场景允许的情况,可以改为代价更小的做法,例如用窗口统计代替聚合统计,例如引入内部的微批次机制减少回撤数据的生成的。实际上,Flink 的 Blink Planer ,就是这么做的。



发布于: 2020 年 06 月 13 日 阅读数: 43
用户头像

KAMI

关注

这个世界复杂又有趣,和你分享我热爱的一切 2020.05.03 加入

数据挖掘研究员,专注分享数据领域的技术和业务,以及逻辑、思维和方法论 | 网易游戏内推长期有效,欢迎私戳 (微信 KAMI-Wei)

评论

发布
暂无评论
白话说流——什么是流,从批认识流(二)