1. 项目背景:闪购业务的数据链路为什么难搞
淘宝闪购这个业务,很多用户理解为“限时秒杀”,但真正落到数据链路这一层,你会发现它的复杂程度远超普通电商。闪购的核心特征是短时间、高并发、强实时。一个场次可能只持续几个小时,但这几个小时内的点击、加购、下单、支付行为,数据密度是平日的几十倍甚至上百倍。对于负责数据平台的同学来说,这意味着所有链路都要在极短的时间窗口内完成数据捕捉、计算和反馈,慢了半拍,推荐、定价、库存这些核心业务就全都被动了。
我所在的团队主要负责闪购业务的数据湖仓体系,职责范围包括用户特征和商品特征的生产,以及对下游推荐、搜索、定价系统的数据支撑,同时还要满足运营和产品同学日常的多维分析需求。这套体系经历过传统离线批处理的阶段,也踩过流批割裂的坑,最终在今年整体迁移到阿里云 EMR Serverless Spark 和 Paimon 这套湖仓方案上。这篇文章就把这次迁移的完整思路、技术细节、实践数据和踩坑过程整理出来,给同样在做湖仓建设的同学一个参考。
1.1 闪购业务区别于常规电商的三个特点
在讲技术方案之前,得先把业务特点聊透,因为后面所有的技术决策都围绕这几个特点展开。
第一,流量是脉冲式的。常规电商的流量虽然也有高峰低谷,但整体是相对平滑的波动。闪购不一样,它有明确的场次概念,比如每晚八点开一场,开抢后几分钟内的流量就是平日的上百倍。这种脉冲式流量对数据链路的要求极高,任何一个环节出现反压或者积压,都会直接影响核心业务指标。更麻烦的是,这种峰值是有计划但很难精准预测的,每次活动的爆发系数都不一样,给资源预估带来了很大挑战。
第二,特征的时效性极强。闪购商品的价格、库存、上下架状态一直处于动态变化中,变化频率远超普通商品。用户看到一个商品值不值得抢,取决于“当前这一刻”的价格和库存状态,特征链路如果还是按天级或者小时级更新,产出的特征基本是废的。我们内部对闪购特征的时效要求,已经从小时级压到了分钟级,部分核心特征甚至要求秒级更新。
第三,分析需求高度多维。运营看场次维度、品类维度、人群维度,商品团队看单品表现,算法团队看特征分布和线上效果,分析视角各不相同。同一个底层数据要支撑多种维度、多种粒度的查询,而且大家都希望查询能秒开,不能每次都要先跑一个十几分钟的SQL。
这三个特点叠加在一起,就要求底层架构必须同时具备高吞吐写入、低延迟产出、灵活查询分析三种能力。缺一个,业务侧都会立刻感受到。
1.2 旧链路的三宗罪:时效差、成本高、查询慢
在切换到湖仓方案之前,我们用的还是传统的“离线批处理 + 实时计算”两套体系并行。离线链路是标准的数仓分层,从明细层到汇总层多点加工,时效基本是T+1,任务调度依赖关系非常复杂,上游一个任务晚点,下游就跟着连锁延迟。实时链路用流式计算直接消费消息队列里的行为日志,覆盖了一部分核心特征,但能产出的特征种类有限,而且实时和离线两套代码经常对不上口径。
这个架构在业务初期还够用,但闪购业务量上来以后,问题就开始集中爆发了。最头疼的有三个。
第一个是离线特征时效太差。T+1特征只能用于第二天才需要的分析场景,完全没法支撑当天实时推荐和动态定价。为了弥补时效,我们只能把一大批特征计算逻辑重复建设在实时链路上,两套代码维护同一个业务口径,经常出现“离线算出来的值和实时对不上”的情况。每次对不上都要人工排查是时效差还是Bug,消耗了大量人力。
第二个是资源利用率太低。离线链路每天有固定的跑批高峰,集群资源在高峰期紧张,低峰期又大量闲置。更要命的是,为了预留大促峰值资源,平日也得保有大量冗余,成本压力很大。峰值到底有多高,谁也没法提前精确预测,只能按最大可能的量去准备。
第三个是分析查询太慢。数据从生产到可查询需要经过多层任务串联,运营同学想看一个“昨晚八点场次里美妆品类的转化率”,往往要等十分钟以上,一旦涉及多张大表Join,半小时也不奇怪。多维分析这个需求在旧架构里一直没能被真正满足。
这三个问题背后指向同一件事:我们需要的是一套能够统一处理流和批、同时还能支撑低成本高灵活查询的底层设施。这也是我们当时决定走湖仓路线的最根本理由。
2. 技术选型:为什么是 EMR Serverless Spark 加 Paimon
方案选型阶段,我们团队做过好几轮调研和对比测试。当时看了三条路线:第一条是继续扩展自建实时计算集群,把所有特征都推到实时链路上;第二条是引入分析型数据库一体机,专门扛多维分析需求;第三条是走湖仓路线,把流批统一到一套存储和计算体系上。最终我们选择的是第三条,具体组件是阿里云 EMR Serverless Spark 配合 Paimon 做湖仓存储。这个选择不是拍脑袋定的,每一条路线的优缺点我们都做了实际验证。
先说说为什么没有选择前两条路线。继续扩展实时计算集群,实时计算能解决的场景很有限,它擅长的是连续不断的流式处理,但对批量历史数据的复杂分析能力弱,而且维护一套独立实时计算集群的成本同样不低。分析型数据库一体机的方案,查询确实快,但它更适合作为加速层,而不是作为完整的湖仓底座,因为数据源头仍然需要一套链路把数据送进去,流转过程中还是会有重复加工和口径不一致的问题。
结论很明确,我们要做的不是换掉某一个环节,而是把底层重新搭一遍。湖仓架构天然适合这件事。
2.1 Paimon 到底是什么角色
很多人对 Paimon 的第一反应是“又一个数据湖格式”,和 Iceberg、Hudi 有什么区别?这里我用大白话解释一下。Paimon 是一种流式数据湖存储格式,它的核心能力是让数据湖支持完整的流式更新语义,同时保留离线数仓的批式分析能力。说得再直白一点,你可以像写消息队列一样往表里持续写数据,同时又可以像读离线数仓一样拿这些数据做批量分析。
Paimon 最打动我们的一点,是它原生支持主键表的 upsert 能力。电商场景里大量数据本质上是更新流而不是纯增量流,比如商品库存从100变成80,用户标签从一组集合变成另一组集合,这些如果用普通数据湖格式处理,只能通过整行覆盖或者合并小文件来实现,成本高而且慢。Paimon 对主键表的更新是原生的,写入时会自动处理去重和增量合并,读的时候拿到的是真实的一致性视图,不需要额外的合并步骤。
它在湖仓架构里的定位是“统一表格式层”。上游无论是实时写入还是离线批量加载,最终都落成 Paimon 表;下游无论跑特征加工、做分析查询,还是被分析引擎直接读取,都只需要面对 Paimon 这一层抽象。这样流和批的数据模型就统一起来了,写这边是流,读那边是批,口径天然一致。
2.2 Serverless 模式的真实优势在哪里
Spark 这个计算引擎我们团队很熟,过去自建集群跑离线任务也是家常便饭。但这次选择 EMR Serverless Spark,核心考量是资源弹性和运维成本。
闪购的流量脉冲特性决定了计算负载也是脉冲式的。大促开始前要集中产出大量特征,活动期间要持续高频更新,活动结束后计算量又会骤降。自建集群如果按峰值配置,低谷期就是在烧钱;按均值配置,峰值时段又必然扛不住。EMR Serverless Spark 的核心优势就是按需弹性,任务来了就拉起计算资源,任务跑完就释放,完全不需要关心集群规模问题。
另外一个很现实的考虑是运维成本。自建一套 Spark 集群,版本升级、组件兼容、节点故障、安全补丁都要自己处理,这些事看着不大,但真正消耗的人力时间非常多。我们团队规模有限,如果精力都花在集群运维上,就没有足够时间做业务数据的深度优化。Serverless 模式把这些工作都托管了,我们只需要专注写任务、调参数、做数据模型设计。
2.3 整体架构怎么搭
方案整体落地的架构思路大致是这样的。最底层是对象存储,存放 Paimon 表的数据文件。数据接入层通过监听业务库的变更日志,加上实时采集的应用行为日志,全部进入消息队列,再由流式任务写入 Paimon。中间这一层就是我们定义的湖仓层,原始数据统一落成 Paimon 表,再通过 Spark 任务做分层加工,产出特征宽表和聚合表。最上层是消费层,特征服务通过接口把数据提供给下游系统,分析查询直接对接到查询引擎或者跑 Spark SQL。
这里有一个关键设计:我们把实时和离线的计算入口尽量统一到 Spark 上,无论是流式任务还是批式任务,都用同一套表定义和同一套代码逻辑。这样做的最大收益就是口径统一,不再维护两套实现。对于部分要求超低延迟的小链路,仍然保留了流式引擎做 Kafka 到 Paimon 的写入,但只要是落到 Paimon 之后的数据处理,一律用 Spark 完成。
这套架构下来,整体复杂度并没有因为引入新组件而升高,反而因为统一了流批,省掉了大量重复的链路和任务。
3. 特征生产链路:从 T+1 到分钟级的核心改造
特征生产是这次湖仓实践的重中之重。我们当时定的目标很明确:把特征产出的时效从T+1压低到分钟级,同时保证实时链路和离线链路的计算结果完全一致。这个目标放在传统架构里几乎不可能完成,但湖仓模式给了我们一条可行的路。
3.1 特征体系的分层设计
改造的第一步,是重新梳理整个特征体系,按时效要求做分层。我们最终把特征分成了三层。
第一层是实时特征,例如用户当前浏览行为、商品实时库存、当下价格。这类特征要求秒级更新,不能走批链路,必须单独走实时处理通道,直接产出到特征存储服务里,供在线推理使用。
第二层是准实时特征,例如过去5分钟的用户点击次数、最近半小时的加购率、某个商品过去一小时的销量排行。这类特征对时效的要求是分钟级,适合用微批的方式处理,每隔几分钟跑一次聚合,产出后写入特征表。
第三层是离线特征,例如用户的长期购买偏好、历史购买力分群、商品长期销售趋势。这类特征更新频率低,每天跑一次批任务就够,但要求结果准确、口径稳定。
在旧架构里,这三层特征基本是各自为政。实时特征靠流式计算,离线特征靠批处理,中间缺了准实时这一层,导致分钟级时效的需求只能靠实时任务硬扛,资源开销大,产出不稳定。湖仓方案落地后,我们把准实时层和离线层统一到了同一条 Paimon 表处理链路上,区别只是写入频率不同,口径不再有分歧。
3.2 流批一体的特征任务怎么实现
具体实现上,我们为每个特征主题定义一张 Paimon 主键表。比如用户行为特征表,主键就是用户ID加商品ID加场景ID的组合;商品状态特征表,主键就是商品ID加场次ID。行为日志从消息队列以流的方式持续写入这张表,写入过程本身通过 Paimon 完成了主键去重和增量合并。
这里贴一下我们当时建表的样子,供大家参考:
CREATE TABLE dwd_user_item_feature ( user_id BIGINT, item_id BIGINT, scene_id STRING, click_cnt_5m BIGINT, click_cnt_30m BIGINT, cart_cnt_5m BIGINT, order_cnt_1h BIGINT, feature_map MAP<STRING, STRING>, update_time TIMESTAMP(3), PRIMARY KEY (user_id, item_id, scene_id) NOT ENFORCED ) WITH ( 'bucket' = '32', 'bucket-key' = 'user_id,item_id', 'merge-engine' = 'deduplicate' );然后我们用一个 EMR Serverless Spark 批任务做周期性调度,每隔几分钟触发一次。这个任务做的事情是读取 Paimon 表中上次调度以来的增量数据,做特征聚合计算,再把结果写回另一张特征结果表。因为 Paimon 支持增量读取和时间旅行,批任务每次只处理新增数据,不碰历史全量,计算量可控。
这个过程中有一个关键点需要重点说:特征聚合计算要区分不同的时间窗口。比如“最近5分钟点击次数”和“最近30分钟点击次数”,如果每次都从底层明细全量重算,计算代价太高。我们在表结构设计上做了窗口预聚合,先用一小段时间的粒度做聚合,再在上层对时间窗口做累加,既满足不同窗口的查询需求,又把计算量控制在合理范围。
大批量特征任务跑完以后,结果表会通过特征服务对外暴露。下游系统通过特征接口获取数据,响应时间基本在毫秒级。从日志输入到特征可用,整条链路延迟控制在分钟级,应对闪购的大部分业务场景足够了。
3.3 特征质量保障机制
特征数据质量是大促期间最容易出问题的地方,数据错了比数据慢更可怕,因为慢只是延迟,错了会直接带偏推荐和定价。我们在这次改造里专门建立了一套三层质量校验机制。
第一层是完整性校验。对比 Paimon 表中的记录数和消息队列里的原始统计量,差值超过阈值立刻报警,能及时抓住数据入湖阶段丢失或者积压的问题。
第二层是口径一致性校验。定期抽取样本数据,用实时链路产出的结果和批链路产出的结果做对比,差异超过阈值就触发告警。这是流批一体架构下非常必要的一道防线,防止两套任务在实现细节上出现偏差。
第三层是延迟监控。每个特征任务的产出时间都要记录,超过设定SLA自动升级告警。这样即使没人在盯着看,延迟问题也能第一时间被感知到。
这套质量保障机制在几次大促实战里确实挡住了不少问题。印象最深的一次,大促前压测时发现某个核心特征表的产出延迟突然从几分钟飙升到几十分钟。排查下来发现是某个上游数据源的表结构发生了变更,新增了一个字段,导致写入任务解析失败,数据积压在消息队列里。这个例子说明,就算底层架构再自动化,数据源头的结构变更监控仍然不可忽视,尤其是大促前更要做好变更审查。
4. 多维分析提速:宽表物化与查询优化
特征生产解决的是“数据能不能用”的问题,多维分析解决的是“数据能不能查得快”的问题。这两个问题在业务场景里其实是配套出现的:模型需要特征做预测,运营需要看数据做决策,分析师需要盘数据找规律。只有特征快、查询也快,整个数据团队才能真正跟上业务的节奏。
4.1 多维分析在闪购场景里到底痛在哪
多维分析在闪购这个场景里,比普通电商的要求更苛刻。因为闪购是场次制,每个场次有独立的主题、品类、商品池和目标人群。运营同学要盯着多个场次横向对比,看不同策略组合下的转化差异,这种分析需求往往要跨多个维度做自由下钻。
我们接到的典型需求大概是这样的:某运营同学打开后台,想看看“昨晚八点场次里,美妆品类在各个年龄段的销售额和转化率,再对比一下上一场的数据”。这个查询在传统数仓里涉及用户行为表、商品维度表、订单事实表、场次维度表四张表的多路关联,跑一次全量查询需要十几分钟,如果数据量再大一点,半小时也很正常。
这种查询方式显然不能满足运营同学的期望。他们想要的是即时交互式的体验,点开面板就能看到结果,而不是每次都得发一个查询工单让数据同学帮忙跑数。真正的解决方案,是要把查询中涉及的高成本计算,提前在写入阶段消化掉。
4.2 宽表构建的核心思路
我们在湖仓层预先物化一张多维分析宽表,宽表的每一行是一条“用户×商品×场次”的记录,包含了这个组合下的行为指标、商品属性、场次属性等几十个字段。这样下游分析场景基本只需要查这一张表,多数查询连 Join 都不需要做,查询速度自然快了很多。
宽表的产出方式和特征表类似,走的是 Paimon 加 Spark 的链路。底层各张明细表先统一写入 Paimon,然后定时任务把明细表做关联加工,产出宽表写回 Paimon。由于 Paimon 支持主键表的增量更新,宽表更新时只需要更新发生变化的行,不需要重建整张表。这一点对于闪购这种高频更新的场景来说特别关键。
构建宽表时,我们做了一个重要的设计决策:维度退化。相对于经典的星型模型,我们把商品名称、类目层级、场次信息等维度属性全部冗余到宽表的事实记录里,查询时不再需要关联维度表。一开始有同事担心冗余字段会增加存储成本,但实际测试下来,对象存储的成本远低于查询时反复关联带来的计算成本,而且宽表采用列式存储格式,维度字段在一个文件里集中存放,冗余带来的额外存储开销比想象中低得多。这个取舍在分析场景收益大于成本。
4.3 预聚合与查询加速的组合拳
除了宽表,我们还用到了预聚合思想,对高频聚合查询做了指标物化。用过数仓的同学应该都熟悉这个概念,核心就是把高频查询涉及的 Group By 结果提前算好,查询时直接读取结果。
举一个具体例子。运营最常看的“场次×品类×小时销售额”,我们会在底层明细表的基础上定时跑一个聚合任务,把结果物化成一张小很多的预聚合表。查询时直接查这张表,速度比每次对明细表执行 Group By 快几十倍。物化结果同样落成 Paimon 表,同样支持增量刷新,不会让数据时效变差。
在查询侧,我们还接入了独立的分析引擎用于加速实时分析。Paimon 表的数据文件是列式存储,分析引擎像读自己的存储一样直接扫描 Paimon 数据,支持谓词下推和索引裁剪。这套组合下来,分析的响应时间基本都压到了秒级,复杂一点跨维度查询也能在几秒内返回。多维分析的体验从“查一次等半天”变成“几乎秒开”,运营同学第一时间就感受到了变化。
5. 性能收益与实战数据
前面讲了不少架构和方案层面的内容,这一节把实际收益量化列出。我们不是做实验验证,而是全部跑在真实生产链路上,并且经历了多轮峰值流量验证,数据是有说服力的。
5.1 特征生产链路的提升
特征生产这块,最大的变化是时效。改造前,离线特征基本停留在T+1,分钟级的准实时特征只能靠实时任务硬扛;改造后,所有准实时特征统一走 Paimon 加 Spark 的链路,分钟级产出成为标准配置。随着任务参数和表分区策略持续调优,部分高频特征已经做到了1到2分钟一轮更新。
计算成本同样明显下降。因为每次 Spark 任务只处理增量数据而不是全量数据,单次计算量相比原来小了不止一个数量级。再加上 Serverless 模式的资源按需释放,整体计算成本大约下降了40%,这个收益在我们最初评估方案时其实没有预期到。而且按量付费的模式也消除了大促前资源紧张的问题,只要提前把任务队列和调度配置好,不需要再提前抢购计算资源。
5.2 多维分析侧的直观变化
多维分析侧的变化更加直观。改造前,运营同学查一次跨维度报表,等待时间以十分钟为单位,经常是查着查着就忘了自己在查什么。改造后,同类的查询稳定在秒级返回,简单查询一秒内出结果,复杂查询一般不超过三秒。
更重要的一点是,我们不再需要为每个分析需求单独开发数据接口,也不需要每次派数据同学去手动跑数。宽表加预聚合的模式让绝大多数分析需求可以直接自助完成。以前那种“数据同学被各种查数需求淹没”的情况彻底结束了,团队可以把更多精力放在复杂业务问题的分析上,而不是低水平的取数劳动。
5.3 存储与资源成本的细节
存储成本方面,Paimon 的列式压缩和主键表合并能力帮我们省了不少空间。同一份行为数据,和以前传统的存储格式相比,实际存储体积下降了30%到40%。这主要归功于列式格式本身的高压缩率,以及 Paimon 自动处理了小文件合并,减少了大量冗余元数据。
资源成本方面,Serverless 模式让我们不再需要提前购买峰值资源。以前大促前要提前几周申请扩容,大促结束后又要安排缩容;现在整个过程几乎全自动,计算资源随任务启停。单看单价,Serverless 可能比自己搭集群略高,但考虑到不需要维护常驻资源池,综合成本反而更有优势。更重要的是,同学的时间省下来了,这部分的隐性收益很难量化,但价值非常大。
6. 踩坑记录与排查经验
任何技术实践都不可能一帆风顺。我们在这套湖仓链路上遇到过的坑不少,挑几个最有代表性的写下来,给后面落地方案的同学做参考。
6.1 小文件问题是湖仓的第一大坑
Paimon 虽然支持自动小文件合并,但触发时机和合并策略都需要仔细调优。我们刚上线时,发现某些表的分区下小文件数量增长特别快,查询性能肉眼可见地变差。排查后发现,流式写入的并发度设置过高,每个写入任务都会产生多个小文件,而默认的小文件合并策略触发频率跟不上写入速度。
解决思路是结合表的写入模式,手动调整小文件合并的触发参数和频率,同时在每天固定时间窗口跑一次全表合并任务,把一天累计的小文件彻底收敛。调整之后,查询性能退化的问题基本消失。这里提醒一句,小文件治理不能完全依赖框架自动完成,理解自己的写入特征并配合手动策略,是湖仓实践迟早要过的一关。
6.2 数据倾斜的真实案例
数据倾斜是分布式计算里最经典的问题,湖仓场景同样躲不过。我们某个特征表的主键包含商品ID,显然会有少量热门商品的更新频率远超普通商品。大促期间,热门商品的更新量可能是普通商品的几百倍,这直接导致 Spark 任务里某个或少数几个执行节点要处理极大的数据量,其他节点空转,整个任务耗时被无限拉长。
我们当时的排查方法是先看任务执行详情里的各阶段耗时分布,定位到倾斜的 key,再分析这个 key 的数据量和更新频率,确认是热 key 问题。解决思路分两步:第一,开启自适应查询执行,让 Spark 自动对倾斜分区做拆分;第二,对确实无法自动处理的极端热 key,用加盐的方式手动将数据打散,计算完成后再在最终聚合阶段还原。这套组合策略基本把倾斜问题控制住了,任务最大耗时缩短了一半以上。
6.3 快照过期与存储回收
Paimon 表每次写入都会生成新快照,如果不对快照做清理,元数据和历史数据文件的存储开销会不断膨胀。我们最初开启快照过期机制时,把保留时间设置得太长,导致存量存储一直往上涨,大表甚至出现了数个TB的冗余。后来把快照保留时间调整到合理范围,同时开启自动清理任务,存储量很快就回落了,整体成本也明显下降。
这里要特别提醒:快照保留时间的设置需要平衡“时间旅行”的实际用途。如果业务里有回溯任务需要读取两三天前的数据,保留时间就不能设太短,否则那些任务会直接失效。我们的做法是按表分级设置,需要回溯的表适当延长快照保留期,纯实时写入的表则尽量缩短,避免一刀切。
6.4 权限与网络问题排查
把分析查询直接指向湖仓数据时,我们遇到过权限配置不正确导致查询失败的问题。主要表现为部分账号看到的数据目录是空的,或者直接报权限拒绝。一开始以为是数据没写好,排查下来发现不是,而是数据目录的权限和存储桶的访问策略没配置到位,导致元数据能读,数据文件却读不了。
解决思路是把数据目录访问权限和存储桶访问策略统一梳理配置,确保计算侧账号对存储路径有完整的读写权限,同时在网络层面把安全组规则调整到位。这个问题本身不复杂,但因为涉及多个团队和多个配置面,沟通和排查花了不少时间。提前把权限模型设计清楚,能省掉后面大量麻烦。
7. 落地建议与个人体会
最后聊几句个人体会和踩坑后的总结。这次把闪购的湖仓链路迁到 EMR Serverless Spark 加 Paimon 上,整体感受是:湖仓方案不是万能的银弹,它最擅长解决的是流批割裂、链路冗长、成本不可控、分析不灵活这几类问题。如果你的业务还处于小流量常规场景,传统架构完全够用,强行上湖仓反而会引入额外复杂度。但像闪购这种高并发、强时效、多维分析需求集中的场景,湖仓模式带来的收益是实打实的。
如果后面有同学想复刻这条链路,我的建议是不要一上来就把所有任务全部迁移,先挑一到两个核心场景做试点,验证时效、成本和稳定性都符合预期,再逐步扩大范围。同时从一开始就要重视数据质量监控和小文件治理,这两个问题越到后期越难补救,越早建立机制越主动。
另外想强调一点,Serverless 不等于不需要做性能调优。它只是把资源管理的活外包了,业务层面的数据模型设计、任务并行度配置、SQL 写法优化,这些仍然需要自己扎扎实实做。在我个人的实际体验里,真正的效率提升来自把数据模型设计好、把口径理顺、把监控体系建好这三件事,技术组件只是实现这些目标的工具。好的架构永远是业务理解和技术实力的结合,而不是某个“神奇组件”带来的一夜奇迹。