ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Flink+Iceberg流批一体数据湖实战:选型、调优与运维

Flink+Iceberg流批一体数据湖实战:选型、调优与运维 1. 流批一体场景下的数据湖选型思考1.1 从Lambda架构到流批一体的演进逻辑做数据平台的人都有一个共同的痛同一份业务逻辑离线链路写一遍实时链路再写一遍两套代码、两套运维、两套口径。早期大家用Lambda架构扛着离线跑T1出报表实时链路单独维护一套指标时间一长两条链路的数据对不上业务方找过来排查成本极高。后来大家开始琢磨Kappa架构把所有计算都收敛到流上但流处理引擎对大规模历史数据的回溯分析、批量修正能力又不够成熟于是“流批一体”这个概念被反复提起。流批一体的核心诉求其实很朴素一套SQL、一份元数据、一份存储既能处理无界的实时流也能处理有界的历史批。要做到这一点计算引擎和存储层必须解耦存储层要能同时承接高频小批量的流式写入和大吞吐的批量读取。这就是数据湖技术在这个阶段被推到台前的原因。在众多数据湖方案里Iceberg、Hudi、Delta Lake是绕不开的三个选项。我们当时做选型的时候核心考量维度有几个schema演进能力、分区裁剪效率、小文件治理机制、与计算引擎的耦合程度、以及社区活跃度。Hudi的upsert能力很强适合高频更新的场景但它和Spark的绑定相对深一些Delta Lake在Databricks生态里体验很好但开源版本在Flink集成上的成熟度当时还不够。Iceberg的设计哲学是“表格式规范优先”它把表的元数据管理抽象得非常干净计算引擎只需要按照规范去读写就行这种解耦特性让它天然适合做流批一体的存储底座。1.2 为什么是Flink加Iceberg的组合Flink在流处理上的优势不用多说真正的流式语义、事件时间处理、精确一次的状态一致性这些都是它的看家本领。但Flink早期在批处理上的表现只能算“能用”直到1.12之后批流一体的SQL层逐渐成熟Flink才真正具备了统一计算引擎的底气。Iceberg这边它的核心抽象是表格式Table Format不是存储引擎也不是文件格式。它定义了一套元数据规范包括snapshot、manifest、manifest list这些概念把“哪些文件属于这张表”“这些文件在某个时间点的状态是什么”管理得清清楚楚。这种设计带来的好处是写入方和读取方完全解耦写入方只管生成新的snapshot读取方根据snapshot做快照隔离读取互不干扰。Flink加Iceberg的组合本质上是用Flink做统一的写入和计算入口用Iceberg做统一的存储和元数据管理。实时链路通过Flink流作业持续写入Iceberg表离线链路通过Flink批作业或者Spark读取同一张Iceberg表做回溯分析。两条链路看到的是同一份数据、同一套schema口径天然一致。在同程艺龙的实践场景里我们面对的是旅游行业典型的数据特征订单、用户行为、库存、价格这些核心业务数据既有实时监控的需求又有T1甚至更长时间跨度的历史分析需求。比如机票价格监控需要秒级感知价格变化并触发预警同时又要做过去一年的价格趋势分析支撑收益管理策略。这种场景下流批一体不是锦上添花而是刚需。1.3 落地前的核心挑战盘点理想很丰满落地的时候问题一个接一个。我们在正式铺开之前梳理了几个必须解决的核心挑战第一写入性能与小文件问题。Flink流作业是持续写入的默认的checkpoint间隔如果设得比较短每次checkpoint都会生成一批新文件几个小时下来Iceberg表里全是小文件元数据膨胀得厉害读取性能急剧下降。这个问题不解决实时链路根本跑不稳。第二schema演进的兼容性。业务需求变化快加字段、改字段类型是家常便饭。Iceberg虽然支持schema演进但Flink写入端和下游读取端对schema变更的感知时机、兼容策略需要仔细设计否则容易出现写入端已经加了字段、读取端还没更新导致报错的情况。第三Exactly-Once语义的保障。Flink的checkpoint机制和Iceberg的snapshot提交需要精确对齐。如果checkpoint成功了但Iceberg提交失败或者反过来都会导致数据不一致。这中间涉及到两阶段提交协议的设计需要深入理解Flink的Sink API和Iceberg的Transaction API。第四查询性能的优化。Iceberg表的数据文件是列式存储的Parquet理论上查询性能不错但如果分区策略设计不合理、manifest文件过多、或者没有做好数据聚簇查询时扫描大量无关文件性能会大打折扣。这四个问题贯穿了我们整个实践过程后面会逐一展开讲具体的解决思路和实操细节。2. 核心机制拆解与关键参数设计2.1 Iceberg表格式的核心概念快速扫盲在深入实操之前有必要把Iceberg的几个核心概念用大白话讲清楚不然后面的参数调优和问题排查会一头雾水。Snapshot快照每次对Iceberg表的写入操作都会生成一个新的snapshot相当于给表拍了一张照片记录了这一刻表里有哪些数据文件。读取的时候指定snapshot ID就能读到那个时间点的数据状态。这个机制天然支持时间旅行查询做数据回溯和审计非常方便。Manifest清单文件snapshot本身不直接记录数据文件列表而是通过manifest文件来组织。一个manifest里记录了一批数据文件的路径、分区信息、列级统计信息比如min/max值。查询的时候Iceberg先读manifest list再根据查询条件过滤manifest最后定位到具体的数据文件。这个层级结构是Iceberg查询性能的关键。Manifest List清单列表记录当前snapshot包含哪些manifest文件以及每个manifest的分区范围等元信息。查询引擎通过manifest list做第一层裁剪快速跳过不相关的manifest。Catalog目录管理Iceberg表的元数据入口负责表的创建、删除、元数据更新等操作。常见的Catalog实现有HiveCatalog、HadoopCatalog、RESTCatalog等。同程艺龙这边用的是HiveCatalog主要是考虑到和现有Hive数仓的兼容性以及元数据管理的统一性。Partition Spec分区规范定义表的分区策略支持隐藏分区、分区演进等高级特性。和Hive分区不同Iceberg的分区是可以演进的改分区策略不需要重写历史数据。理解这几个概念之后再看Flink写入Iceberg的流程就清晰了Flink作业在checkpoint时把当前批次的数据文件信息打包成一个新的snapshot通过Catalog提交到Iceberg表。下游读取时根据最新的snapshot或者指定的历史snapshot来读取数据。2.2 Flink写入Iceberg的两种模式对比Flink集成Iceberg主要有两种写入模式append模式和upsert模式。这两种模式的选择直接决定了后续的数据处理逻辑和运维复杂度。Append模式就是纯追加写入每条数据进来直接写到新的数据文件里不做任何去重或更新。这种模式实现简单、写入性能高适合日志类、事件类等只追加不更新的场景。但缺点是如果上游数据有重复或者需要更新Iceberg表里会出现重复数据需要下游做去重处理。Upsert模式支持根据主键更新数据。Flink作业在写入时会根据主键判断这条数据是新增还是更新然后生成对应的数据文件和删除文件。Iceberg通过equality delete file和position delete file来实现行级更新。这种模式适合订单、用户画像等需要频繁更新的场景但写入性能会比append模式低一些因为需要维护删除文件而且读取时要做merge操作。我们当时的选型策略是ODS层用append模式DWD和DWS层用upsert模式。ODS层接的是原始日志和binlog数据只追加不更新用append模式写入性能最优。DWD层做清洗和维度关联后需要根据主键做更新用upsert模式保证数据唯一性。DWS层做聚合指标也是upsert模式按维度主键更新指标值。这里有个实操心得upsert模式的主键选择非常关键。主键字段最好是分区字段的子集或者有强关联这样Iceberg在写入时能更好地做文件裁剪和合并。如果主键和分区完全无关写入时会产生大量跨分区的删除文件性能会明显下降。2.3 Checkpoint间隔与文件大小的平衡计算这是流式写入Iceberg最核心的参数调优问题。Flink的checkpoint间隔决定了多久生成一批新文件而Iceberg的数据文件大小又直接影响查询性能。两者需要配合调整。先看默认情况假设checkpoint间隔是1分钟每个checkpoint周期写入的数据量是500MB那么每个checkpoint会生成大约5到10个Parquet文件取决于并行度。一天下来就是1440个checkpoint产生上万个文件。每个文件平均50MB到100MB虽然不算特别小但manifest文件的数量会膨胀得很快查询时读取manifest的开销就会变得不可忽略。我们的调优思路是让每个checkpoint周期写入的数据量尽量接近一个合理的数据文件大小。Iceberg社区推荐的Parquet文件大小是128MB到512MB之间我们取中间值256MB作为目标。然后反推checkpoint间隔。具体计算过程假设Flink作业的写入吞吐是每秒10MB那么要攒够256MB需要大约25.6秒。考虑到checkpoint本身有开销以及数据写入的波动我们把checkpoint间隔设为30秒。这样每个checkpoint周期大约写入300MB数据生成1到2个Parquet文件文件大小在150MB到300MB之间比较合理。但这里有个矛盾checkpoint间隔太长故障恢复时需要重放的数据就越多恢复时间越长。30秒的间隔意味着最坏情况下要重放30秒的数据对于大部分场景是可以接受的。如果业务对恢复时间要求极高可以适当缩短checkpoint间隔同时通过调整Flink的写入并行度来控制每个并行子任务写入的数据量让每个子任务在每个checkpoint周期写入的数据量接近目标文件大小。还有一个参数是写入并行度。并行度越高每个子任务分到的数据越少生成的文件越多但每个文件越小。我们的经验是写入并行度不要超过Kafka分区数否则会有子任务空闲。同时如果发现生成的文件普遍偏小可以适当降低并行度让每个子任务攒更多数据再写。2.4 Schema演进策略与兼容性设计Schema演进是数据湖相比传统数仓的一大优势但用不好也会踩坑。Iceberg支持的操作包括新增列、删除列、重命名列、修改列类型有限制、调整列顺序等。Flink写入端和下游读取端对schema变更的感知是异步的需要设计好兼容策略。我们的做法是所有schema变更走统一的元数据管理流程。新增字段时先在Iceberg表上执行ALTER TABLE ADD COLUMN然后更新Flink写入作业的schema定义最后通知下游读取方更新。这个顺序不能乱否则写入端会报字段不存在的错误。对于列类型修改Iceberg支持int到long、float到double这种安全的类型提升但不支持反向转换。我们规定只允许类型提升不允许类型收窄。如果业务确实需要收窄类型走新建表加数据迁移的流程不在原表上直接改。还有一个容易忽略的点Flink SQL中定义的schema和Iceberg表的schema要定期做一致性校验。我们遇到过Flink作业的schema定义和Iceberg表实际schema不一致的情况原因是有人直接在Iceberg表上加了字段但忘了更新Flink作业。后来我们加了一个定时校验任务每天对比Flink作业的schema和Iceberg表的schema不一致就告警。2.5 小文件治理的自动与手动策略小文件是流式写入数据湖的顽疾。Iceberg提供了几种治理手段我们根据数据的重要程度和查询频率采用了分层策略。对于高优表核心业务表查询频繁我们开启了Iceberg的自动合并功能。在Flink写入端配置write.distribution-mode为hash让相同分区键的数据尽量写到同一个文件里。同时在Iceberg侧配置定期执行rewrite_data_files和rewrite_manifests操作把小于目标大小的文件合并成大文件清理过期的manifest。对于普通表日志类、归档类我们采用手动合并策略每天凌晨低峰期跑一次合并任务。合并任务的参数需要根据表的数据量和查询模式来调。比如target-file-size-bytes设为256MBmin-file-size-bytes设为64MB小于64MB的文件会被合并。max-file-size-bytes设为512MB超过这个大小的文件不再合并。对于极冷表很少查询的历史归档我们不做合并只做定期的snapshot过期清理删除不再需要的旧snapshot和对应的数据文件释放存储空间。这里有个实操细节合并操作本身也会消耗计算资源。如果合并任务和实时写入任务跑在同一个集群上可能会互相影响。我们的做法是把合并任务调度到独立的批处理集群上通过读取Iceberg表的snapshot来做合并不影响实时写入链路。3. 完整实操流程与核心环节实现3.1 环境准备与依赖版本锁定在开始集成之前版本兼容性是第一个要确认的事情。Flink和Iceberg的版本对应关系比较敏感版本不匹配会导致各种奇怪的报错。我们当时用的组合是Flink 1.13.2 Iceberg 0.12.0 Hadoop 3.2.1。这个组合是经过社区验证的稳定版本。依赖方面需要在Flink的lib目录下放入以下jar包# Iceberg核心包 iceberg-flink-runtime-0.12.0.jar # Hive Catalog依赖如果使用HiveCatalog iceberg-hive-runtime-0.12.0.jar # Hadoop依赖如果Flink集群没有自带 hadoop-common-3.2.1.jar hadoop-hdfs-client-3.2.1.jar注意iceberg-flink-runtime是一个shaded包已经包含了大部分依赖不要重复引入Iceberg的其他模块否则会出现类冲突。Hive Metastore的配置需要在Flink的sql-client-defaults.yaml或者代码中指定catalogs: - name: iceberg_catalog type: iceberg catalog-type: hive uri: thrift://your-hive-metastore:9083 warehouse: hdfs://your-namenode:8020/warehouse/iceberg3.2 Flink SQL建表与参数配置详解建表是第一步也是参数配置最集中的地方。下面是一个典型的upsert模式Iceberg表的建表语句CREATE TABLE iceberg_catalog.db.order_detail ( order_id BIGINT, user_id BIGINT, product_id BIGINT, order_amount DECIMAL(10,2), order_status STRING, create_time TIMESTAMP(3), update_time TIMESTAMP(3), dt STRING, PRIMARY KEY (order_id) NOT ENFORCED ) PARTITIONED BY (dt) WITH ( connector iceberg, catalog-type hive, catalog-database db, catalog-table order_detail, write.distribution-mode hash, write.upsert.enabled true, write.metadata.delete-after-commit.enabled true, write.metadata.previous-versions-max 10, write.format.default parquet, write.parquet.compression-codec zstd, write.target-file-size-bytes 268435456 );几个关键参数的解释write.distribution-mode设为hash表示按照分区键做hash分发相同分区的数据会写到同一个Flink子任务减少小文件数量。如果设为none数据随机分发小文件会更多。write.upsert.enabled设为true开启upsert模式。注意这个参数在Flink SQL中是通过主键定义隐式开启的如果定义了PRIMARY KEYIceberg会自动使用upsert模式。write.metadata.delete-after-commit.enabled设为true每次提交后自动删除过期的元数据文件防止元数据无限膨胀。write.metadata.previous-versions-max设为10保留最近10个版本的元数据支持时间旅行查询最近10个snapshot。write.target-file-size-bytes设为256MB这是目标文件大小Iceberg在写入时会尽量让每个文件接近这个大小。write.parquet.compression-codec设为zstd压缩比和性能比较均衡。如果对存储成本敏感可以用zstd如果对读取性能要求极高可以用snappy。3.3 实时写入作业的提交与调优建完表之后提交Flink流作业写入数据。作业的并行度设置需要根据数据量和集群资源来定。我们的经验值是每个并行子任务的写入吞吐控制在5MB/s到10MB/s之间。如果总吞吐是100MB/s那么并行度设为10到20比较合适。Checkpoint的配置在Flink作业的配置文件中execution.checkpointing.interval: 30s execution.checkpointing.mode: EXACTLY_ONCE execution.checkpointing.timeout: 10min execution.checkpointing.min-pause: 10s execution.checkpointing.max-concurrent-checkpoints: 1 state.backend: rocksdb state.backend.incremental: trueexecution.checkpointing.interval设为30秒和前面计算的文件大小目标对应。execution.checkpointing.timeout设为10分钟给Iceberg提交snapshot留足时间。如果超时时间设得太短Iceberg提交还没完成checkpoint就超时了会导致作业频繁重启。execution.checkpointing.max-concurrent-checkpoints设为1禁止并发checkpoint。Iceberg的snapshot提交需要串行执行并发checkpoint会导致提交冲突。state.backend用RocksDB开启增量checkpoint减少状态存储的开销。3.4 离线读取与时间旅行查询实操Iceberg表建好、数据写进去之后离线读取就很简单了。用Flink SQL或者Spark SQL都可以直接查-- Flink SQL查询最新数据 SELECT * FROM iceberg_catalog.db.order_detail WHERE dt 2024-01-15; -- Flink SQL时间旅行查询查询某个snapshot的数据 SELECT * FROM iceberg_catalog.db.order_detail /* OPTIONS(snapshot-id1234567890) */ WHERE dt 2024-01-15;时间旅行查询在排查数据问题时特别有用。比如业务方反馈某天的数据有问题我们可以通过时间旅行查询到那天的snapshot对比前后几个snapshot的数据变化快速定位是哪次写入引入了异常数据。还有一个实用技巧通过Iceberg的元数据表查询表的文件分布情况。Iceberg提供了snapshots、manifests、files等元数据表可以直接用SQL查询-- 查询表的所有snapshot SELECT * FROM iceberg_catalog.db.order_detail.snapshots; -- 查询某个snapshot的文件列表 SELECT * FROM iceberg_catalog.db.order_detail.files WHERE snapshot_id 1234567890;这些元数据表在排查小文件问题、分析数据分布时非常有用。3.5 监控指标与告警配置生产环境跑起来之后监控是必不可少的。我们主要监控以下几类指标Flink作业层面checkpoint成功率、checkpoint耗时、背压情况、吞吐量。这些指标通过Flink的Metric系统采集接入Prometheus和Grafana做可视化。Iceberg表层面snapshot数量、manifest文件数量、数据文件数量、平均文件大小、元数据文件大小。这些指标通过定时查询Iceberg的元数据表来采集。存储层面HDFS的存储使用量、文件数量增长趋势。如果发现文件数量增长过快说明小文件问题严重需要触发合并任务。告警规则方面我们设置了几个关键阈值checkpoint连续失败3次告警、单次checkpoint耗时超过5分钟告警、Iceberg表的小文件数量小于64MB的文件超过1000个告警、元数据文件大小超过1GB告警。4. 常见问题与排查技巧实录4.1 写入性能突然下降的排查路径这是我们在生产环境遇到最多的问题。表现是Flink作业的吞吐量突然下降checkpoint耗时变长背压升高。排查路径如下第一步看Flink的背压情况。通过Flink Web UI或者Metric系统查看各个算子的背压状态。如果背压出现在Iceberg Sink算子说明写入Iceberg这一侧有问题。第二步看Iceberg表的文件数量。如果文件数量在短时间内快速增长说明小文件问题严重了。大量小文件会导致Iceberg在提交snapshot时需要处理大量的manifest文件提交耗时变长进而拖慢整个checkpoint。第三步看HDFS的写入延迟。如果HDFS的写入延迟升高也会导致Iceberg写入变慢。这种情况通常是HDFS集群本身的问题比如NameNode负载过高、DataNode磁盘IO瓶颈等。第四步看Iceberg的提交日志。Iceberg在提交snapshot时会打印详细的日志包括生成了多少个数据文件、多少个删除文件、提交耗时等。通过日志可以定位到具体的瓶颈。我们遇到过一次典型的性能下降原因是某个Flink作业的并行度从10调到了20导致每个子任务写入的数据量减半生成的文件数量翻倍小文件问题加剧最终拖慢了整个写入链路。解决办法是把并行度调回10同时开启自动合并。4.2 Checkpoint失败与数据不一致的修复Checkpoint失败是流作业的常见问题但和Iceberg集成后失败的原因和修复方式有一些特殊性。场景一Iceberg提交超时导致checkpoint失败。表现是checkpoint超时日志里能看到Iceberg的commit操作还在执行。这种情况通常是Iceberg提交的数据量太大或者HDFS写入太慢。解决办法是缩短checkpoint间隔减少每次提交的数据量或者增加checkpoint的超时时间。场景二Iceberg提交成功但checkpoint失败。这种情况比较棘手因为Iceberg已经提交了snapshot但Flink认为checkpoint失败了会回滚到上一个checkpoint。这会导致Iceberg表里多了一批“孤儿数据”虽然不影响读取因为读取是基于snapshot的但会浪费存储空间。解决办法是配置Iceberg的write.metadata.delete-after-commit.enabled为true定期清理孤儿snapshot。场景三Checkpoint成功但Iceberg提交失败。这种情况会导致数据丢失因为Flink认为checkpoint成功了不会重放这部分数据但Iceberg里没有写入。这种情况通常是由于Iceberg的Catalog不可用或者权限问题导致的。解决办法是配置重试机制Iceberg的commit操作支持重试可以在Flink的Sink配置中设置重试次数和重试间隔。实操心得我们后来在Flink作业里加了一个“提交确认”机制每次checkpoint完成后额外做一次Iceberg的snapshot校验确认最新的snapshot ID和checkpoint的ID对应。如果不一致触发告警并暂停作业人工介入排查。4.3 小文件过多的紧急处理方案小文件问题如果已经积累到影响查询性能的程度需要紧急处理。我们的处理流程是第一步评估影响范围。查询Iceberg的files元数据表统计小于64MB的文件数量和总大小评估合并需要的时间和资源。第二步暂停实时写入。如果小文件问题非常严重合并任务和实时写入同时跑会互相影响建议先暂停实时写入作业等合并完成后再恢复。暂停时间根据合并的数据量来定一般几十分钟到几个小时。第三步执行合并任务。用Spark或者Flink批作业执行Iceberg的rewrite_data_files操作-- Spark SQL执行合并 CALL iceberg_catalog.system.rewrite_data_files( table db.order_detail, options map( target-file-size-bytes, 268435456, min-file-size-bytes, 67108864, max-file-size-bytes, 536870912 ) );第四步清理过期元数据。合并完成后执行rewrite_manifests和expire_snapshots清理不再需要的manifest文件和snapshot。第五步恢复实时写入。确认合并完成后恢复Flink作业并调整参数防止小文件问题再次出现。4.4 Schema变更导致作业失败的应急处理Schema变更导致的作业失败通常发生在写入端和读取端的schema不一致时。应急处理流程情况一写入端报字段不存在。原因是Iceberg表加了字段但Flink作业的schema没更新。解决办法是更新Flink作业的schema定义并重启作业。如果作业不能立即重启可以先用ALTER TABLE DROP COLUMN把新加的字段删掉等作业更新后再加回来。情况二读取端报字段类型不匹配。原因是Iceberg表的字段类型改了但读取端的schema没更新。解决办法是更新读取端的schema定义。如果读取端是BI工具或者即席查询需要通知相关方更新。情况三写入端报主键冲突。原因是upsert模式的主键定义和Iceberg表的主键定义不一致。解决办法是检查两边的定义确保完全一致。避坑技巧我们后来规定所有schema变更必须走工单流程变更前先在测试环境验证确认写入端和读取端都能正常工作后再上生产。同时在Flink作业里加了schema兼容性检查作业启动时对比Flink schema和Iceberg schema不一致直接拒绝启动。4.5 常见问题速查表问题现象可能原因排查方法解决方案写入吞吐突然下降小文件过多、HDFS延迟高查看Iceberg文件数量、HDFS监控执行合并、调整并行度Checkpoint频繁超时单次提交数据量太大查看checkpoint耗时、Iceberg提交日志缩短checkpoint间隔、增加超时时间数据重复upsert模式未生效检查主键定义、查询Iceberg表数据确认主键定义、开启upsert查询性能差分区裁剪失效、文件过大查看查询计划、文件大小分布调整分区策略、合并大文件元数据膨胀snapshot未清理查看snapshots元数据表配置自动清理、执行expire_snapshots作业启动失败schema不一致、依赖冲突查看启动日志、对比schema更新schema、检查jar包版本时间旅行查询报错snapshot已过期查看snapshots元数据表调整snapshot保留策略5. 生产环境运维经验与性能优化心得5.1 资源规划与容量评估方法Iceberg表的存储容量增长是可以预估的。核心公式是日增数据量 × 副本数 × 压缩比 × 保留天数。比如日增原始数据100GBHDFS副本数3Parquet压缩比0.3保留365天那么总存储需求大约是100GB × 3 × 0.3 × 365 ≈ 32TB。这还不包括合并操作产生的临时文件和旧snapshot占用的空间实际规划时要留出30%到50%的余量。计算资源方面Flink写入作业的资源需求主要取决于写入吞吐和checkpoint的数据量。我们的经验值是每10MB/s的写入吞吐需要大约4个CPU核心和8GB内存。这个值会根据数据复杂度、序列化开销、Iceberg提交开销等因素浮动建议在实际压测后确定。合并任务的资源需求取决于表的数据量和合并频率。每天合并一次的话合并任务的处理能力建议是日增数据量的2到3倍确保能在低峰期内完成合并。5.2 多表共用一个Flink作业的利弊分析为了节省资源我们一开始尝试把多个Iceberg表的写入放在同一个Flink作业里。好处是节省了TaskManager的资源减少了作业数量运维成本低。但坏处也很明显故障隔离性差。一张表的写入出现问题整个作业都会受影响所有表的数据写入都会延迟。资源竞争。不同表的写入吞吐不同有的表数据量大、有的表数据量小共用一个作业会导致资源分配不均数据量大的表可能抢不到足够的资源。Checkpoint相互影响。一个作业的checkpoint是全局的如果某张表的Iceberg提交特别慢会拖慢整个checkpoint影响所有表。后来我们改成了按业务域拆分作业每个业务域的核心表放在一个作业里非核心表单独跑。这样既保证了核心表的稳定性又控制了作业数量。5.3 数据回溯与重跑的正确姿势数据回溯是数据平台的刚需Iceberg在这方面提供了很好的支持。我们的回溯流程是第一步确定回溯范围。通过时间旅行查询确定要回溯到哪个snapshot以及需要重跑的时间段。第二步暂停下游消费。通知下游停止消费Iceberg表的数据避免读到中间状态的数据。第三步执行回溯写入。用Flink批作业或者Spark作业重新计算指定时间段的数据写入Iceberg表。注意写入时要指定新的分区或者使用INSERT OVERWRITE语义避免和现有数据冲突。第四步校验数据。回溯完成后对比回溯前后的数据量和关键指标确认数据正确。第五步恢复下游消费。通知下游恢复消费并告知回溯完成的时间点。实操心得回溯操作最好在低峰期执行并且提前通知相关方。我们有一次在业务高峰期做回溯导致Iceberg表的写入压力骤增影响了实时链路的性能。后来规定所有回溯操作必须提前一天报备安排在凌晨执行。5.4 从Iceberg 0.12到0.14的升级踩坑记录Iceberg社区迭代很快我们后来从0.12升级到了0.14中间踩了一些坑记录一下坑一Flink版本兼容性。Iceberg 0.14要求Flink 1.14及以上版本我们当时用的Flink 1.13.2不兼容需要先升级Flink。升级Flink又涉及到状态兼容性问题需要做状态迁移。坑二API变更。Iceberg 0.14对Flink SQL的Connector参数做了一些调整比如write.upsert.enabled参数被废弃改为通过主键定义自动识别。升级后需要修改建表语句。坑三元数据格式变更。Iceberg 0.14对元数据格式做了一些优化升级后需要执行一次元数据迁移操作。这个操作是不可逆的升级前一定要备份元数据。坑四性能表现差异。升级后发现写入性能比0.12略有下降排查后发现是0.14默认开启了更严格的元数据校验。通过调整write.metadata.compression-codec参数把元数据压缩算法从gzip改为zstd性能恢复到了升级前的水平。5.5 未来演进方向与扩展思路Iceberg和Flink的集成还在快速演进中我们也在持续关注几个方向一是Flink CDC与Iceberg的深度集成。目前我们是从Kafka接binlog数据写入Iceberg链路比较长。Flink CDC可以直接从数据库抓取变更数据写入Iceberg链路更短、延迟更低。我们正在测试这个方案初步效果不错。二是Iceberg的Row-level Delete优化。Iceberg 0.14引入了更高效的删除文件格式upsert模式的写入性能有明显提升。我们计划在下一个版本升级中验证这个特性。三是多引擎共享Iceberg表。目前我们主要是Flink写入、Flink和Spark读取。未来计划让Trino、Presto等引擎也能直接查询Iceberg表实现真正的多引擎共享。四是冷热数据分层存储。Iceberg支持自定义文件存储路径可以把冷数据放到更便宜的存储介质上热数据保留在高性能存储上。这个特性对降低存储成本很有帮助我们正在做方案设计。整体来看Flink加Iceberg的组合在同程艺龙的实践是成功的。流批一体不是一句口号而是实实在在解决了我们数据口径不一致、链路重复建设的问题。当然落地过程中遇到的坑也不少上面这些经验都是一个个问题踩出来的。如果你也在做类似的实践希望这些内容能帮你少走一些弯路。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表