Flink如何实现新的流处理应用第二部分:版本化状态

这是我们关于 Flink 如何实现新的流处理应用系列中的第二篇博文。第一部分介绍了事件时间和乱序处理。

这篇文章是关于版本化应用程序状态,后面是关于会话和高级窗口的文章。

1. 有状态数据流处理

流处理可以分为无状态处理和有状态处理。无状态流处理应用仅是接收事件,然后基于接收的单个事件的信息产生某种响应(例如,报警或事件转换)。因此,没有”记忆”或聚合能力。但是在许多场景下还是有用的(例如,过滤,简单的转换),许多有趣的流处理应用,例如基于时间窗口的聚合,复杂事件处理,多事件的模式匹配,以及事务处理都是有状态的。

早期的流处理系统,如 Apache Storm(使用 core API)不支持状态(Storm Trident,Storm 通过附带的库来支持状态)。Storm 程序可以在 Bolts 上定义 Java 对象来保存状态,与外部数据库和键/值存储系统进行交互,但是出现故障的时候,系统并不能提供状态的正确性保证,可能退回到 At-Least-Once 语义(数据重复),或 At-Most-Once 语义(数据丢失)。这种缺乏准确性保证,再加上无法处理大数据流(高吞吐量),使得必须使用像 Lambda 这样的混合解决方案。Flink 代表了新一代的流处理系统,并保证了状态的正确性,使得有状态的应用变得更加容易实现。在 Flink 程序中,你可以使用如下方式定义状态:

  • 使用 Flink 的窗口转换操作,你可以定义基于事件时间或处理时间的时间窗口,计数窗口以及自定义窗口。请参阅这里了解 Flink 窗口的简短介绍。
  • 使用 Checkpoint 接口,你可以注册任何类型的 Java/Scala 对象(例如,HashMap),以确保在失败后能正确恢复。
  • 使用 key/value 状态接口,你可以使用集群上通过键分区的状态。

状态在哪里存储?首先,所有上述形式的状态都存储在 Flink 可配置的 状态后端中。目前(注:发表此文时为2016年,现在有三种可选的状态后端),Flink 将状态存储在内存中,并将状态备份到文件系统中(例如,HDFS)。我们正在积极努力提供其他的状态后端和备份选项。例如,我们最近贡献了一个基于 RocksDB 的状态后端,而且我们正在开发一个使用 Flink 管理内存的状态后端,如果需要的话,可以从内存溢出到磁盘上。根据我们的经验,流处理应用程序,特别是有状态的流处理应用程序比批处理作业更难操作。批处理作业可以在一晚上运行完,如果结果不符合要求或者作业运行失败,可以重新运行。但是,流式作业 7*24 小时不间断运行,应用程序通常面向用户,因此不能随便地停止和重新运行。Flink 线上用户有必要担心在作业升级(应用程序代码和Flink本身),出现故障以及应用程序和集群维护的过程中作业的表现情况。

2. 保存点:版本化状态

在 Flink 中,我们引入了保存点功能,可以解决上述问题以及未来更多问题。保存点可以从正在运行的 Flink 作业上获取,实质上是在一个时间点上定义可以从外部访问的作业的快照。包含当前正在从数据源读取数据的偏移量,以在这个偏移量处的程序状态。在内部,保存点只是 Flink 普通的定期检查点,以保证在发生故障时的正确性。主要区别是:

  • 保存点可以手动触发。
  • 保存点永不过期,除非用户手动进行处理。

通过命令行使用指定 JobID 获取正在运行作业的保存点,只需运行:

flink savepoint JobID

上述会返回存储保存点的路径(默认配置文件系统,例如本地,HDFS,S3等)。要从保存点恢复作业,只需运行如下即可:

flink run -s pathToSavePoint jobJar

使用保存点,不必从头开始重新读取事件流以重新填充 Flink 作业的状态,因为你可以随时获取一致性快照并从该检查点恢复。另外,当日志保留期限有限时,定期保存状态是非常有用的,因为日志不能从头开始读取。另一种理解保存点的方式是在定义好的时间点保存应用程序状态的版本,类似于使用 git 等版本控制系统来保存应用程序的版本。最简单的例子是在修改应用程序代码的同时以一定时间间隔获取快照:

更重要的是,你可以从多个保存点分支出来,创建一个应用程序版本树:

这里,时间 t1 和 t2 分别在正在运行的作业 v0 上生成两个保存点,创建版本 v0t1 和 v0t2。他们都可以用来恢复作业。举个例子,利用 t1 时间点的保存点,我们使用修改了的应用程序代码来恢复作业,创建 v1 作业。在时间 t3 和 t4,分别从版本 v0 和 v1 获取更多的保存点。保存点可用于解决流式作业线上各种问题:

  • 应用程序代码升级:假设你在已经运行的应用程序中发现了一个 bug,希望未来的事件能够使用修改错误后的代码来处理。通过获取作业的保存点,使用新的代码从该保存点重新启动,下游应用程序看不到任何差异。
  • Flink 版本升级:升级 Flink 本身也变得更容易,因为你可以获取正在运行数据流的保存点并使用升级后的 Flink 版本从保存点重新读取它们。
  • 维护和迁移:使用保存点,可以轻松”暂停和恢复”应用程序。这对于集群维护以及将作业迁移到新集群尤其有用。另外,这对开发,测试和调试应用程序也非常有用,因为你不需要读取已经完成的事件流。
  • 假设模拟(复原):很多时候,运行一个可选的应用程序逻辑来模拟过去可控制点的”假设”场景非常有用。
  • A/B测试:通过从完全相同的保存点并行运行两个不同版本的应用程序代码,可以对A/B测试场景进行建模。

3. 结论

通过这篇文章,我们可以看到:

  • 许多有趣的流式应用案例,如时间窗口上的聚合,复杂事件处理或模式匹配,在系统内都需要有状态程序的支持。Flink 对状态的支持使这些类型的应用程序成为可能,并允许 Flink 对状态的正确性(确切地说是一种语义)做出保证。
  • 有状态流处理应用程序会面临许多操作上的问题,例如升级时的表现(应用程序代码和 Flink 本身),出现故障以及应用程序和集群维护。Flink 对保存点的支持通过允许你对应用程序代码和状态进行版本化来帮助解决这些操作问题。

目前的限制是应用程序的并发度必须与生成保存点的应用程序的并发度相匹配。如何使用保存点,请查看有关保存点如何工作的文档以及如何如何使用命令行使用它们

英译对照

  • 状态: state
  • 状态后端: state backend
  • 偏移量: offset

原文:How Apache Flink™ Enables New Streaming Applications, Part 2

赏几毛白!