Iceberg 在基于 Flink 的流式数据入库场景中的应用

ByteEcho
• 阅读 1271

本文以流式数据入库的场景为基础,介绍引入 Iceberg 作为落地格式和嵌入 Flink sink 的收益,并分析了当前可实现的框架及要点。

应用场景

流式数据入库,是大数据和数据湖的典型应用场景。上游的流式数据,如日志,或增量修改,通过数据总线,经过必要的处理后,汇聚并存储于数据湖,供下游的应用(如报表或者商业智能分析)使用。

Iceberg 在基于 Flink 的流式数据入库场景中的应用

上述的应用场景通常有如下的痛点,需要整个流程不断的优化:

  • 支持流式数据写入,并保证端到端的不重不丢(即 exactly-once);
  • 尽量减少中间环节,能支持更实时(甚至是 T+0)的读取或导出,给下游提供更实时更准确的基础数据;
  • 支持 ACID,避免脏读等错误发生;
  • 支持修改已落地的数据,虽然大数据和数据湖长于处理静态的或者缓慢变化的数据,即读多写少的场景,但方便的修改功能可以提升用户体验,避免用户因为极少的修改,手动更换整个数据文件,甚至是重新导出;
  • 支持修改表结构,如增加或者变更列;而且变更不要引起数据的重新组织。

引入 Iceberg 作为 Flink sink

为了解决上述痛点,我们引入了 Iceberg 作为数据落地的格式。Iceberg 支持 ACID 事务、修改和删除、独立于计算引擎、支持表结构和分区方式动态变更等特性,很好的满足我们的需求。

同时,为了支持流式数据的写入,我们引入 Flink 作为流式处理框架,并将 Iceberg 作为 Flink sink。

下文主要介绍 Flink Iceberg sink 的实现框架和要点。但在这之前,需要先介绍一些实现中用到的 Flink 基本概念。

Flink 基本概念

从 Flink 的角度如何理解"流"和"批"

Iceberg 在基于 Flink 的流式数据入库场景中的应用

Flink 使用 DataFrame API 来统一的处理流和批数据。

Stream, Transformation 和 Operator

一个 Flink 程序由 stream 和 transformation 组成:

  • Stream: Transformation 之间的中间结果数据;
  • Transformation:对(一个或多个)输入 stream 进行操作,输出(一个或多个)结果 stream。

当 Flink 程序执行时,其被映射成 Streaming Dataflow,由如下的部分组成:

  • Source (operator):接收外部输入给 Flink;
  • Transformation (operator):中间对 stream 做的任何操作;
  • Sink (operator):Flink 输出给外部。

下图为 Flink 官网的示例,展示了一个以 Kafka 作为输入 Source,经过中间两个 transformation,最终通过 sink 输出到 Flink 之外的过程。

Iceberg 在基于 Flink 的流式数据入库场景中的应用

State, Checkpoint and Snapshot

Flink 依靠 checkpoint 和基于 snapshot 的恢复机制,保证程序 state 的一致性,实现容错。

Checkpoint 是对分布式的数据流,以及所有 operator 的 state,打 snapshot 的过程。

■ State

一个 operator 的 state,即它包含的所有用于恢复当前状态的信息,可分为两类:

  • 系统 state:如 operator 中对数据的缓存。
  • 用户自定义 state:和用户逻辑相关,可以利用 Flink 提供的 managed state,如 ValueState、ListState,来存储。

State 的存储位置,可以分为:

  • Local:内存,或者本地磁盘
  • State backend:远端的持久化存储,如 HDFS。

如下图所示:

Iceberg 在基于 Flink 的流式数据入库场景中的应用

■ Checkpoint

Flink 做 checkpoint 的过程如下:

  1. Checkpoint coordinator 首先发送 barrier 给 source。
  2. Source 做 snapshot,完成后向 coordinator 确认。
  3. Source 向下游发送 barrier。
  4. 下游 operator 收到所有上游的 barrier 后,做 snapshot,完成后向 coordinator 确认。
  5. 继续往下游发送 barrier,直到 sink。
  6. Sink 通知 coordinator 自己完成 checkpoint。
  7. Coordinator 确认本周期 snapshot 做完。

如下图所示:

Iceberg 在基于 Flink 的流式数据入库场景中的应用

■ Barrier

Barrier 是 Flink 做分布式 snapshot 的重要概念。它作为一个系统标记,被插入到数据流中,随真实数据一起,按照数据流的方向,从上游向下游传递。

由于每个 barrier 唯一对应 checkpoint id,所以数据流中的 record 实际被 barrier 分组,如下图所示,barrier n 和 barrier n-1 之间的 record,属于 checkpoint n。

Iceberg 在基于 Flink 的流式数据入库场景中的应用

Barrier 的作用是在分布式的数据流中,将 operator 的多个输入流按照 checkpoint对齐(align),如下图所示:

Iceberg 在基于 Flink 的流式数据入库场景中的应用

Flink Iceberg sink

了解了上述 Flink 的基本概念,这些概念又是如何被应用和映射到 Flink Iceberg sink 当中的呢?

总体框架

Iceberg 在基于 Flink 的流式数据入库场景中的应用

如图,Flink Iceberg sink 有两个主要模块和两个辅助模块组成:

Iceberg 在基于 Flink 的流式数据入库场景中的应用

实现要点

■ Writer

  1. 在当前的实现中,Java 的 Map 作为每条记录,输入给 writer。内部逻辑先将其转化为作为中间格式的 Avro IndexedRecord,而后通过 Iceberg 里的 Parquet 相关 API,累积的写入 DataFile。
  2. 使用 Avro 作为中间格式是一个临时方案,为简化适配,并最大限度的利用现有逻辑。但长期来看,使用中间格式会影响处理效率,社区也在试图通过 ISSUE-870 来去掉 Avro,进而使用 Iceberg 内建的数据类型作为输入,同时也需要加入一个到 Flink 内建数据类型的转换器。
  3. 在做 checkpoint 的过程中,发送 writer 自己的 barrier 到下游的 committer 之前,关闭单个 Parquet 文件,构建 DataFile,并发送 DataFile 的信息给下游。

■ Committer

  1. 全局唯一的 Committer 在收到上游所有 writer 的 barrier 以后,将收到的 DataFile 的信息填入 manifest file,并使用 ListState 把 manifest file 作为用户自定义的 state,保存于 snapshot 中。
  2. 当 checkpoint 完成以后,通过 merge append 将 manifest file 提交给 Iceberg。Iceberg 内部通过后续的一系列操作完成 commit。最终让新加入的数据对其他的读任务可见。

试用 Flink Iceberg sink

社区上https://github.com/apache/incubator-iceberg/pull/856提供了可以试用的原型代码。下载该 patch 放入 master 分支,编译并构建即可。如下的程序展示了如何将该 sink 嵌入到 Flink 数据流中:

// Configurate catalog
org.apache.hadoop.conf.Configuration hadoopConf =
    new org.apache.hadoop.conf.Configuration();
hadoopConf.set(
    org.apache.hadoop.hive.conf.HiveConf.ConfVars.METASTOREURIS.varname,
    META_STORE_URIS);
hadoopConf.set(
    org.apache.hadoop.hive.conf.HiveConf.ConfVars.METASTOREWAREHOUSE.varname,
    META_STORE_WAREHOUSE);
 
Catalog icebergCatalog = new HiveCatalog(hadoopConf);
 
// Create Iceberg table
Schema schema = new Schema(
   ...
);
PartitionSpec partitionSpec = builderFor(schema)...
TableIdentifier tableIdentifier =
    TableIdentifier.of(DATABASE_NAME, TABLE_NAME);
// If needed, check the existence of table by loadTable() and drop it
// before creating it
icebergCatalog.createTable(tableIdentifier, schema, partitionSpec);
 
// Obtain an execution environment
StreamExecutionEnvironment env = 
    StreamExecutionEnvironment.getExecutionEnvironment();
 
// Enable checkpointing
env.enableCheckpointing(...);
  
// Add Source
DataStream<Map<String, Object>> dataStream =
    env.addSource(source, typeInformation);
 
// Configure Ieberg sink
Configuration conf = new Configuration();
conf.setString(
    org.apache.hadoop.hive.conf.HiveConf.ConfVars.METASTOREWAREHOUSE.varname,
    META_STORE_URIS);
conf.setString(IcebergConnectorConstant.DATABASE, DATABASE_NAME);
conf.setString(IcebergConnectorConstant.TABLE, TABLE_NAME);
 
// Append Iceberg sink to data stream
IcebergSinkAppender<Map<String, Object>> appender =
   new IcebergSinkAppender<Map<String, Object>>(conf, "test")
       .withSerializer(MapAvroSerializer.getInstance())
       .withWriterParallelism(1);
appender.append(dataStream);
 
// Trigger the execution
env.execute("Sink Test");

后续规划

Flink Iceberg sink 有很多需要完善的地方,例如:上文中提到的去掉 Avro 作为中间格式;以及在各种失败的情况下是否仍能保证端到端的 exactly-once;按固定时长做 checkpoint,在高低峰时生成不同大小的 DataFile,是否对后续读不友好等。这些问题都在我们的后续规划中,也会全数贡献给社区。

参考资料:

[1] Iceberg 官网:
https://iceberg.apache.org/
[2] Flink 1.10文 档:
https://ci.apache.org/projects/flink/flink-docs-release-1.10/
[3] Neflix 提供的 Flink Iceberg connector 原型:
https://github.com/Netflix-Skunkworks/nfflink-connector-iceberg
[4] Flink Iceberg sink 设计文档:
https://docs.google.com/document/d/19M-sP6FlTVm7BV7MM4Om1n_MVo1xCy7GyDl_9ZAjVNQ/edit?usp=sharing
[5] Flink 容错机制(checkpoint) :
https://www.cnblogs.com/starzy/p/11439988.html

# 社区活动推荐 #

普惠全球开发者,这一次,格外与众不同!首个 Apache 顶级项目在线会议 Flink Forward 全球直播中文精华版来啦,聚焦 Alibaba、Google、AWS、Uber、Netflix、新浪微博等海内外一线厂商,经典 Flink 应用场景,最新功能、未来规划一览无余。点击下方链接可了解更多大会详情:https://developer.aliyun.com/live/2594?spm=a2c6h.14242504.J_6074706160.2.3fca361f4cYyQx

点赞
收藏
评论区
推荐文章
Prodan Labs Prodan Labs
4年前
IoT基础架构的演进 — Kuiper
EMQXKuiper是映云科技开源的轻量级物联网边缘数据分析和流式处理软件,Kuiper设计的一个主要目标就是将在云端运行的实时流式计算框架(如ApacheSpark,ApacheStorm和ApacheFlink等)迁移到边缘端。Kuiper参考了云端流式处理项目的架构与实现,结合边缘流式数据处理的特点,采用了编写基于源(Sou
Stella981 Stella981
3年前
Flink SQL 实战:双流 join 场景应用
本文主要介绍在流式场景中join的实战。大家都知道在使用SQL进行数据分析的过程中,join是经常要使用的操作。在离线场景中,join的数据集是有边界的,可以缓存数据有边界的数据集进行查询,有NestedLoop/HashJoin/SortMergeJoin等多表join;而在实时场景中,join两侧的数据都是无边界的数据流,所以缓
Stella981 Stella981
3年前
Knative 实战:基于阿里云 Kafka 实现消息推送
在Knative中已经提供了对Kafka事件源的支持,那么如何在阿里云上基于Kafka实现消息推送,本文给大家解锁这一新的姿势。背景消息队列forApacheKafka是阿里云提供的分布式、高吞吐、可扩展的消息队列服务。消息队列forApacheKafka广泛用于日志收集、监控数据聚合、流式数据处理、在线和离线分析等
Stella981 Stella981
3年前
Flink 助力美团数仓增量生产
简介:本文由美团研究员、实时计算负责人鞠大升分享,主要介绍Flink助力美团数仓增量生产的应用实践。内容包括:1、数仓增量生产;2、流式数据集成;3、流式数据处理;4、流式OLAP应用;5、未来规划。一、数仓增量生产1.美团数仓架构先介绍一下美团数仓的架构以及增量生产。如下图所示,这是美团数仓的简单架构,我
Stella981 Stella981
3年前
Kafka连接器深度解读之错误处理和死信队列
Kafka连接器是Kafka的一部分,是在Kafka和其它技术之间构建流式管道的一个强有力的框架。它可用于将数据从多个地方(包括数据库、消息队列和文本文件)流式注入到Kafka,以及从Kafka将数据流式传输到目标端(如文档存储、NoSQL、数据库、对象存储等)中。现实世界并不完美,出错是难免的,因此在出错时Kafka的管道能尽可能优雅地处理是最好的。一
Stella981 Stella981
3年前
Kafka连接器深度解读之JDBC源连接器
在现实业务中,Kafka经常会遇到的一个集成场景就是,从数据库获取数据,因为关系数据库是一个非常丰富的事件源。数据库中的现有数据以及对该数据的任何更改都可以流式传输到Kafka主题中,在这里这些事件可用于驱动应用,也可以流式传输到其它数据存储(比如搜索引擎或者缓存)用于分析等。实现这个需求有很多种做法,但是在本文中,会聚焦其中的一个解决方案,即Kafka
Stella981 Stella981
3年前
Flink集成iceberg数据湖之合并小文件
背景使用流式数据入湖开启压缩程序快照过期删除无用文件数据查询遇到的坑最大并发度问题文件被重复压缩扫描任务读取文件问题不读取大文
数据驱动测试-从方法探研到最佳实践
在自动化测试实践中,测试数据是制造测试场景的必要条件,本文主要讲述了在沟通自动化框架如何分层,数据如何存储,以及基于单元测试pytest下如何执行。并通过实践案例分享,提供数据驱动测试的具体落地方案。
Flink测试利器之DataGen初探 | 京东云技术团队
什么是FlinksqlFlinkSQL是基于ApacheCalcite的SQL解析器和优化器构建的,支持ANSISQL标准,允许使用标准的SQL语句来处理流式和批处理数据。通过FlinkSQL,可以以声明式的方式描述数据处理逻辑,而无需编写显式的代码。使用
元宇宙华锐 元宇宙华锐
3个月前
场景可视化与数据编辑器:构建数据应用情境
场景可视化是将数据与特定的应用场景相结合,借助数据编辑器对数据进行灵活处理和调整,通过模拟和展示真实场景,使企业能够更直观地理解数据在实际业务中的应用和影响,为企业的决策和运营提供有力支持。它能够将抽象的数据转化为具体的、可感知的场景,帮助企业更好地把握业