Flink 复习
Flink 整体认知 - Flink 整体认知 - 认识 Flink - Flink 是什么 - Flink 是一个对于有界和无界数据流进行有状态计算的框架和分布式处理引擎 - 框架:Flink 不是一个单独的软件命令,而是一套开发 + 运行的系统 - Flink 和 Hadoop/Spark/Storm 的区别 - 基本定义 - Hadoop 是什么 - 大数据基础设施体系 - HDFS:分布式存储 - MapReduce:离线批处理计算 - YARN:资源管理 - 早期大数据时代的“存储+资源管理+离线计算“基础平台 - Spark 是什么 - 通用的大数据计算引擎 - 流处理主要通过 Structured Streaming - 更偏通用分析平台,批处理很强,流处理也能做 - Storm 是什么 - 是一个分布式实时计算系统 - 较早一代的纯流式处理框架,比 Flink 早 - 比较维度 - 主要定位 - Hadoop - 海量数据存储 - 离线批处理 - 高吞吐,不强调低延迟 - Spark - 通用大数据引擎 - 强批处理 - 可扩展到 SQL、ML、Streaming - Flink - 有状态流处理 - 事件时间 - 流批统一 - 更强实时计算语义 - 处理模型 - Hadoop: Batch - 读一批数据->map->shuffle->reduce->输出结果 - Spark: Batch + Micro-batch - Flink: Native Streaming,批流统一 - 延迟特征 - Hadoop - 延迟高,通常分钟级甚至更长 - Spark - micor-batch 延迟比原生流高一点 - Flink - 低延迟实时计算 - 状态能力 - Hadoop - 不擅长生命周期有状态流计算 - Spark - 能做状态处理,但不是以 stateful streaming 为核心 - Flink - stateful computations over data streams - exactly-once state consistency - 什么是批处理、流处理、实时计算 - 批处理 - 定义:先把一段时间内的数据收集起来,等数据攒够一批后,再统一进行计算 - 特点:先到齐,再处理、延迟高、吞吐高、程序有结束点 - 流处理 - 定义:把数据看成一个持续到来的事件流,数据一到,系统就持续处理 - 特点:连续不断、持续进行、低延迟、要处理历史记忆 - 实时计算 - 定义:从数据产生,到计算结果可用,时间尽可能短 - 实时计算不是来了就必须零延迟,而是在业务可接受的时间范围内尽快给出结果 - Flink 的应用场景 - 事件驱动应用 - 系统不是定时查数据库,而是收到事件才触发计算、状态更新或外部动作 - 典型业务 - 欺诈检测 - 异常检测 - 规则告警 - 业务流程监控 - 数据分析应用 - 从原始数据中提取信息和洞察 - 典型业务 - 每分钟订单量 - 热门榜单 - 实时数仓 - 离线 + 实时一体分析 - 数据管道与 ETF - 从上游系统读数据,做清洗、转换、补全、标准化、过滤、路由,再写到下游系统 - 典型业务 - Kafka 到 Kafka - Kafka 到数据库/搜索引擎/湖仓 - CDC + 实时同步 - Flink 基本架构 - JobManager、TaskManager - 最小 Flink 集群 -  - 在本地或某台机器上提交作业 - JobManager 接收这个作业,分析要怎么跑 - 把真正要执行的子任务分发给多个 TaskManager - TaskManager 在各自机器上并行处理数据 - 什么是 JobManager - 是 Flink 集群的控制中心 - 负责接收/解析/调度/监控/作业、协调故障恢复、管理元信息 - 什么是 TaskManager - 是 Flink 集群里真正干活的 worker - 主要负责执行子任务/占用和提供计算资源/与其他 TaskManger 传输数据/向 JobManager 汇报状态 - Slot 是什么 - 定义 - Flink 用 Slot 告诉调度器:这台 TaskManager 最多能接多少份执行任务 - 为什么需要 Slot - 控制一个 TaskManager 能接多少任务 - 做资源切分 - 在隔离与利用率之间做平衡 - Slot 和 TaskManager 的关系 - 一个 TaskManager 可以有多个 Slot - Slot 是 TaskManager 提供给集群调度的执行容量 - Slot 是资源申请和任务放置的基本单位之一 - Slot 和并行度的关系 - 并行度指的是一个算子会拆成多少个并行子任务 - 执行这些子任务需要 Slot - 在默认 slot sharing 开启时,作业大致需要的 slot 数 = 该作业的最高并行度 - JobGraph、ExecutionGraph - 什么是 JobGraph - Flink 作业的逻辑图/逻辑执行计划 - JobGraph 里面的点,叫做 JobVertex,它是一个逻辑阶段/一个逻辑算子节点 - JobGraph 不强调每个并行子任务 - 什么是 ExecutionGraph - 是 JobGraph 经过并行展开后的实际执行拓扑 - ExecutionGraph 的核心点是 ExecutionVertex - 并行度是什么 - 本质 - Flink 算子可以想象成一个类似读 Kafka/做 map 转换/做窗口聚合的工作 - 并行度决定一个算子会被拆分成几个 subtask 一起做 - 为什么需要并行度 - 一份工作只让一个实例做,处理能力有限 - 提高并行度,本质就是把工作拆开,让多个 subtask 同时处理 - 一个 flink 作业如何运行起来 - 总流程 - 写 Flink 程序,调用 execute() - Client 负责提交作业 - 程序先形成逻辑数据流图 - JobManager 接收作业 - JobManager 把 JobGraph 转成 ExecutionGraph - 调度器根据并行度和可用 slot 做任务部署 - TaskManager 真正执行 subtask - 运行中持续监控、处理状态、必要时恢复故障 - 编写代码时,发生了什么 - 写的是逻辑处理链路,不是真正的分布式执行指令 - 调用 execute() 后,作业才真正进入提交流程 - Client 做了什么 - Client 是提交入口,负责将应用提交到集群 - Client 不是主要计算节点 - JobGraph 在什么时候出现 - 代码先被整理为数据流图,也就是 JobGraph - JobGraph 偏逻辑层,还没有真正展开成每个 subtask,不是最终的并发执行形态 - JobManager 接手后做什么 - 接收作业 - 管理作业生命周期 - 把逻辑图变成执行图 - 协调资源和任务部署 - Execution Graph 怎么来的 - ExecutionGraph 是 JobGraph 的并行化版本 - 并行度在这里真正展开 - 运行状态也在这里变得具体 - 调度阶段发生什么 - 要跑多少个 subtask - 这些 subtask 放到哪些地方跑 - 数据怎么在它们之间流动 - slot 发挥什么作用 - TaskManager 提供 slots - 调度器找空闲 slot - 默认 slot sharing 会影响所需 slot 数 - TaskManager 真正干活时发生了什么 - TaskManager 执行 subtask - 每个 subtask 处理自己那部分的输入数据 - 上下游 subtask 之间可能发生网络传输 - 状态、窗口、定时器等能力都在这里真正运作 - 作业运行中,JobManager 还在做什么 - 跟踪任务状态 - 监控资源与任务健康情况 - 失败时触发恢复 - 作业失败了会怎样 - JobManager 感知失败 - 调度器决定如何恢复 - TaskManager 上的任务可能被重新部署 - 搭建开发环境 - Maven 项目骨架 - POM 文件 -  - fink-streaming-java - 是 DataStream API 入门最核心的依赖之一 - 编写 StreamExecutionEnvironment、DataStream 这类代码会用到它 - flink-clients - 用于本地执行、提交作业相关能力 - DataStream API 的基本项目结构 - 最小程序骨架 -  -  - 代码详解 - StreamExecutionEnvironment - 这是 DataStream 程序的入口 - StreamExectionEnviroment 是所有 Flink 程序的基础,通过 getExecutionEnvironment() 获取 - 是整个 Flink 程序的运行上下文或总入口 - 负责创建 source、串起数据流、设置并行度/checkpoint 等运行参数、触发执行 - Source - Source 就是数据从哪里来 - DataStrema 最初是从各种 source 创建出来的 - Transformation - 就是对数据流做处理 - DataStream 程序是对数据流做 transformation 的程序 - 基础类别 - map:一条变一条 - filter:按条件过滤 - flatMap:一条变多条 - keyBy:按 key 分组 - sum/reduce:聚合 - Sink - Sink 就是结果往哪里去 - 结果通过 sink 返回,可以写入文件、标准输出等 - print() 可以直接在控制台打印输出 - sinkTo(xxx) 可以写入自定义的目的 - execute() - Flink Job 是在调用 execute() 时创建并提交的 - DataStream 程序最后一步就是触发程序执行 - 跑通 WordCount - 最小 WordCount 代码 -  -  - 代码解析 - 创建执行环境 - StreamExecutionEnvironment 是每个 DataStream 程序的起点,表示开始描述一条 Flink 数据处理流水线了 - 创建输入流 - flatMap 把每一行拆分成单词 - flatMap 可以对一个输入产生任意多输出 - Tuple2<String, Integer> - 是 Flink 常见的二元组类型 - keyBy(value -> value.f0):按单词分组 - keyBy 会把流按照 key 逻辑分区,所有相同 key 会进入同一个分区 - sum(1):对第二个字段求和 - DataStreamAPI 基础 - Source、Transformation、Sink - Source - Flink 程序接收数据的地方 - 可以来自内存集合、文件、消息队列、socket 等 - 集合 Source - DataStream<String> stream = env.fromElements("a","b","c"); - DataStream<Integer> nums = env.fromElements(1,2,3); - 真实生产的 Source - Kafka - 文件 - Socket - 自定义 source - 其他外部系统 connector - Transformation - Operators 会把一个或多个 DataStream 转换成新的 DataStream - 程序可以把这些变换组合成复杂的数据流拓扑 - map - 一条输入变成一条输出 - DataStream<Integer> result = nums.map(x -> x*2) - map 不会改变条数 - 适合做字段转换、格式转换、简单加工 - flatMap - 一条输入可以变为多条输出,也可以一条都不输出 -  - flat 适合切词、拆分、展开 - WordCount 通常用它来把一行拆分成多个词 - 比 map 更灵活 - filter - 按条件保留或丢弃数据 - DataStream<Integer> evenNums = nums.filter(x -> x % 2 == 0); - filter 不改数据结构,主要做筛选 - keyBy - 按 Key 对数据流做分组 - 所有具有相同 key 的记录会被分到同一个分区上,通常内部是哈希分区 -  - keyBy 不是聚合 - 只是按 key 把数据归组 - 后面的 sum、reduce、状态、窗口通常都要建立在 keyBy 后面 - reduce - 对同一个 key 下的数据做增量操作 -  - reduce 必须通常接在 keyBy 后面 - 是增量聚合 - 比一次性收集所有数据再算更高效 - Sink - result.print() - print() 是最小可用 Sink - 主要用于学习和调试 - 后续接 Kafka、文件、数据库,思路还是一样,只是 Sink 换了 - union/connect - union - 定义 - 会把两条或多条数据流合并为一条新流,新流包含所有输入流的元素 - 所有输入流必须是相同类型 - 示例 -  - 关键点 - 类型必须一直 - union 后得到的还是同一种类型的普通 DataStream - union 适合本来就是同类数据,只是来自不同来源 - connect - 连接两条流,但是保留各自的类型信息 - 示例 -  - connect 更像先把两条流并排放在一起,然后你自己决定怎么分别处理左边和右边 - connect 后怎么用 - connect 一般不会单独停在那,通常会继续接处理函数 -  - RichFunction - 为什么要有 RichFunction - map/filter/flatMap 拿不到 Flink 运行时信息,也不方便做初始化和清理 - 需求:程序开始时初始化资源/程序结束时做清理/知道当前子任务编号/知道并行度/拿到运行时上下文 - RichFunction 定义了生命周期方法,以及访问函数执行上下文方法 - RichFunction 是什么 - RichFunction 是一个基础接口,很多常见的 rich 版本函数都建立在它之上 - 平时真正会写的,不是 RichFunction 本身,而是具体子类 - RichMapFunction - RichFilterFunction - RichFlatMapFunction - 可以理解为普通 Function 的增强版 - 和普通函数的区别 - 除了处理逻辑,还多了 open()、close()、getRuntimeContext() - open 用于初始化,close 用于清理,getRuntimeContext 用于获取运行时上下文 - RichFunction 最核心的三个能力 - open(OpenContext openContext) - 这是初始化方法 - 会在真正工作方法例如 map、join 前调用 - 适合做一次性初始化工作 - 就像开工前准备 - close() - 这是清理方法 - 会在主工作方法最后一次调用之后执行 - 适合做清理工作 - 就像收工后打扫现场 - getRuntimeContext() - 拿运行时上下文的方法 - 包含函数运行并行度、当前 subtask 的索引、执行该 task 的名称 - 例子 -  - 基本调试方法 - 先用最小输入跑通 - 先用 fromElements() 构造很小的输入集 - 最基础的调试方式: print() - 中间的关键步骤也可以加上 print() -  - 按 source/transformation/sink 分段排查 - 先怀疑 source - source 有没有真的产生数据 - 输入是不是空的 - 输入格式是不是和我想的不一样 - 可以先 lines.print("source") 确认下 - 再怀疑 transformation - 每做一步变化,就临时加一个 print() - 最后怀疑 sink 时间与窗口 - 时间与窗口 - 时间语义 - processing time - 什么是 processing time - processing time 就是某个算子在处理这条数据时,所在机器的系统时间 - 不看事件真正什么时候发生,而是看 Flink 现在处理到它时是几点 - processing time 的本质 - 用机器时钟当做时间标准 - 不需要从数据取时间戳 - 不需要 watermark 来推动时间前进 - event time - 什么是 event time - 不看 flink 什么时候处理到这条数据,而是看这条数据业务上到底是什么时候发生的 - 这个时间通常在进入 flink 前就嵌入到记录了 - 为什么 event time 重要 - 如果想得到可复现、符合业务真实时间的分析结果,就应该用 event time - 把统计口径从系统什么时间看到它,切回什么时候真正发生 - 本质 - 用事件自带的时间戳,作为所有时间相关操作的基准 - 必须能从数据集提取时间戳 - 系统需要某种机制来判断某个时间点之前的数据大致到齐了 - ingestion time - 事件进入 Flink 时被赋予的时间 - WaterMark - WaterMark 是什么 - WaterMark 是 Flink 用于衡量 EventTime 进度的机制 - Watermark 在数据流中携带一个时间戳 t,WaterMark(t) 表示事件已经推进到 t - 为什么需要 WaterMark - 真实世界里的数据经常会乱序到达 - 问题:应该等到什么时候,才能认为某个时间点之前的数据差不多到齐了 - Watermark 表示什么 - 到目前为止,我认为时间戳小于等于 t 的事件应该都到了 - Watermark 和窗口的关系 - Watermark 最常见的作用,就是触发 EventTime 窗口计算 - Watermark 决定什么时间可以开始算 - Watermark 和迟到数据有什么关系 - 如果某条事件的时间戳小于等于当前已经到达的 watermark,但现在才到,这条数据就是迟到数据 - Watermark 怎么来的 - 使用 Event Time 时,Flink 需要知道每个元素的时间戳 - 时间戳可以通过 TimestampAssigner 提取 - 需要 WatermarkGenerator 生成 watermark,两者一起通过 WatermarkStrategy 配置 - Watermark 来自于定义的时间策略 - Watermark 理解 - Watermark 就像 Event Time 世界里的逻辑时钟 - 系统告诉下游:可以把时间推进到这里了 - 有序和乱序流的 Watermark 生成方式 - 为什么分有序流和乱序流 - Watermark 的生成方式,本质取决于你的数据时间戳分布特征 - 有序流的 Watermark 生成方式 - 核心假设 - 后面到来的事件,时间戳不会比前面的小 - 生成逻辑 - 在有序流里,当前看到的最大时间戳就代表时间已经推进到那里了 - 乱序流的 Watermark 生成方式 - 核心假设 - 虽然事件会乱序到达,但是可以给出一个最大乱序范围 B - 生成逻辑 - 当前 watermark = 已观察到的最大事件时间戳 - 最大乱序容忍时间 - 具体例子 -  - 事件对象里面要有事件时间字段 - Source 只是构造测试数据 - WatermarkStrategy 是核心 - forBoundedOutOfOrderness(Duration.ofSeconds(5)) - 允许这条流最多乱序 5s - Watermark 会根据当前看到的最大时间戳-5 秒来推进 - withTimestampAssigner()告诉 Flink 事件时间从那一列取 - assignTImestampsAnsWatermarks - 给流里的每个元素附上事件时间戳 - 同时让 Flink 根据策略找 Watermark - 窗口基础 - 为什么需要窗口 - 流数据是无界的,理论上不会自然结束 - 对这种数据不能简单说等全部到期再统计 - Flink 的窗口机制就是把连续到来的流按规则划分为一个个有序集合 - Flink 里窗口的总体分类 - Tumbling Window (滚动窗口) - 定义:是固定大小、连续、互不重叠的窗口 - 适合场景 - 每 5 分钟订单数 - 每小时 GMV - 每日 UV - 每分钟接口调用量 - Sliding Window (滑动窗口) - 定义:也是固定大小的窗口,会按照一个更小的步长持续滑动 - 示例:窗口大小 10 分钟,每 5 分钟滑动一次 - 适合场景 - 最近 10 分钟 PV,每分钟更新一次 - 最近 30 分钟订单量,每 5 分钟更新一次 - Session Window (会话窗口) - 定义 - Session Window 没有固定窗口,它的边界由活动间隔决定 - session window 的边界由 inactivity gap 定义 - 指定 gap 时间内没有新事件到来,当前 session 就关闭 - 如果中间一直没有很长时间断开,这些行为属于同一个会话 - 适合场景 - 用户会话分析 - App 连续使用时长 - 连续操作链路 - Count Window (计数窗口) - 定义 - Count Window 按元素个数切 - 每 10 条数据统计一次/每 100 条点击做一次聚合 - 适合常见 - 每 100 条日志做一次分析 - 每 50 条事件批量聚合 - 窗口最重要的两个维度 - 切分维度 - 按时间切 - 按数量切 - 是否重叠 - 不重叠 - 可能重叠 - 不固定边界 - 窗口对齐 - 时间窗口是对齐到 epoch 的 - 时间窗口默认是按时间对齐的,不是按程序启动时刻随意切 - 窗口聚合 - 窗口聚合解决什么问题 - 窗口把无界流切成有限片段后,通常还要对每个片段做计算 - 不同业务对怎么算有不同要求 - 有的业务只要一个简单结果,例如总数 - 有的要复杂中间结构,比如平均值要维护 sunm 和 count - 有的除了结果,还要输出窗口边界、key、触发时间等元信息 - 增量聚合 - 增量聚合,就是数据一到,就立即把它并入当前结果 - 不是把整个窗口所有元素都存起来,等窗口结束后再统一计算 - 全量窗口处理 - 先把窗口里所有元素都留着,等窗口触发时,再把完整集合交给函数处理 - reduce:最轻量的窗口聚合 - 本质 - ReduceFunction 的核心思想是:把两个同类型元素合并成一个同类型元素 - 为什么高效 - 窗口里不需要保留全部元素,只需要持续维护一个当前归并结果 - 最大的限制 - 输入类型和输出类型必须一致 - aggregate:更通用的增量聚合 - 本质 - 把聚合拆成了几个阶段 - 创建累加器 createAccumulator() - 把新元素加进累加器 add() - 从累加器得到最终输出 getResult() - 为什么比 reduce 更灵活 - 把三种类型分开 - 输入类型 IN - 累加器类型 ACC - 输出类型 OUT - ProcessWindowFunction:最灵活的窗口处理 - 本质 - 核心价值不是更快,而是信息更多、能力更强 - 适合什么场景 - 需要窗口开始时间/结束时间 - 需要访问 key - 需要输出带窗口元信息的结果 - 需要看完整窗口数据 - 需要复杂后处理逻辑 - 最大的优势 - 能够拿到窗口元信息 - 迟到数据处理 - 什么叫迟到数据 - 这条数据的事件时间本来属于某个旧窗口 - 但它到达系统时,这个窗口已经因为 watermark 推进而触发过了 - 它来晚了 - allow lateness - 是 Flink 为窗口保留的额外容忍时间 - allowedLateness(...) 指定窗口在 watermark 超过结束时间后还能保存多久,以接收迟到数据 - side output - side output 是把某些特殊数据从主流旁路输出的机制 - 把太晚了、已经不能再进主窗口的数据单独收集出来 - 结果修正 - 配置了 allow lateness 后,窗口第一次输出的结果可能不是最终结果 - 后面如果又来了还能接受的迟到数据,窗口结果会更新并再次触发输出 状态与容错 - 状态与容错 - State 基础 - 什么是状态 - 状态=程序对过去信息的记忆 - 没有状态:程序只看当前一条数据 - 有状态:程序处理当前数据时,还能参考历史数据 - 为什么流处理需要状态 - 要做累计 - 用户累计消费金额 - 某设备累计告警次数 - 某单词当前总出现次数 - 要做对比 - 当前温度和上一次温度相比是否突增 - 当前订单状态和之前状态是否矛盾 - 要做时序逻辑 - 10 s 内连续 3 次登录失败 - 下单后 15 分钟未支付 - 一段会话里的连续点击行为 - 要在故障后恢复业务上下文 - Key State - 定义 - Keyed State 是和 Key 绑定的状态 - Keyed State 只能用于 Keyed Stream 上 - 先对 DataStream 做 keyBy(...),然后才能使用 keyed state - 为什么重要 - 很多业务天然都是按照实体分开记忆的 - 底层直觉 - 一个分布式哈希表,按 key 切开,每个并行实例只负责自己那部分 key - Operate State - 定义 - Operator State 是和算子并行实例绑定到状态,而不是和业务 key 绑定 - 算子级记忆,而不是业务实体记忆 - 适合什么场景 - source/sink 级别的处理位点 - 算子实例自己的工作上下文 - Keyed State - ValueState:最常用的 Keyed State - 是什么 - ValueState<T> 表示:每个 key 只有一个值 - 典型场景 - 每个用户累计点击次数 - 每个订单当前状态 - 每个设备最近一次温度 - 每个用户最近一次登录时间 - 每个 key 当前告警开关状态 - ListState:每个 Key 存一串值 - 是什么 - ListState<T> 表示:每个 Key 下面维护一个列表 - 典型场景 - 缓存最近若干条事件 - 暂存等待拼接到多条消息 - 收集一个 key 下的一批原始记录 - 先攒起啦,后面统一处理 - 风险 - 容易无限增长 - MapState:每个 Key 下再套一个 map - 是什么 - MapState<UK,UV> 表示:每个 key 对应一个 map - 典型场景 - 每个用户下,不同商品类别的点击次数 - 每个设备下,不同传感器字段的最新值 - ReducingState:自动做同类型聚合 - 是什么 - ReducingState<T> 表示:每个 key 不直接存明细,而是存一个经过 reduce 的聚合结果 - AggregatingState:更灵活的聚合状态 - 是什么 - AggregatingState<IN, OUT> 和 Reducing State 很像,但更通用 - 包含输入类型 - ProcessFunction - 为什么需要 ProcessFunction - 传统的算子 - map/filter/flatMap/keyBy/window 聚合 - 更偏声明式处理,而不是自己掌控每条事件+时间回调+状态细节 - 无法解决的问题 - 订单创建后 15 分钟未支付就报警 - 用户 10s 内连续登录失败 3 次 - 某 key 1 分钟没有更新时输出当前统计值 - 无法解决问题共同点 - 要逐条处理事件 - 要记住状态 - 要在未来某个时间点触发逻辑 - ProcessFunction 是什么 - 当高级 API 不够表达你的业务规则时,就下到 ProcessFunction 这一层自己控制 - 更像一个加强版的 flatMap - 对每条输入都能处理 - 能产生 0、1 或多条输出 - 能访问上下文 - 能注册 timer - 在 timer 到点时再执行逻辑 - KeyedProcessFunction 的核心方法 - processElement(...) - 每来一条输入事件,Flink 就会调用它 - 事件到了,现在立即处理它 - onTimer(...) - 注册的 timer 到点时,Flink 会调用它 - 之前约好的时间到了,现在执行补充逻辑 - Context 提供什么 - 在 processElement(...),Flink 会提供一个 Context,这个 Context 能做几件事 - timestamp():拿当前元素时间戳 - timeService():访问定时器服务 - getCurrentKey():拿当前 Key - output(...):输出到 side output - 什么是 Timer - 在当前处理某条事件时,告诉 Flink:“到未来某个时间点,请再回头执行一次逻辑“ - Timer 为什么 和状态一起用 - Timer 只负责未来提醒你一次,但是提醒你时应该做什么,要靠状态来判断 - Checkpoint 与容错 - Checkpoint 是什么 - Checkpoint 是 Flink 对“作业状态+输入流位置“的一致性快照 - Checkpoint 至少包含两类东西 - 各个 Stateful operator 的状态快照 - 各个 source 当前读到哪儿的位点,例如 Kafka offset 这类输入位置 - 为什么流处理必须依赖 Checkpoint - 状态负责记忆,checkpoint 负责让记忆在失败后不丢 - Checkpoint 保存的是什么 - Operator/Keyed State - Source 位置 - Timers 连接外部系统 - 连接外部系统 - Flink 读写文件与接 - Kafka 集成 - Flink 与数据库 - 序列化与数据格式 Flink SQL 与 Table API - Flink SQL 与 Table API - 认识 Table API 与 Flink SQL - 为什么要有 Table API 与 SQL - DataStream 更像手写处理逻辑,Table API/SQL 更像声明我要算什么 - Table API 是什么 - Table API 是一种 language-integrated query API - 是 SQL 的超集,专为 Flink 设计 - Flink SQL 是什么 - 用 SQL 语法来描述 Flink 上的流批处理逻辑 - 可以理解为 - 输入表先注册好 - 直接写 SQL - Flink 帮你把 SQL 变为实际可执行程序 - Table API 和 SQL 的关系是什么 - 不是两套互斥系统,而是同一层抽象的两种表达方式 - SQL - 更适合分析型开发 - 数仓开发 - DDL/DML 风格任务 - 让更多懂 SQL 的人参与开发 - Table SQL - 想保留代码层面的类型和组合能力 - 需要和 Java/Scala/Python 代码更紧密集成 - 想在 API 层做更多程序化的表运算 - 什么是 Dynamic Table (动态表) - 动态表就是把“不断到来的流“逻辑上看成一张会持续变化的表 - 什么是 Continuous Query (连续查询) - Flink SQL 不是查一下就结束都传统 SQL,而是持续运行、持续更新结果的 SQL - append/retract/upsert 结果是什么意思 - 问题:结果表变化,怎么把这个变化告诉下游? - 动态表包含三种编码方式 - Append-only - 结果表只会新增,不会修改旧结果,只发追加记录就行 - Retract - 如果结果表中的旧值会被更新或删除,就不能只 append - retract stream 用 add/retract message 来表示 - 对于 update 会先发撤回旧值,再发一条新增新值 - Upsert - upsert stream 需要唯一键 - INSERT 和 UPDATE 都可以用 upsert message 表示 - DELETE 用 delete message 表示 - 相比 retract,upsert 只需一条消息 - Flink SQL 基础语法 - 整体心智模型 - 典型 Flink SQL 作业 - CREATE TABLE source ... - CREATE TABLE sink ... - INSERT INTO sink - SELECT ... FROM source WHERE ... GROUP BY ... - 四步流程 - 定义源表 - 定义结果表 - 写查询逻辑 - 把查询逻辑结果写到目标表 - CREATE TABLE 是什么 - 本质 - CREATE TABLE 是在当前 Catalog 里注册一个表定义,让这个表可以被 SQL 使用 - 告诉 Flink:有一张表,它的字段是什么,连接那个外部系统,怎么读,怎么写 - 最小骨架 -  - 列定义:字段名和类型 - WITH (...):连接器和外部系统参数 - WITH(...) - 通常是连接外部系统所需的 table options - connector - 指定背后连接那个系统(Kafka/filesystem/upsert-kafka/print) - format 相关参数 - 告诉 Flink 数据是 JSON、CSV、Avro 等什么格式 - 外部系统连接参数 - 例如 Kafka topic、broker 地址、分区键等 - Flink SQL 最基础的查询语法 - SELECT - SELECT user_id, amount FROM orders; - WHERE - SELECT user_id, amount FROM orders WHERE amount > 100; - GROUP BY - SELECT user_id, COUNT(*) FROM orders GROUP BY user_id; - INSERT TO - INSERT INTO result SELECT user_id, COUNT(*) FROM orders GROUP BY user_id; - sqlQuery() 和 executeSql() 怎么理解 - sqlQuery() 用于 SELECT/VALUES 这类查询,返回一个 Table - executeSql() 用于执行 DDL、DML 等语句,例如 CREATE TABLE、INSERT INTO - 时间属性与窗口 SQL - 什么是时间属性 - 在流式 SQL 中,窗口函数要求引用的是有效的时间属性,也就是 processing time attribuet 或 event time attribute - 被 Flink 运行时承认,并能参与时间推进和窗口计算的特殊时间列 - 时间属性分类 - 处理时间属性 (Processing Time) - 这条记录被 Flink 当前机器处理到的时间 - 事件时间属性 (Event Time/Rowtime) - 事件时间是业务事件真正发生的时间 - 在 SQL 中定义处理时间属性 - 在 CREATE TABLE 时,可以定义一个处理时间列 -  - 在 SQL 中定义事件时间属性 - 事件时间通常来自源数据中的某个时间段 - 仅仅有这个字段还不够,通常要搭配 WATERMARK -  - ts 是事件时间类 - WATERMARK FOR ts AS ... 告诉 Flink 如何基于 ts 推进事件时间 - 窗口 SQL 推荐怎么写 - Windowing TVF (窗口表值函数) - 窗口聚合支持 TUMBLE/HOP/CUMULATE/SESSION 这些都通过 Windowing TVF 表达的 - 什么是 TUMBLE 窗口 SQL - TUMBLE 对应滚动窗口 - 按固定窗口大小分配数据 - 窗口不重叠 - 在结果中增加 window_start、window_end、window_time 三列 -  - DESCRIPTOR(ts):告诉 Flink 用 ts 这列作为时间属性 - INTERVAL '5' MINUTES:窗口大小 5 分钟 - 什么是 HOP 窗口 SQL - HOP 对应滑动窗口 - HOP 有 slide 和 size - 它会创建可能重叠的窗口 - 同一条记录可能会进入多个窗口 -  - 每 1 分钟滑动一次 - 每个窗口覆盖近 10 分钟 - 结果表同样会带窗口边界列 - 什么是 CUMULATE 窗口 SQL - CUMULATE 是累积窗口 - 适合总窗口范围较大,但想看逐步累积结果 - 每小时一个大窗口,每 5 分钟输出一次当前已累计到哪里的聚合值 -  - step 是 5 分钟 - max size 是 1 小时 - 窗口会逐步扩张输出 - 什么是 SESSION 窗口 SQL - SESSION 对应会话窗口 -  - 30 分钟内持续有事件,属于同一会话 - 维表 JOIN 与实时数仓 - 什么是事实流,什么是维表 - 实时流 - 不断到来的业务事件流 - 通常来自于 Kafka、CDC、日志流等 source table - 维表 - 维表通常是描述型、补充型信息 - 为什么实时数仓一定遇到维表 JOIN - 事实流提供发生了什么,维表提供这件事属于谁、是什么、怎么解释 - 什么是实时宽表 - 把事实流和一个或多个维表关联后,得到的一张字段更完整、可直接分析或落库的结果表 - 什么是 Lookup Join - 以事实流为驱动,对外部维表做按键查询 - 什么是 Temporal Join/Versioned Table Join - 按某个时间点,去取维表在那个时刻的版本,再和事实表做关联 - 什么是 Versioned Table - 一张会变化、并且保留变化历史的表 原理深入 - 原理深入 - 背压与性能调优 - 什么是背压 - 上游发太快,下游吃不动