Olist 项目不是简单把几个 CSV 读进 Spark 再画图,而是一条湖仓链路的工程练习:业务机采集、HDFS 原始层、ODS/DWD/DWS/ADS 分层、Hive Metastore 注册、数据质量门禁、Kafka 重放设计、Spark Structured Streaming、图分析与 Doris 服务层设计。
我把它当成一项需要留下工程证据的毕业设计来做:每一层都要有输入输出、验证口径和失败复盘,而不是只展示最终大屏。下面会区分已经跑通的离线验证与还依赖 Kafka broker 的端到端实验,避免把设计稿写成生产结论。
01集群与主链路
项目跑在一组 Hadoop 集群上,业务机和大数据节点分开:
| 节点 | 角色 |
|---|---|
| main | 业务数据落地、Flume source、MySQL、Redis、Docker |
| hadoop01 | NameNode、Hive Metastore、Spark driver、Doris FE/BE |
| hadoop02 | ResourceManager |
| hadoop03 | SecondaryNameNode |
主链路是:
main:/bigdata/soft/olist/raw
-> Flume
-> hdfs://hadoop01:8020/data/olist/raw
-> ODS Parquet
-> DWD fact/dim
-> DWS monthly aggregates
-> quality / graph / streaming outputs
-> Hive Metastore
-> Doris serving / ADS viewsSpark Scala 项目通过 assembly JAR 交付,核心 JAR 为 batch-etl-assembly-0.1.0.jar。本地验证可以用 local[2],最终保留 spark-submit 入口,方便提交到集群。
02ODS:CSV 不是天然干净的
Olist 原始数据里最典型的问题来自 olist_order_reviews_dataset.csv:评论文本可能包含带引号的换行。最开始 Flume 使用 line-based deserialization,结果把一条评论拆坏,下游出现了荒诞的 review score。
修复方式:
- Flume 离线采集改用
BlobDeserializer。 - Spark CSV 读取开启
multiLine=true、quote="\""、escape="\""。 - 重新采集 review 原始数据,并重跑 ODS、DWD、DWS、Quality。
修复后 review score 分布恢复正常,非法评分被清空。这件事提醒我:数据仓库的第一关不是建模,而是把文件格式读对。
03DWD:修正事实表粒度
DWD 层最重要的设计是把不同事实粒度拆开:
| 表 | 粒度 |
|---|---|
fact_order_item | 一行一个订单明细 item |
fact_order_payment | 一行一个订单聚合后的支付信息 |
fact_order_review | 一行一个订单聚合后的评论信息 |
早期直接把 order、item、payment、review 做大宽表 join,多对多关系会把 item 行数撑起来——同一笔订单的多次支付和一条评论会被交叉复制,join 出来的行数明显高于真实的 item 数。
最终修正为:先在 payment/review 侧按订单聚合,再与 item fact 做下游关联。这样既保住 item 明细粒度,也避免 GMV、订单数和质量规则被 join 膨胀污染。
04DWS 与 GMV 对账
DWS 输出三类月度汇总:
| 表 | 粒度 |
|---|---|
dws_user_month | 用户 × 月 |
dws_category_month | 品类 × 月 |
dws_region_month | 区域 × 月 |
我用 GMV 做核心对账指标:fact 表算一次,三张 DWS 汇总表各算一次,四条口径的结果完全相等。三个 DWS 主题的 GMV 都能回到事实表,说明分层没有在汇总阶段丢数据或重复计算。这个项目里,GMV 对账比多做几个图表更重要。
05质量门禁:从文档规则变成可执行规则
质量模块用 Deequ 与 DataFrame 规则组合,单表规则和跨表一致性规则分开写,规则本身和它们的定义一起存表,而不是散在代码里。
规则覆盖:
- 完整性:主键、金额、关键维度不可为空。
- 唯一性:订单 item、payment、review 的业务键唯一。
- 合法性:金额非负、订单状态枚举、评分在合法区间。
- 一致性:fact GMV 与 user/category/region DWS GMV 对齐。
- 膨胀检测:修正后的事实粒度不允许明显偏离。
- 漂移检测:有 baseline 时检查 GMV 和订单量波动。
Baseline 批次的结果是全部规则通过、没有 BLOCK。
随后我做了异常注入实验:空主键、重复 item、负金额、非法评分、GMV 漂移。注入进去的错误都被对应规则拦下,触发 BLOCK 的那几条规则命中预期,Precision、Recall、F1 没有漏项。这个实验比“我写了质量规则”更有说服力,因为它验证了规则真的能拦住预期错误。
06Kafka 重放与批流对账设计
实时部分不是为了包装成生产级实时湖仓,而是设计一条可解释的批流对账链路:
Olist 历史 CSV
-> Kafka JSON events
-> Spark Structured Streaming
-> per-minute GMV aggregates
-> replay_manifest
-> batch-stream consistency check代码和设计文档已经明确了两个容易忽略的约束:重放要按 event_time + event_id 排序,末尾需要用 watermark event 推进窗口关闭;批侧只比较 replay_manifest 中真正进入重放范围的 (order_id, order_item_id),不能拿全量 batch 与部分 streaming 直接对比。
目前本地已经完成了离线链路、质量规则和相关测试的验证。Kafka broker 依赖的 producer -> broker -> Structured Streaming -> 对账这条端到端运行,在当前环境没有实际拉起 broker,因此我不把它写成已经跑出的吞吐或一致性结论。replay_manifest、事件时间排序和对账脚本是下一次接通运行环境后要验证的基线。
07图分析与 Doris 服务层
图分析把 customer、product、seller、category 建成交易图,一次运行会输出节点规模、边规模、最大连通分量占比和卖家特征数这几项指标,结果写回 graph_metrics 与 seller_graph_features。
PageRank 和连通分量更像实验层,用来解释卖家、商品和品类之间的网络结构,而不是直接替代经营指标。
Doris 服务层的表设计只选择适合查询和展示的结果,例如 dws_user_month、dws_category_month、quality_batch_result、graph_metrics、seller_graph_features、batch_stream_consistency。ADS view 再面向前端组织 KPI、月度 GMV、品类 Top、区域 Top、质量状态、图指标和批流对账口径。当前优先保证离线结果和表设计可追溯;Doris smoke check 与查询性能实验仍在后续清单里。
08项目复盘
这个项目让我最有收获的不是某一个技术点,而是“每层都要能解释为什么存在”:
- ODS 要解决原始文件读取和类型问题。
- DWD 要守住事实粒度。
- DWS 要能回到事实表对账。
- Quality 要能通过异常注入验证。
- Streaming 要有 manifest 才能在接通 broker 后公平比较。
- Doris 服务层要服务查询,不是盲目同步所有表。
后续如果继续做,我会优先补齐 Kafka broker 端到端重放、Doris smoke check、查询性能实验和前端 API 的真实结果绑定,而不是再堆更多中间层。