摘要:本文介绍了携程商旅在准实时数仓与湖仓一体化建设中的实践。团队基于Paimon构建稳定高效的数据链路,覆盖订单宽表加工、退票提醒、广告归因等场景,并探索批流一体与 Tag 增量计算,总结了不同场景下Partial Update、Aggregation 等特性的应
作者简介
Cuirj,携程资深数据仓库工程师,关注数仓架构和AI在BI的应用;
Joey Qin,携程数据仓库工程师,关注湖仓技术和AI在BI的应用;
团队热招岗位:数据分析师、高级前端开发工程师
导读:本文介绍了携程商旅在准实时数仓与湖仓一体化建设中的实践。团队基于Paimon构建稳定高效的数据链路,覆盖订单宽表加工、退票提醒、广告归因等场景,并探索批流一体与 Tag 增量计算,总结了不同场景下Partial Update、Aggregation 等特性的应用经验,对准实时数仓建设具有一定参考价值。
一、背景
携程商旅专注于为企业客户提供一站式差旅管理服务,覆盖机票、酒店、用车、火车等多类差旅场景。用户可通过商旅平台完成预订、审批、报销等全流程操作,业务链条长、数据流转环节多,数据规模与复杂度持续攀升。
伴随商旅业务的增长和产品形态的日益丰富,业务对数据时效性的要求不断提高,原有的T+1离线数仓架构已无法满足准实时数据分析需求,而基于Kafka、Flink的传统实时数仓虽能支撑部分实时计算场景,但适用性有限,且其计算中间层无法直接用于分析。
因此商旅大数据团队积极探索准实时湖仓一体路线,提升业务数据新鲜度,推动以Paimon为代表的新型湖仓引擎在核心业务场景落地,助力数据架构升级和业务创新。
目前Paimon在携程商旅的使用场景主要有以下两个方面:
1)准实时数仓搭建:基于Flink CDC和Paimon搭建准实时湖仓,也是当前业界比较典型的湖仓一体解决方案;
2)批流一体融合实践:基于Paimon的流批一体存储基础上,分别用Flink和Spark进行流式处理和批处理。
二、准实时数仓搭建
2.1 准实时订单宽表开发
订单明细宽表是商旅订单管理模块的核心应用层表,支撑用户随时查询订单明细,当前链路采用小时级离线任务加工宽表。随着商旅业务的快速发展,用户对数据的实时性提出了更高的期望,但是订单明细宽表字段很多,数据来源分散,ETL过程涉及近十张表、加工逻辑复杂而且链路较长。
在引入Paimon之前,我们也尝试过基于Flink+Kafka搭建实时宽表,但在实际过程中暴露出以下主要痛点:
1)离线小时级的批任务运行不稳定,ETL流程一旦超时将阻塞下个小时实例运行,数据延迟更高。
2)而Flink+Kafka多流Join在复杂链路下稳定性不足,维护成本高。
如何在保证数据新鲜度的同时,兼顾开发效率和链路稳定性,成为准实时订单宽表开发的核心挑战。引入Paimon能够有效解决上述问题,其湖仓一体的特性支持Upsert更新和动态写入,兼容离线与实时场景,Partial Update特性可代替多流Join构建宽表,显著提升了链路的稳定性和开发效率。
基于Paimon的准实时宽表构建过程如下图所示,ODS层通过Flink CDC将MySQL业务数据实时入湖,EDW层借助Paimon的Partial Update和Aggregation合并引擎构建宽表,另外也使用Paimon表当作维表存储,代替HBase/Redis进行Lookup Join。
在离线ETL任务中,宽表的加工过程通过多表Join的方式放在一个Job里完成,但Paimon的Partial Update不同于Join,使用场景是有条件的,要求目标宽表的主键和源表主键相同,因此离线ETL逻辑不能照搬到实时任务上,所以将离线作业拆分为三个Flink作业:基于Partial Update构建订单产品通用信息宽表、基于Aggregation构建订单中间宽表、基于Lookup Join退化维度信息。
2.1.1 基于Partial Update的构建订单产品信息宽表
火车票产品信息表、机票产品信息表、产品通用信息表具有相同粒度的主键(col1,col2),具体实现过程中,首先创建一张Paimon宽表,merge-engine设置为partial-update,并通过sequence group机制控制多个流中每个流的更新顺序,最终汇聚成一张订单产品宽表。
下图展示了核心SQL逻辑及算子DAG流程。
与Flink多流Join方案相比,Paimon的Partial Update机制在宽表构建中具备明显优势。首先Partial Update无需维护复杂Join产生的state,极大降低了作业的state存储开销,避免了因state膨胀导致的资源瓶颈和性能下降。其次作业的CheckPoint过程更加轻量,提升了整体链路的稳定性和恢复能力,减少了因state不一致或CheckPoint失败引发的异常。通过将多流数据的字段级变更直接落地到Paimon表,既保证了数据新鲜度,也简化了准实时链路的开发与运维,助力准实时订单宽表加工链路高效、稳定。
2.1.2 基于Aggregation构建订单中间宽表
针对ODS表主键不一致、无法通过一次Partial Update实现多流数据合并的场景,我们采用了Paimon的Aggregation合并引擎,并结合nested_update函数进行处理。
具体做法是:将三个主键分别为col1、(co1, col8)、(col1, col18) 的流表,通过Aggregation引擎聚合到以 col1 为主键的宽表。nested_update函数的作用类似于hive SQL中的collect_list,能够将非 col1 作为主键的流表记录,按 col1 聚合为Array类型,统一宽表的主键粒度。此外,对于 col8 和 col18 的计数需求,由于Paimon Aggregation引擎表暂不支持count函数,我们通过sum+case when的方式实现等价计算,满足了业务对多维度数据聚合的需求。
下图展示了核心SQL逻辑及算子DAG流程,这样既保证了数据的完整性和一致性,也提升了宽表加工的灵活性和扩展能力。
2.1.3 基于Lookup Join退化维度信息
传统实时数仓中,实时场景Lookup Join的维表存储通常选择HBase、Redis和MySQL,它们都需要依赖第三方存储,增加实时链路的复杂度和运维成本。引入Paimon后,用Paimon表来存储维度数据,不再依赖第三方存储,而且维表数据量不大的情况下Lookup Join性能完全可以接受,大大简化了实时链路的架构。
通过Aggregation加工的宽表和维表进行Lookup Join丰富维度信息,nested_update函数聚合的字段通过unnest展开与维表Join,作用等价于常用的explode函数。
下图展示了核心SQL逻辑及算子DAG流程。
2.2 机票自动退票提醒优化
机票自动退票提醒功能要求提供当天需提醒的机票订单,虽然这些订单都是历史数据,但由于票号状态会不断刷新,状态变化直接影响订单是否需要被筛选提醒。
原有链路是T-1离线任务,提前计算第二天需提醒的订单,下游通过获取昨日分区数据来满足当天的提醒需求。
这种设计存在数据延迟问题:数据延迟超过2天,虽然需提醒的订单在近2天内出现的概率极小,但实际上这段时间内订单票号仍可能发生变化,影响最终筛选结果。为提升数据准确性和新鲜度,我们基于Flink和Paimon对原有链路进行了改造。在改造过程中也发现,如果全链路仅依赖Flink实时计算,历史数据在首次流式消费后已被处理,后续即便满足提醒条件但未发生数据变更,仍无法再次触发计算,导致部分订单可能被遗漏,无法及时捕获和提醒。
在确保数据准确性的基础上,为提升数据新鲜度,我们设计了如图所示的实时与离线混合链路:订单票号等核心字段的加工使用Flink+Paimon准实时链路完成,最终的订单筛选则通过Spark批作业定时执行,产出的结果表通过携程内部DaaS服务注册为API,便于下游系统实时获取提醒订单,兼顾了数据的时效性与服务的稳定性。
2.3 广告订单归因准实时上报
在商旅酒店广告投放场景中,需将酒店列表页涉及广告酒店的曝光、用户点击及下单行为准实时上报给广告主。用户下单行为的上报需与用户近3天内的点击日志进行归因匹配,只有在下单时间前3天内存在有效点击行为的订单,才会被上报给广告主。订单上报的场景对时效性有一定要求,业务方期望能够做到端到端分钟级时效。
在实际落地过程中,面临以下挑战:
1)上报所需字段和逻辑在业务系统中涉及7张MySQL表,实时多流Join实现难度和成本较大、稳定性挑战较大。
2)点击日志每日增量多,数据表膨胀速度较快,需有效控制表存储,保障查询和Join性能。
如何高效整合多表数据、管理膨胀的点击日志表,并满足分钟级别的上报时效,是该场景下的核心业务痛点。
最终设计开发的ETL链路如下图所示,基于Aggregation For Partial Update解决多流join的挑战,通过Append Scalable表和分区数据过期机制来提高Lookup Join的效率和稳定性,采用Filesystem Catalog实时消费Paimon表并同步调用SOA服务进行数据上报。结合Flink作业3~5分钟的Checkpoint周期,整个链路端到端延迟稳定控制在8分钟以内。
详细过程如下:
1)ODS层构建
依然是借助Flink CDC全增量一体同步的功能,将MySQL数据实时入湖,需要注意的是ODS表的bucket数设置,需要估算表的大小以及考虑近几年的数据增量,按照官方建议的每个bucket 控制在1G左右设置bucket数量。
2)基于Aggregation For Partial Update构建宽表
在订单管理宽表构建的场景中,我们使用Partial Update打宽具有相同主键流表,在Partial Update的合并过程中也支持aggragation函数。在订单上报场景的宽表实现逻辑上,上报的酒店价格需要减去所有商家侧的优惠金额,涉及商家促销表和商家优惠券表。如果需要获取订单的促销优惠金额需要按照订单号sum,因此在使用Partial Update构建宽表时使用sum聚合函数,对于促销表和优惠券表这两个流表只需要筛选出商家侧的记录参与宽表的构建,即可计算商家促销优惠金额和商家优惠券金额,下图展示了核心SQL逻辑及算子DAG流程。
3)分区数据过期机制
订单归因需要关联订单下单时间前3天内的点击记录,因此点击记录维表的生命周期设置为3天。Paimon提供了两种数据失效机制:一种是基于主键表的record level expiration,另一种是基于分区的partition level expiration。
我们分别对这两种方式进行了实践,表配置如下图所示,实际效果来看,记录级失效(record level expiration)如官方文档所述,无法保证及时清除过期数据,离预期效果相差甚远。相比之下,采用非主键、动态分桶的分区表,并设置分区保留4天(partition expiration),能够确保分区最早日期超过4天时自动失效,Lookup Join过程始终可关联到下单时近3天的点击日志。这种实现方式的DAG流程如下图所示,也是官方推荐的实现方式,支持自动compaction合并小文件,能有效控制数据量规模并提升查询效率。
4)Paimon的Catalog消费实践
点击记录和订单归因结果写入Paimon表后,需要同时调用广告投放方的SOA服务进行上报,因此服务调用需集成进整个准实时链路。结合官方文档提供的多种Catalog类型,考虑到内部权限和认证问题,最终选择了访问便捷的Filesystem Catalog,将订单归因结果表注册为DataStream流,同时调用下游SOA服务完成上报,既保证了数据处理的时效性,也简化了链路的权限管理和运维复杂度。
三、批流一体实践
现阶段流批一体方向由于Flink的批处理能力无法代替Spark,尤其是SQL语义的差异较大,所以暂时不能做到计算引擎和代码层面的流批一体。当前比较成熟和落地的场景是流批一体存储,即Flink CDC流式写入Paimon后,基于相同的Paimon ODS表Spark负责批处理、Flink负责流处理,整体仍然是Lambda架构。
具体过程:
1)配置Paimon catalog
Spark3可以通过catalog读写Paimon表,配置过程如下:
/opt/app/spark-3.2.0/bin/spark-sql \ --conf 'spark.sql.catalog.paimon_catalog=org.apache.paimon.spark.SparkGenericCatalog' \ --conf 'spark.sql.extensions=org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions' \ --conf 'spark.sql.storeAssignmentPolicy=ansi' \ -e "select * from paimon.dp_lakehouse.ods_xxx limit 10;"携程大数据平台已默认配置,使用方式和体验与Spark SQL无异;
2)流批读写Paimon表
得益于 Paimon 对主键表的 Upsert 及动态分区写入能力,流批 ETL 链路具备了实现增量计算的基础。实际应用中,我们分别尝试以创建时间和更新时间作为分区字段:以创建时间分区,可实现动态分区的更新,支持历史数据的回溯更新,但难以高效扫描变更数据;以更新时间分区,能够便捷获取变更数据,但不支持历史分区的数据回溯更新。因此,如何在高效获取增量数据的同时,兼顾历史数据的更新能力,是实现增量计算的关键挑战。
3)基于Tag的增量计算
Paimon 与其他数据湖技术一样,支持 Tag 功能。Tag 是基于 Paimon 表快照创建的标签,能够长期保留指定快照及其对应的数据文件。Paimon 支持查询任意两个 Tag 之间的增量数据。结合前述结论,可以将创建时间作为分区字段,定期创建 Tag 以形成数据切片。下面是我们按天周期创建ods表的Tag切片,用于下游增量计算。
通过 Tag 之间的增量查询,不仅能够高效获取数据变更,还能将增量计算数据写入目标分区表,实现对历史数据的回溯更新。该方案在批处理场景下的增量计算具有重要意义,不仅能够节省ETL的计算资源,还大幅缩短了作业执行时间。在我们内部实践中,基于Tag的增量计算替代全量ETL后,作业的处理速度提升了4~5倍,尤其在增量数据较少的ETL场景下,带来了显著收益。
四、总结
如本文所述,基于 Flink CDC 与 Paimon 的准实时数仓架构,有效支撑了携程商旅多个场景的准实时数据应用需求。通过主键表 Upsert 替代 Row_number 去重,利用 Aggregation 聚合函数代替 SQL 中的 Group By 操作,有效提升了链路效率。对于宽表与流表主键粒度一致的场景,优先采用 Partial Update 方式构建宽表,实现高效的数据合并与更新,若主键粒度不一致,则采用 Aggregation的 Nested_update 和 Unnest 组合,灵活满足多样化的数据整合需求。在性能开销方面,Partial Update 优于Lookup Join,Lookup join又优于 Regular Join,整体方案兼顾了实时性、查询效率与运维简易性,显著提升了业务支撑时效性。
此外 Paimon 的 Tag 功能在批处理场景下的增量计算中具有重要应用价值。通过基于快照创建 Tag,可以定期对数据进行切片,长期保留关键时间点的历史数据。利用 Tag 之间的增量查询能力,能够高效获取数据变更,实现批量场景下的高效数据同步与回溯更新。这不仅显著提升了计算效率,还增强了数据的可维护性和灵活性。
五、未来规划
当前业务实践仍采用 Lambda 架构,计算与存储分离。出于业务稳定性的考量,暂未在实时场景中实践 Branch 和 Tag 等特性。后续将重点探索 Paimon 与 Flink 的流批一体能力,进一步推动计算与存储的深度融合。
来源:高可用架构一点号