flink知识与理解

文章目录

  • 以词频统计为例,flink program的基本步骤
  • 常见的示例数据
  • 时间,窗口与水位线
  • 分层API
  • 统计窗口内topN
  • flink提供的库
    • 端到端精确一次
  • 24/7
  • savepoint
  • actor
  • 重点作业指标
    • 延迟
    • 吞吐
    • checkpoint大小
    • dag结点是否存在反压backpressure
      • 什么是backpressure
      • 如何排查
  • 编程时遇到的问题

source, 源、上游
sink,汇,下游
flink流批一体,api都是同一套,在代码的runtime mode里指定stream或batch,或者提交命令行参数指定。
flink事件驱动,数据来了才动(比如窗口的创建)。
flink收/发kafka时,默认情况是不对key做序列化/反序列化,要key的话需要加几行代码。

以词频统计为例,flink program的基本步骤

  • 创建环境
    stream有stream的环境,dataset有dataset的环境,还可以创建local测试环境看webui。一般直接get,会自己判断需要创建的环境)
  • 准备datasource,1.13后流批一体,env.fromsource()
    读文件,socket,或者kafka连接器。生产环境一般是无界流。
    flink消费kafka时,偏移量策略有earliest(一定从最早消费,与kafka同名策略的含义不同)等。
    也可以用flink的datagen来作为测试数据源。
  • 写flatmap。
    输入是String,输出是(String, Integer)的二元组,实际上是(string, 1)。
  • 用group by方法将二元组按[0]分组。
  • 将二元组按[1]聚合。
  • 输出。
    新api是sinkto,大多数用连接器。可以输出到hdfs(过程类似log4j输出文件,sink的并行度决定inprogress文件的个数),JDBC(mysql),kafka。
    写kafka需要配置序列化器,kafka地址,topic名称,kafka一致性级别,如果是精准一次,要开checkpoint,配kafka事务前缀、超时时间(小于15min)。
    写JDBC需要配置攒batch,重试次数,。
  • execute启动。
    普通execute会阻塞,有executeAsync,不过一般一个类一个execute。

下面是代码,适合新建文件时复制。

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

public class WindowWordCount {

    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStream<Tuple2<String, Integer>> dataStream = env
                .socketTextStream("localhost", 9999)
                .flatMap(new Splitter())
                .keyBy(value -> value.f0)
                .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
                .sum(1);

        dataStream.print();

        env.execute("Window WordCount");
    }

    public static class Splitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
        @Override
        public void flatMap(String sentence, Collector<Tuple2<String, Integer>> out) throws Exception {
            for (String word: sentence.split(" ")) {
                out.collect(new Tuple2<String, Integer>(word, 1));
            }
        }
    }

}

上面用的flatmap,下面这个是map,匿名类写法,输入是String类型,输出是Integer类型。

DataStream<String> input = ...;

DataStream<Integer> parsed = input.map(new MapFunction<String, Integer>() {
    @Override
    public Integer map(String value) {
        return Integer.parseInt(value);
    }
});

常见的示例数据

上面的例子中,示例数据是一个个字符串,其实不太典型。
典型的示例数据有id,ts,vc三部分。ts是timestamp的缩写。

时间,窗口与水位线

事件时间本身是有逻辑语义,而处理时间会随网络波动。flink1.12后,默认事件时间。

窗口,比如统计9点到10点的uv unique visitor,就需要窗口。窗口的结束,标志着处理的开始。如果说数据是水流,窗口就像水桶。见下面水位线的图。
窗口分为时间窗口(定点发车)和计数窗口(人齐发车)。
窗口也可以分为:
滚动窗口,窗口之间首尾衔接,每个数据只属于一个窗口;
滑动窗口,滑动步长可小于窗口大小,所以一个数据可能属于多个窗口;每隔步长输出当前最新窗;
会话窗口,会话有超时时间(静态/动态),如果没有超时就属于同一个会话,超过了就新会话;
全局窗口,需要自定义触发器来结算。也可以自定义移除器,在触发前/后移除一些数据。
窗口相关的逻辑一般分两步,开窗时window函数参数指定类型,写窗口aggregate处理函数。
口答时可以这样说:先key by,再开窗,然后reduce/aggregate。
开窗时无key by(api名字带all后缀)则只有一条逻辑流,同一个task,并行度为1。key by相反。
flink知识与理解_第1张图片
reduce是增量聚合,reduce函数每来一条数据就会执行一次。输入、累加器、输出的类型相同。
aggregate也是增量聚合,可以让输入、累加器、输出的类型不同。它更通用,代码量更大。
全窗口聚合一般用process,需要processWindowFunction,可以从入参context获得window等。
增量聚合和全窗口聚合可以嵌套使用,全窗口中只存当前的一条增量聚合结果,也有上下文。

聚合可以在窗上做,也可以直接key by不开窗就聚合。

窗口的getStart会取最近一个上界点,比如10s的滚动窗口,取getstart,总会是整十秒。

窗口会在属于此窗口的第一条数据到来时单例创建,窗口触发后延迟x秒销毁(x默认为0)。

提取每个区间中(区间大小可以为1)数据中事件时间的最大值,保持单调增(乱序数据),这就是水位线watermark。水位线能标志当前事件处理进度。
watermark = 当前最大事件时间 - 延迟时间 - 1ms
1ms是考虑区间开闭的问题,把压线数据也算进来。
watermark也是数据,也会插入流,且在原数据之后。因此process函数获取当前watermark时会有一条数据的延迟。

为了避免窗口错过乱序时的迟到数据,可以将水位线在所取事件时间的基础上统一减一个常量。有序流不需要这个延迟,顺序越乱,延迟应当更高。
这样做并不会错误处理早到数据,如下图:
flink知识与理解_第2张图片
水位线体现了低延时和准确性的权衡。
极致的低时延,可以直接用处理时间。
极致的准确性,要求水位线的延迟非常大。

写代码的时候,需要告知数据中哪部分代表事件时间,其它细节暂略。

分层API

flink知识与理解_第3张图片
官网中有这三层的demo。代码量,自然是上层少,下层多。

统计窗口内topN

先按统计量key by,
开滑动窗口,
增量聚合统计出现次数,
全窗口给数据打所属窗口的标签,
(aggregate函数可以有两个参数,一个aggregate function,一个processWindowFunction)
(聚合后变为普通DataStream,流中数据为(windowEnd, 统计量,出现次数))
按窗口标签keyby,
最后keyedProcessFunction,每来一条数据就加入list里并设一次定时器windowEnd+1,onTimer的动作就是先sort再取前n个。这里有说法是flink sql使用红黑树来做top n。

flink提供的库

CEP complex event processing:用于流处理中的模式识别
DataSet API:用于批处理
Gelly:可扩展的图处理和图分析,已经实现了label propagation,triangle enumeration和page rank。
也提供方便实现自定义图算法的API。

端到端精确一次

一致性,考虑重复和缺失,分为至少一次、至多一次、精确一次三种。
端到端包括flink的上游和下游。一致性等级有木桶效应,所以需要source和sink都能保证精准一次。

source如果是kafka,可以重置offset,这是精确一次的前提(上游可重放)。
检查点只可以保证flink内部精准一次。

flink知识与理解_第4张图片
两阶段提交,利用了flink的检查点机制和外部提供的事务:
第一阶段预提交,从第一个barrier到达开始新事务,各自提交,此时外部系统查不到此数据,
第二阶段,检查点完成之后,如果每一个都提交成功,则正式提交。否则回滚。
正式提交如果失败,从检查点恢复,重新启动外部事务,所以检查点也有必要保存事务的状态。
两阶段提交的逻辑也已经被封装在连接器里。
外部事务的超时时间需要大于flink检查点时间。

flink如果写hdfs,hdfs不提供事务,可以用sink任务模拟事务,比如预提交的log带一长串后缀,类似临时文件,正式提交时重命名文件删除这个后缀。

flink写kafka时注意,kafka默认read uncommitted,2pc的时候,即使是预提交,也会被消费,
所以要在此kafka的消费者(假设为flink)的隔离级别设为read comitted。

24/7

checkpoint和可重置stream source联合保证了恰好一次状态一致性。
异步、增量保证checkpoint对latency service layer agreements(SLAs)的影响很小。
对不同的存储系统,flink提供transactional sinks,保证即使failure也能恰好一次。
flink与cluster managers紧密集成。比如Hadoop YARN,Mesos,Kubernetes。如果一个进程fail,另一个新进程会自动接管。
HA-mode,高可用模式,基于ZooKeeper,消除所有单点failure。

savepoint

对比checkpoint,我感觉savepoint是手动保存,针对的是主动停机(比如flink本身版本更新,bug修复,不同版本应用的AB测试,集群迁移、集群扩展);而checkpoint是自动保存,针对的是意外停机(failure)。借助savepoint,实时处理也几乎能实现热更新。

actor

https://www.brianstorti.com/the-actor-model/

actor model不同于thread,是另一种并发解决方案。用于erlang和elixir两种语言。Java的akka库也是actor的应用之一。
actor之间是isolated的。actor有private state(不会被其它actor修改),有mailbox。这几点与进程不同。
单个actor是串行的。

When an actor receives a message, it can do one of these 3 things:
Create more actors
Send messages to other actors
Designate what to do with the next message
也有状态存储,但不是简单的修改原状态为新状态,关键是上一条信息影响下一条信息的处理方式。

erlang和actor的哲学是let it crash。传统的方案是,讨论各种情况(比如Java,catch所有可能的exception),然后分情况进行解决。而actor则总可以利用之前存储的private state进行复位,而不管是什么错误。执行复位的是supervisor actor。这种特性称为self heal。

akka的局限性在于,只提供at most once语义,剩下的需要application来实现。spark等中间件曾借助akka实现。(简单来说,akka过于原生,好比C和汇编。)

重点作业指标

延迟

作业消费kafka的延迟时间/延迟条目数。延迟过大,说明作业性能有问题,flink消费kafka的能力不足。

吞吐

判断作业当前并发下总消费数据量,如果跟kafka实际数据量的差距较大,说明flink消费kafka的能力不足。

checkpoint大小

state不应过大,如果checkpoint过大,说明数据流动在DAG中不顺畅,有堆积。

dag结点是否存在反压backpressure

什么是backpressure

(https://www.zhihu.com/question/49618581/answer/237078934)
翻译成背压是不恰当的。
反压是一种现象,在数据流从上游生产者向下游消费者传输的过程中,上游生产速度大于下游消费速度,导致下游的 Buffer 溢出,这就是反压现象。
关键是buffer溢出。
Backpressure 和 Buffer 是一对相生共存的概念,只有设置了 Buffer,才有 Backpressure 出现;只要设置了 Buffer,一定存在出现 Backpressure 的风险。如果生产速度不会威胁到消费速度,那也就不需要设置buffer,也不会出现backpressure。
例如你是开发服务器后端的,有一个 Socket 不断地接收来自用户的 http 请求来把用户需要的网页返回给用户。你的服务器所能承受的同时访问用户数是有上限的吧?比如说,你的服务器主机的处理器和内存情况决定了,它最多只能承受 5000~6000 个用户同时访问,再多的话服务器就有当掉的风险了。那么你决定:把用户数上限设置为 5000,当超出 5000 用户数的时候,再有新的访问就把它丢弃或者拒绝。那么对于这个案例,5000 就是你对于用户访问数设置的 Buffer;第 5001 个用户的访问,就叫做造成了 Backpressure 的产生;而你的「丢弃或拒绝」的行为,就是对于 Backpressure 的处理。
生产速度大于消费速度,所以需要 Buffer;
外部条件有限制,所以 Buffer 需要有上限;
Buffer 达到上限这个现象,有一个简化的等价词叫做 Backpressure;
Backpressure 的出现其实是一种危险边界,唯一的选择是丢弃新事件。

如何排查

在flink中,下游如果处理不过来,上游就会堆积,一直往下游找,第一个没有出现堆积的就是问题根源。

编程时遇到的问题

map函数中不能包含类的成员,否则会报无法序列化的错。
不能访问closure中的属性。

你可能感兴趣的:(大数据,开发,flink,大数据)