详解-Flink-实时应用的确定性

36次阅读

共计 5165 个字符,预计需要花费 13 分钟才能阅读完成。

作者:林小铂(网易游戏)

确定性(Determinism)是计算机科学中非常重要的个性,确定性的算法保障对于给定雷同的输出总是产生雷同的输入。在分布式实时计算畛域,确定性是业界始终难以解决的课题,由此导致用离线计算修改实时计算结果的 Lambda 架构成为大数据畛域过来近十年的支流架构。

而在最近几年随着 Google The Dataflow Model 的提出,实时计算和离线计算的关系逐步清晰,在实时计算中提供与离线计算统一的确定性成为可能。本文将基于风行实时计算引擎 Apache Flink,梳理构建一个确定性的实时利用要满足什么条件。

确定性与准确性

比起确定性,准确性(Accuracy)可能是咱们接触更多的近义词,大多数场景下两者能够混用,但其实它们稍有不同: 精确的货色肯定是确定的,但确定性的货色未必百分百精确。在大数据畛域,不少算法能够依据需要调整老本和准确性的均衡,比方 HyperLogLog 去重统计算法给出的后果是有肯定误差的(因而不是精确的),但却同时是确定性的(重算能够失去雷同后果)。

要分区确定性和准确性的缘故是,准确性与具体的业务逻辑严密耦合难以评估,而确定性则是通用的需要(除去多数场景用户成心应用非确定性的算法)。当一个 Flink 实时利用提供确定性,意味着它在异样场景的主动重试或者手动重流数据的状况下,都能像离线作业个别产出雷同的后果,这将很大水平上进步用户的信任度。

影响 Flink 利用确定性的因素

投递语义

常见的投递语义有 At-Most-Once、At-Least-Once 和 Exactly-Once 三种。严格来说只有 Exactly-Once 满足确定性的要求,但如果整个业务逻辑是幂等的,基于 At-Least-Once 也能够达到后果的确定性。

实时计算的 Exactly-Once 通常指端到端的 Exactly-Once,保障输入到上游零碎的数据和上游的数据是统一的,没有反复计算或者数据失落。要达到这点,须要别离实现读取数据源(Source 端)的 Exactly-Once、计算的 Exactly-Once 和输入到上游零碎(Sink 端)的 Exactly-Once。

其中后面两个都比拟好保障,因为 Flink 利用出现异常会主动复原至最近一个胜利 checkpoint,Pull-Based 的 Source 的状态和 Flink 外部计算的状态都会主动回滚到快照工夫点,而问题在于 Push-Based 的 Sink 端。Sink 端是否能顺利回滚依赖于内部零碎的个性,通常来说须要内部零碎反对事务,然而不少大数据组件对事务的反对并不是很好,即便是实时计算最罕用的 Kafka 也直到 2017 年的 0.11 版本才反对事务,更多的组件须要依赖各种 trick 来达到某种场景下的 Exactly-Once。

总体来说这些 Trick 能够分为两大类:

  • 依赖写操作的幂等性。比方 HBase 等 KV 存储尽管没有提供跨行事务,但能够通过幂等写操作配合基于主键的 Upsert 操作达到 Exactly-Once。不过因为 Upsert 不能表白 Delete 操作,这种模式不适宜有 Delete 的业务场景。
  • 预写日志(WAL,Write-Ahead-Log)。预写日志是广泛应用于事物机制的技术,包含 MySQL、PostgreSQL 等成熟关系型数据库的事物都基于预写日志。预写日志的基本原理先将变更写入缓存区,等事务提交的时候再一次全副利用。比方 HDFS/S3 等文件系统自身并不提供事务,因而实现预写日志的重任落到了它们的用户(比方 Flink)身上。通过先写长期的文件 / 对象,等 Flink Checkpoint 胜利后再提交,Flink 的 FileSystem Connector 实现了 Exactly-Once。然而,预写日志只能保障事务的原子性和持久性,不能保障一致性和隔离性。为此 FileSystem Connector 通过将预写日志设为暗藏文件的形式提供了隔离性,至于一致性(比方临时文件的清理)则无奈保障。

为了保障 Flink 利用的确定性,在选用官网 Connector,特地是 Sink Connector 时,用户应该注意官网文档对于 Connector 投递语义的阐明 [3]。此外,在实现定制化的 Sink Connector 时也须要明确达到何种投递语义,能够参考利用内部零碎的事务、写操作的幂等性或预写日志三种形式达到 Exactly-Once 语义。

函数副作用

函数副作用是指用户函数对外界造成了计算框架意料之外的影响。比方典型的是在一个 Map 函数里将两头后果写到数据库,如果 Flink 作业异样主动重启,那么数据可能被写两遍,导致不确定性。对于这种状况,Flink 提供了基于 Checkpoint 的两阶段提交的钩子(CheckpointedFunction 和 CheckpointListener),用户能够用它来实现事务,以打消副作用的不确定性。另外还有一种常见的状况是,用户应用本地文件来保留长期数据,这些数据在 Task 从新调度的时候很可能失落。其余的场景或者还有很多,总而言之,如果须要在用户函数里扭转内部零碎的状态,请确保 Flink 对这些操作是知情的(比方用 State API 记录状态,设置 Checkpoint 钩子)。

Processing Time

在算法中引入以后工夫作为参数是常见的操作,但在实时计算中引入以后零碎工夫,即 Processing Time,是造成不确定性的最常见也最难防止的起因。对 Processing 的援用能够是很显著、有欠缺文档标注的,比方 Flink 的 Time Characteristic,但也可能是齐全出乎用户预料的,比方来源于缓存等罕用的技术。为此,笔者总结了几类常见的 Processing Time 援用:

  • Flink 提供的 Time Characteristic。Time Characteristic 会影响所有应用与工夫相干的算子,比方 Processing Time 会让窗口聚合应用以后零碎工夫来调配窗口和触发计算,造成不确定性。另外,Processing Timer 也有相似的影响。
  • 间接在函数里拜访内部存储。因为这种拜访是基于内部存储某个 Processing Time 工夫点的状态,这个状态很可能在下次访问时就产生了变动,导致不确定性。要取得确定性的后果,比起简略查问内部存储的某个工夫点的状态,咱们应该获取它状态变更的历史,而后依据以后 Event Time 去查问对应的状态。这也是 Flink SQL 中 Temporary Table Join 的实现原理 [1]。
  • 对外部数据的缓存。在计算流量很大的数据时,很多状况下用户会抉择用缓存来加重内部存储的负载,但这可能会造成查问后果的不统一,而且这种不统一是不确定的。无论是应用超时阈值、LRU(Least Recently Used)等间接和零碎工夫相干的缓存剔除策略,还是 FIFO(First In First Out)、LFU(Less Frequently Used)等没有间接关联工夫的剔除策略,拜访缓存失去的后果通常和音讯的达到程序相干,而在上游通过 shuffle 的算子外面这是难以保障的(没有 shuffle 的 Embarrassingly Parallel 作业是例外)。
  • Flink 的 StateTTL。StateTTL 是 Flink 内置的依据工夫主动清理 State 的机制,而这里的工夫目前只提供 Processing Time,无论 Flink 自身应用的是 Processing Time 还是 Event Time 作为 Time Characteristic。BTW,StateTTL 对 Event Time 的反对能够关注 FLINK-12005[2]。

综合来讲,要完全避免 Processing Time 造成的影响是十分艰难的,不过轻微的不确定性对于业务来说通常是能够承受的,咱们要做的更多是提前预料到可能的影响,保障不确定性在可控范畴内。

Watermark

Watermark 作为计算 Event Time 的机制,其中一个很重要的用处是决定实时计算何时要输入计算结果,相似文件完结标志符(EOF)在离线批计算中达到的成果。然而,在输入后果之后可能还会有早退的数据达到,这称为窗口完整性问题(Window Completeness)。

窗口完整性问题无奈防止,应答方法是要么更新计算结果,要么抛弃这部分数据。因为离线场景提早容忍度较大,离线作业能够推延肯定工夫开始,尽可能地将提早数据纳入计算。而实时场景对提早有比拟高的要求,因而个别是输入后果后让状态保留一段时间,在这段时间内依据早退数据继续更新后果(即 Allowed Lateness),尔后将数据抛弃。因为定位,实时计算人造可能呈现更多被抛弃的早退数据,这将和 Watermark 的生成算法严密相干。

尽管 Watermark 的生成是流式的,但 Watermark 的下发是断点式的。Flink 的 Watermark 下发策略有 Periodic 和 Punctuated 两种,前者基于 Processing Time 定时触发,后者依据数据流中的非凡音讯触发。

基于 Processing Time 的 Periodic Watermark 具备不确定。在平时流量安稳的时候 Watermark 的晋升可能是阶梯式的(见图 1(a));然而在重放历史数据的状况下,雷同长度的零碎工夫内解决的数据量可能会大很多(见图 1(b)),并且伴有 Event Time 歪斜(即有的 Source 的 Event Time 显著比其余要快或慢,导致取最小值的总体 Watermark 被慢 Watermark 拖慢),导致原本抛弃的早退数据,当初变为 Allowed Lateness 之内的数据(见图 1 中红色元素)。

相比之下 Punctuated Watermark 更为稳固,无论在失常状况(见图 2(a))还是在重放数据的状况(见图 2(b))下,下发的 Watermark 都是统一的,不过仍然有 Event Time 歪斜的危险。对于这点,Flink 社区起草了 FLIP-27 来解决 [4]。基本原理是 Source 节点会选择性地生产或阻塞某个 partition/shard,让总体的 Event Time 放弃靠近。

除了 Watermark 的下发有不确定之外,还有个问题是当初 Watermark 并没有被纳入 Checkpoint 快照中。这意味着在作业从 Checkpoint 复原之后,Watermark 会从新开始算,导致 Watermark 的不确定。这个问题在 FLINK-5601[5] 有记录,但目前只体现了 Window 算子的 Watermark,而在 StateTTL 反对 Event Time 后,或者每个算子都要记录本人的 Watermark。

综上所述,Watermark 目前是很难做到十分确定的,但因为 Watermark 的不确定性是通过抛弃早退数据导致计算结果的不确定性的,只有没有抛弃早退数据,无论两头 Watermark 的变动如何,最终的后果都是雷同的。

总结

确定性有余是妨碍实时计算在要害业务利用的次要因素,不过以后业界曾经具备了解决问题的实践根底,剩下的更多是计算框架后续迭代和工程实际上的问题。就目前开发 Flink 实时利用而言,须要留神投递语义、函数副作用、Processing Time 和 Watermark 这几点造成的不确定性。

参考:

  1. Flux capacitor, huh? Temporal Tables and Joins in Streaming SQL
    https://flink.apache.org/2019/05/14/temporal-tables.html
  2. FLINK-12005 Event time support
    https://issues.apache.org/jira/browse/FLINK-12005
  3. Fault Tolerance Guarantees of Data Sources and Sinks
    https://ci.apache.org/projects/flink/flink-docs-release-1.10/dev/connectors/guarantees.html
  4. FLIP-27: Refactor Source Interface
    https://cwiki.apache.org/confluence/display/FLINK/FLIP-27%3A+Refactor+Source+Interface
  5. [FLINK-5601] Window operator does not checkpoint watermarks
    https://issues.apache.org/jira/browse/FLINK-5601

作者介绍:

林小铂,网易游戏高级开发工程师,负责游戏数据中心实时平台的开发及运维工作,目前专一于 Apache Flink 的开发及利用。探索问题原本就是一种乐趣。

正文完
 0