flink 数据倾斜 例子 flink mapstate例子_flink 数据倾斜 例子

1.状态的简单概述

之前学习的流处理中的基于Socket的入门案例中其实就已经由Flink帮我们实现了对历史数据/历史状态/状态的记录

如,

启动程序后,在nc -lk 9999中发送hello world

会得出:(hello,1)和(world,1)

再次发送hello world

会得出:(hello,2)和(world,2)

说明Flink帮我们记录了历史数据/历史状态/状态

也就是说Flink内置的很多操作/API已经实现好了状态/State

我们直接使用即可

2.有状态计算和无状态计算

无状态计算

不需要考虑历史数据

相同的输入得到相同的输出就是无状态计算, 如map/flatMap/filter…

flink 数据倾斜 例子 flink mapstate例子_数据_02

首先举一个无状态计算的例子:消费延迟计算。

假设现在有一个消息队列,消息队列中有一个生产者持续往消费队列写入消息,多个消费者分别从消息队列中读取消息。

从图上可以看出,生产者已经写入 16 条消息,Offset 停留在 15 ;有 3 个消费者,有的消费快,而有的消费慢。消费快的已经消费了 13 条数据,消费者慢的才消费了 7、8 条数据。

如何实时统计每个消费者落后多少条数据,如图给出了输入输出的示例。可以了解到输入的时间点有一个时间戳,生产者将消息写到了某个时间点的位置,每个消费者同一时间点分别读到了什么位置。刚才也提到了生产者写入了 15 条,消费者分别读取了 10、7、12 条。那么问题来了,怎么将生产者、消费者的进度转换为右侧示意图信息呢?

consumer 0 落后了 5 条,consumer 1 落后了 8 条,consumer 2 落后了 3 条,根据 Flink 的原理,此处需进行 Map 操作。Map 首先把消息读取进来,然后分别相减,即可知道每个 consumer 分别落后了几条。Map 一直往下发,则会得出最终结果。

大家会发现,在这种模式的计算中,无论这条输入进来多少次,输出的结果都是一样的,因为单条输入中已经包含了所需的所有信息。消费落后等于生产者减去消费者。生产者的消费在单条数据中可以得到,消费者的数据也可以在单条数据中得到,所以相同输入可以得到相同输出,这就是一个无状态的计算。

有状态计算

需要考虑历史数据

==相同的输入得到不同的输出/不一定得到相同的输出,==就是有状态计算,如:sum/reduce

flink 数据倾斜 例子 flink mapstate例子_flink 数据倾斜 例子_03

以访问日志统计量的例子进行说明,比如当前拿到一个 Nginx 访问日志,一条日志表示一个请求,记录该请求从哪里来,访问的哪个地址,需要实时统计每个地址总共被访问了多少次,也即每个 API 被调用了多少次。可以看到下面简化的输入和输出,输入第一条是在某个时间点请求 GET 了 /api/a;第二条日志记录了某个时间点 Post /api/b ;第三条是在某个时间点 GET了一个 /api/a,总共有 3 个 Nginx 日志。

从这 3 条 Nginx 日志可以看出,第一条进来输出 /api/a 被访问了一次,第二条进来输出 /api/b 被访问了一次,紧接着又进来一条访问 api/a,所以 api/a 被访问了 2 次。不同的是,两条 /api/a 的 Nginx 日志进来的数据是一样的,但输出的时候结果可能不同,第一次输出 count=1 ,第二次输出 count=2,说明相同输入可能得到不同输出。输出的结果取决于当前请求的 API 地址之前累计被访问过多少次。第一条过来累计是 0 次,count = 1,第二条过来 API 的访问已经有一次了,所以 /api/a 访问累计次数 count=2。单条数据其实仅包含当前这次访问的信息,而不包含所有的信息。要得到这个结果,还需要依赖 API 累计访问的量,即状态。

这个计算模式是将数据输入算子中,用来进行各种复杂的计算并输出数据。这个过程中算子会去访问之前存储在里面的状态。另外一方面,它还会把现在的数据对状态的影响实时更新,如果输入 200 条数据,最后输出就是 200 条结果。

有状态和无状态计算的使用场景

无状态计算的场景:各种简单的转换/过滤等操作,如简单的map/flatMap/filter…

有状态计算的场景:如各种聚合统计,sum/reduce/max/min…

flink 数据倾斜 例子 flink mapstate例子_flink_04


什么场景会用到状态呢?下面列举了常见的 4 种:

1.去重:比如上游的系统数据可能会有重复,落到下游系统时希望把重复的数据都去掉。去重需要先了解哪些数据来过,哪些数据还没有来,也就是把所有的主键都记录下来,当一条数据到来后,能够看到在主键当中是否存在。

2.窗口计算:比如统计每分钟 Nginx 日志 API 被访问了多少次。窗口是一分钟计算一次,在窗口触发前,如 08:00 ~ 08:01 这个窗口,前59秒的数据来了需要先放入内存,即需要把这个窗口之内的数据先保留下来,等到 8:01 时一分钟后,再将整个窗口内触发的数据输出。未触发的窗口数据也是一种状态。

3.机器学习/深度学习:如训练的模型以及当前模型的参数也是一种状态,机器学习可能每次都用有一个数据集,需要在数据集上进行学习,对模型进行一个反馈。

4.访问历史数据:比如与昨天的数据进行对比,需要访问一些历史数据。如果每次从外部去读,对资源的消耗可能比较大,所以也希望把这些历史数据也放入状态中做对比。

4.状态的分类

方式1:Managed State & Raw State

ManagerState:托管状态,Flink自动管理和优化.支持多种数据结构,适合大多数应用场景

RawState:原始状态,需要用户自己管理,支持字节数组,自定义Operator才可以用

注意:开发中都是使用ManagedState

flink 数据倾斜 例子 flink mapstate例子_flink 数据倾斜 例子_05

方式2:Keyed State & Operator State

eyed State :用在进行了KeyBy操作的流上,每个Key对应一个State,支持多种数据结构

Operator State : 可以用在没有keyBy的流上,常用于Source或Sink,如FlinkKafkaConsumer中就使用了,支持的数据结构有ListSate和BroadcastState

注意:这里学习的Keyed State & Operator State都是属于ManagedState

flink 数据倾斜 例子 flink mapstate例子_历史数据_06