前阵子跟一个做数据平台的朋友聊天,他说现在最怕的不是集群资源不够,而是需求方提的取数需求越来越多,凌晨调度的ETL任务还没跑完,业务那边已经在群里催第三条线的指标了。这句话我特别有共鸣。做了这么多年数据工程,从最初的Kettle脚本、Shell加SQL混着的ETL,到后来维护一套又一套的调度依赖,再到这两年把核心数据逐步服务化、接口化,中间踩过的坑比写过的SQL还多,也亲眼看着“大数据处理流程”这个体系从一列慢吞吞的绿皮车,慢慢变成了能够实时响应、按需取用的数据服务网络。
这篇文章我想把这几年看到的、经历的、动手改造的完整过程梳理一遍。它不是单纯讲ETL工具怎么用,而是想聊清楚一个核心问题:为什么传统的ETL流程在现在这种业务节奏下越来越吃力,以及当我们说“从ETL到数据服务”的时候,到底在改变什么。适合正在做数据平台建设、数据仓库维护,或者想搞清楚自己团队该不该做数据服务化改造的朋友参考。
1. 先认清ETL这棵老树的生长逻辑
很多刚入行的朋友以为ETL就是“抽数据、洗数据、存数据”三个动作,字面上没错,但实际跑起来完全是另一回事。ETL真正的核心不是那三个字母,而是围绕它建立起来的一整套“以批处理为中心、以调度系统为骨架”的数据加工体系。
1.1 不可动摇的三段式分工
Extract、Transform、Load这三段式分工,本质上是把数据加工拆成了三个可以独立调度、独立重跑、独立排查的阶段。抽取阶段只管把源系统的数据搬出来,转换阶段做清洗、关联、聚合、口径加工,装载阶段把结果写入目标表或者应用系统。这种分工在数据量不大、业务节奏偏慢的年代非常靠谱,每个阶段都可以单独验证数据质量,出问题了重跑某一段就行,不会牵一发动全身。
我早期做过一个零售行业的数仓项目,每天凌晨两点从十几个门店系统抽取销售流水,清洗后统一以订单维度入仓,再按商品、门店、时间三个维度做汇总。那时候日增量大概几百万行,整个链路跑完大概四十分钟。这种模式当时已经完全够用,业务方每天上午九点打开报表,看到昨天的数据,一切岁月静好。
1.2 调度依赖才是ETL真正的大山
ETL做得越久,越会发现真正的复杂度不在SQL本身,而在调度依赖。一个核心报表的数据链路往往是十层八层的:ODS层抽数,DWD层清洗,DWS层汇总,ADS层出应用表,每一层之间都有严格的上下游依赖关系。为了不让某个环节失败导致全链路重建,还要设计断点重跑、补数、幂等等机制。
时间久了,调度系统里的任务节点会膨胀到几千甚至上万个,依赖关系密密麻麻。我见过最夸张的一个项目,某个核心任务的上游依赖链有六十多层,某天源系统字段变化导致底层层数据异常,结果一路传导上去,业务看到的是好几张报表同时出错。排查的时候顺着依赖图一层层翻,光是定位根因就花了半天。
这就是传统ETL体系的第一个深层问题:它是为“已知的、稳定的、周期性的数据处理需求”设计的,链路越长,越脆弱。
1.3 业务侧等不及“第二天看到数据”
更致命的问题出现在业务节奏上来之后。传统ETL的交付模式是“T+1”,今天的数据明天才能看到。这个模式在过去是合理的,因为那时候的业务决策确实用不上小时级的数据。但后来精细化运营、实时风控、动态定价这些场景出现后,业务方要的是“当下这一刻”的数据,而不是昨天的快照。
我印象特别深的是有个电商团队,他们在做促销活动时的实时大屏,数据延迟不能超过五分钟。传统ETL根本接不住这种需求,于是他们自己搞了一套Flink实时计算,把订单、支付、退款等几个核心事件流直接算好推给前端。这套系统跑得确实好,但问题也来了:实时算的口径和数仓里T+1算的口径对不上,两边数据打架。这就是演进过程中最常见的撕裂感——老的ETL体系没有消失,新的实时服务又冒出来了,中间缺一个能统一衔接的层。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 演进不是推翻ETL,而是把数据处理流程重塑一遍
很多人一提“从ETL到数据服务”,就觉得要抛弃ETL。这个理解是错的。准确地说,ETL作为数据处理的基本手段永远不会消失,真正改变的是它的位置、形态和边界。
2.1 转换时点后移:从ETL到ELT的务实选择
第一波明显的演进是从ETL走向ELT,也就是把Transform从“装载之前”挪到“装载之后”,把装载变成先把数据尽量原样落进去,再在需要的时候通过计算引擎做转换。这背后的驱动力有三个:存储成本大幅下降、计算引擎能力大幅提升、业务对明细数据的需求越来越强。
以前我们不敢把太多明细数据留在数仓里,因为存储贵、计算慢,所以ETL阶段就得提前聚合,只保留汇总结果。现在对象存储加数据湖的底子下,把所有原始数据都原样存下来,成本可以接受,然后什么时候要、要什么粒度,再通过查询引擎现场算。这个变化的本质,是从“加工好再给”变成了“给好原料,按需加工”。
我团队里有个常规做法:ODS层不再做过分清洗,源系统给什么结构就尽量保持什么结构,只做类型推断和分区管理。清洗、口径加工这些动作下推到DWD或者DWS层去做,这样上游改动小,下游灵活度高。
2.2 数据服务化:从“交付数据”到“交付能力”
ELT解决的是灵活性问题,但还没有解决“数据怎么被方便地消费”的问题。这个时候出现的,就是数据服务化。数据服务化的核心不是把数据表开放给业务方去写SQL,而是把数据加工成可以直接被业务系统调用的服务能力。
我理解的“数据服务”,本质上是在数据平台和应用之间加了一层标准化的交互界面。业务方不再关心你的数据存放在哪里、底层跑的是Spark还是Presto,他们只需要按照约定好的接口传参数、拿结果。这就像以前的餐厅只能接受堂食点单,现在支持你能通过外卖平台按菜单点餐——菜单就是服务目录,出餐口就是数据API。
这一步变化的意义在于,数据处理流程从“面向报表交付”转变成了“面向场景消费”。同一个用户标签、同一份订单指标,既可以供报表查询,也可以供推荐系统实时调用,还可以供风控系统批量拉取。一套数据资产,多种服务形态。
2.3 数据处理流程的整体变化
演进之后的整体架构会变成一个什么样子呢?我画过很多版本,最后沉淀下来的核心是四层:存储层、计算层、服务层、消费层。
存储层是底座,以数据湖为主,既能存结构化数据,也能存半结构化、非结构化数据。计算层做统一计算引擎,批和流可以放在一套体系里。服务层是新增量,把指标、标签、明细数据变成可查询可调用的服务。消费层就是各类业务系统、BI工具、算法平台。
这个架构下,传统ETL并没有消失,它被拆散重组了:抽取和装载变成了存储层的入湖管道,转换变成了计算层的加工逻辑,而原本最重的“面向报表组织数据”的那部分工作,被服务层接管和强化了。
3. 数据服务到底长什么样:一个能落地的参考设计
概念说多了容易飘,我直接讲一个相对完整、可以照着做的数据服务设计。这是我在一个中等规模数据团队里落地过的结构,不算复杂,但已经把核心问题都覆盖了。
3.1 分层结构:指标层、API层、权限层
数据服务不是什么玄乎的东西,落到实现上就那么几个模块:指标建模、查询服务、API网关和权限控制。
指标建模做的事情是统一口径。比如“销售额”这个指标,可能每个部门都有自己的理解和定义,有的算含税、有的算不含税,有的算退款前的、有的算退款后的。指标建模就是把这种混乱收敛成一套标准的业务口径,用元数据固定下来。API层根据指标定义自动生成查询接口,业务方调用的时候只要传维度条件、时间范围,返回的就是标准口的数据。权限层控制谁能看哪些数据、能调哪些接口,避免数据越权访问。
3.2 细节一:指标口径建模是数据服务的地基
数据服务最怕的问题是口径不一致。业务方如果发现同一个数字两个接口查出来不一样,他们对整个数据平台的信任就会崩塌。所以做数据服务,第一件事不是写接口,而是把口径管起来。
我分享一个很朴素的做法。每个核心指标必须有一套标准化的口径描述,至少包含:指标名称、业务定义、计算公式、统计维度、时间范围约束、是否含当前未完成数据(比如当日实时)、版本号。这套描述放在数据服务的元数据中心里,API层在返回数据的同时,响应的meta字段里带上口径版本号。这样即使未来口径调整,下游也能知道用的是哪个版本的口径。
口径管理之外,还要做指标的分类分级。有些指标是全局统一的,比如GMV、订单量,必须只有一套标准定义;有些指标是部门自定义的,可以允许在统一的基础指标之上做衍生计算。分级的目的就是既保证核心一致性,又保留业务灵活性。
3.3 细节二:数据API不是简单的查表接口
做数据服务最容易掉进去的坑,是把数据API做成了“数据库表直查接口”。如果只是把表结构暴露出去,那本质上还是换了个方式让人写SQL,并没有解决数据消费的问题。
一个规范的指标查询API,输入参数里应该包含业务维度、统计指标、过滤条件、时间范围、聚合粒度;输出参数里应该是标准的指标值和对应的维度组合。API内部会根据请求自动解析指标定义、拼接查询逻辑、路由到合适的存储引擎执行,然后把结果按标准格式返回。
为了实现这个目标,查询引擎层面要把常见的“明细查询”和“聚合查询”分开对待。明细查询通常要快速返回,适合直接在前缀索引好的明细表上执行;聚合查询一般会走预聚合好的结果表,或者在OLAP引擎上现场聚合。这也是为什么现在不少人会在数据服务底层配StarRocks或者Doris这类OLAP引擎——它们的聚合查询性能确实能扛住在线场景。
接口示例:
code复制GET /v1/metrics/sales_amount
?dimensions=province,product_category
&filters=channel:online
&start_time=2025-01-01
&end_time=2025-01-31
&granularity=day
响应示例:
code复制{
"code": 0,
"data": [
{
"province": "广东",
"product_category": "手机数码",
"date": "2025-01-01",
"sales_amount": 3283500.00
}
],
"meta": {
"metric_version": "v3.2",
"trace_id": "a7c9f2"
}
}
3.4 细节三:数据服务必须区分离线和实时
数据服务体系建设的时候,不要试图用一个方案同时搞定离线和实时。离线场景还能容忍秒级甚至分钟级延迟,实时场景往往要求毫秒级到秒级。两种场景的技术选型、数据链路、保障策略完全不一样。
我们最终采用了双通道设计:离线通道跑T+1的批处理任务,结果写入聚合表,由OLAP引擎提供查询服务;实时通道用Flink消费业务消息队列,直接计算结果写入服务层,供在线系统调用。两个通道都遵循同一套指标定义,计算结果定期做一致性比对,发现偏差及时回刷。
这套设计落地之后,算是勉强把离线和实时之间的口径裂缝补上了。不说绝对统一,但至少两边对账的时候不会差得离谱。
4. 从ETL到数据服务:实践中的关键选型与踩坑实录
这个演进过程不算平坦,踩坑是常态。我挑几个最典型的问题展开讲讲,都是真实发生过的套路。
4.1 技术选型的三个关键决策
演进过程中,技术选型上最容易纠结的有三块:调度平台、表格式存储、OLAP查询引擎。
调度平台方面,老项目很多用Crontab或者Azkaban,新项目建议直接上Apache DolphinScheduler或者Apache Airflow。DolphinScheduler对工作流编排和补数支持得比较舒服,而且自带中文界面,团队上手门槛低。Airflow胜在生态丰富,但运维成本略高,适合团队技术底子厚一些的。关键不是选哪个,而是统一——最怕团队里三个项目三种调度,维护起来真是灾难。
表格式存储方面,数据湖三剑客Delta Lake、Apache Iceberg、Apache Hudi里,我这两年的倾向是Iceberg。Iceberg在快照隔离、时间旅行、schema演进方面的设计干净利落,对大批量变更的场景特别友好,而且跟Spark、Flink这些引擎的适配已经比较成熟了。Hudi有更早的更新插入能力,但日常维护的心智负担明显高一些。
OLAP查询引擎方面,如果业务方的查询模式偏固定,批式聚合多,Apache Doris或StarRocks是现实的选择,建好模型后在线查询性能非常稳。如果查询模式偏探索式,数据模型天天变,Presto/Trino这种MPP查询引擎更合适,灵活但要有足够的计算资源托底。
4.2 坑一:数据血缘缺失,改造无从下手
做任何演进之前,一定要先把数据血缘理清楚,否则就是盲人摸象。
我接手过一个项目,调度系统里有四千多个任务,但没有任何血缘关系图。当时的评估团队想梳理核心数据的加工链路,结果发现最初设计这套ETL的人已经离职两年了,文档早就过时,只能靠读SQL和猜来还原链路。这导致整个梳理过程花了将近一个月,期间还因为误判依赖关系,改动上游任务的调度时间导致下游报表数据晚出两个小时。
后来我们专门做了一个数据血缘采集的机制,基于调度平台的任务依赖,再加上对SQL语句的静态解析,自动构建字段级血缘。这个血缘图对后续做指标建模和接口设计帮助巨大,因为它能告诉你某张报表的某个字段到底从哪里来、经过了哪些加工逻辑。
4.3 坑二:指标口径不统一,服务上线没多久就被吐槽
指标口径统一这件事,难的不是定义,而是执行。数据服务上线初期,我们把GMV口径定义为“支付成功且未退款订单的总金额”,业务方也确认了。结果上线两周后,运营部门反馈接口返回的数据和他们自己统计的对不上。
排查下来发现,运营部门统计的时候把“待发货订单”也算进去了。两边一个从回款角度定义,一个从履约角度定义,都没有错,但口径不一致导致结果差异很大。这个案例说明,指标口径的统一必须和业务方反复确认,而且最好在指标设计阶段就形成一套评审机制,而不是事后再补。
后来我们做了个口径登记表,每个指标上线前必须经过业务方、数据团队、后端团队三方确认,还要写明适用场景和边界条件。这个机制土,但极其有效。
4.4 坑三:权限控制做成了马奇诺防线
数据服务化之后,数据的消费方式变了,权限控制的思路也要跟着变。传统ETL时代,权限控制主要靠数仓的库表权限和报表系统的行级权限。到了数据服务阶段,业务方通过API拿数据,底层是哪个表已经对他不可见了,这时候权限控制的重心就从“能不能访问某张表”变成了“能不能访问某个指标、某个维度、某类数据范围”。
我们第一版权限设计做得太松,只控制到API层面,导致有些业务方通过一个接口把全量用户明细数据拉出来。后来补上了行级权限,在API里注入数据范围过滤条件,根据调用方的组织层级自动限制可见数据。这个改动不大,但确实是数据服务化之后必须要做的一道防线。
5. 数据服务不是终点,而是一个持续演进的过程
从ETL到数据服务,本质上是把数据处理从一个面向过程的、被动响应的模式,拉向一个面向服务的、主动供给的模式。这个过程不是一蹴而就的,而是一步步摸索出来的。
很多团队在推进的时候容易着急,一开始就想把所有数据都服务化,结果发现业务方还没准备好、口径还没对齐、底层血缘也乱,项目推进阻力极大。我见过比较稳妥的路径是:先选一两个业务价值最高、口径最明确的核心指标做试点,把整个流程跑通,倒逼底层数据治理和血缘建设,等模式验证成熟了,再推广到更多的数据域。试点是最考验执行力的阶段,但走通了后面就会顺很多。
从执行节奏上说,我的经验是每做一批数据服务化改造,都同步沉淀出一套面向业务方的数据服务文档,把每个指标的口径、API的调用方式、常见的应用场景都写清楚。文档的价值在前期不显眼,但业务方在用数据服务之后会越来越依赖这套文档,同时他们反馈回来的问题又能反向驱动指标定义的持续优化。
数据服务化也不是一次性的技术升级,它对团队的组织方式也提出了新要求。以前数仓团队的核心能力是写SQL和调调度,现在还需要具备接口设计、服务治理、性能优化、元数据管理这些偏“产品化”和“工程化”的能力。这不是说老能力过时了,而是大家都要在原有基础上长出新的技能来。
我自己现在带团队,最重视的就是把数据处理团队从纯交付型团队,逐步培养成“数据资产产品型”团队。我们不再只是接需求、出报表,而是主动跟业务方聊清楚他们真正需要的数据能力,然后想清楚怎么沉淀成可复用的数据服务。这不只是一个架构上的改变,更是一种工作方式的转变。
如果你所在的公司也正在经历类似的阶段,团队规模不大、业务变化快、数据需求越来越多样化,建议可以认真考虑一下数据服务化这个方向。一开始可能会有些阵痛,但走通之后回报是相当可观的。
