在已经为生产级湖仓打下运维基础之后,我们就可以在 Hudi 之上构建一个全面、集成的一体化解决方案。本章将展示如何以 Apache Hudi 为基石,搭建一套端到端的生产级数据湖仓架构。我们不再孤立地审视单个组件,而是跟随一份数据从最初摄取一路到分析洞察与AI 驱动应用的完整生命周期。
现代数据架构需要:与上游来源的无缝集成、对流批一体(streaming & batch)的统一支持、对多样数据类型的可靠处理,以及同时服务于多种下游消费者(需求各异)。关键不在于“数据完美”,而在于灵活拼装核心能力,在现实世界的数据孤岛与运维挑战中,仍能交付新颖洞察。你需要让数据在组织内“变得容易”,并赋能团队在其之上快速构建。
本章将说明如何在统一湖仓之上组合多种处理框架,优雅地化解这些挑战。Hudi 的多样性既支持这种集成级别,也让你更容易“正确地做事”,兼顾数据一致性、性能与治理。
在本章中,我们将构建一套完整的数据平台,使原始数据逐步转化为业务价值。你将学习如何:
- 使用 Apache Flink 与 Hudi Streamer 处理流式变更,在保持事务保障的同时应对复杂的更新模式。
- 通过 Hudi Kafka Connect sink 摄取大体量日志数据,高效捕获仅追加事件流(append-only) 。
- 借助 SQL 能力完成转换并提取业务洞察,从增量处理到交互式分析。
- 将数据用于 AI 应用,为 LLM(大语言模型) 构建上下文知识库以产出业务洞察。
Note
第 8 章讨论过的 Hudi Streamer 也可用于本架构各层的摄取功能。本章额外展示 Flink 与 Kafka Connect,以体现 Hudi 工具链的丰富多样。
你将看到,Hudi 的流式摄取、表存储格式、增量处理与查询优化如何共同支撑一个整洁的数据架构:高效服务多方干系人,同时抑制数据蔓延与处理开销。
架构总览(Architecture Overview)
本章以一家虚构公司 RetailMax Corp. 为例,实践性地演示从数据摄取到应用 AI 获得业务洞察的数据平台构建。通过该场景,你将看到 Hudi 的特性如何解决促使你翻开本书的那些真实世界问题。
图 10-1 展示了我们的架构,它以 Medallion 架构 为蓝本。读完本章,你将获得一套用 Hudi 实现端到端湖仓的蓝图。这里的模式与技术可适配不同行业的多样用例,为组织的数据战略提供灵活基础。
图示:基于 Medallion 的架构图,展示数据从上游来源流经 Bronze、Silver、Gold 各层,并集成 Hudi 表与 AI 工作流。
图 10-1. 使用 Hudi 工具、组件与集成的 Medallion 架构
RetailMax Corp:一个真实的湖仓场景
RetailMax 经营蒸蒸日上的业务,拥有重要的线上阵地——电商网站与移动应用。当前战略目标聚焦于:提升客户体验、优化运营、推动收入增长。正在推进的一些关键举措包括:
- 360° 客户视图
汇总所有触点的客户数据(线上交互、购买、会员计划、门店交易),全面理解客户行为与偏好。 - 实时个性化
在网站与 App 上为用户提供个性化商品推荐、优惠与内容。 - 库存与供应链优化
跨渠道维持实时且准确的库存可视性,减少缺货/积压,提升履约效率。 - 欺诈检测
实时识别并阻断欺诈交易。 - 自助式分析
让业务用户(市场、销售、运营)可自助做临时分析与报表,而不过度依赖 IT。
RetailMax 数据很多,而且分散各处。
与第 8 章的航空公司类似,RetailMax 同样遭遇数据孤岛问题。它的一些关键关系表保存在“古早但好用”的 PostgreSQL 中,包括:
- customer_master
客户画像、人口统计、联系方式。 - product_catalog
商品详情(SKU、描述、品类、定价)。 - sales_transactions
线上订单与门店 POS 的历史销售数据。
公司还有两个 Kafka topic,代表频繁且近实时变化的对象更新:
- web_clickstreams
高吞吐、仅追加的实时用户交互流;包含页面浏览、商品详情页浏览、推荐点击、加购、搜索等。 - inventory_updates
来自仓库与门店 POS 的库存变动事件;对及时的库存管理至关重要,且对特定 SKU 可能频繁更新。
表 10-1 总结了 RetailMax 的数据来源及其在基于 Hudi 的湖仓中的计划摄取方式。
表 10-1. RetailMax 的数据源与特性
| 数据源 | 权威系统(System of record) | 数据类型/模型 | 摄取方式 | 目标 Hudi 表 | 速度/体量 |
|---|---|---|---|---|---|
| customer_master | PostgreSQL | 结构化/可变 | 通过 Debezium/Flink 的 CDC | hudi_customer_master_bronze | 中/中 |
| product_catalog | PostgreSQL | 结构化/可变 | CDC(Debezium/Flink) | hudi_product_catalog_bronze | 低/中 |
| sales_transactions | PostgreSQL | 结构化/可变 | CDC(Debezium/Flink) | hudi_sales_transactions_bronze | 中/高 |
| web_clickstreams | Apache Kafka | 半结构化/仅追加 | Kafka Connect | hudi_web_clickstreams_bronze | 很高/很高 |
| inventory_updates | Kafka | 半结构化/可变 | Kafka Connect 或 Flink | hudi_inventory_updates_bronze | 高/高 |
用 Hudi 实现 Medallion 架构
Medallion 架构是湖仓中常见的数据组织模式:将数据按层次划分为 Bronze / Silver / Gold,以实现结构化与逐步精炼。
- Bronze 层:存放来自源系统的原始、不可变数据,用于审计与重放(reprocessing)。
- Silver 层:对数据进行清洗、标准化与转换,在强 schema 约束与基础质量校验下,形成统一一致视图。
- Gold 层:存放高度精炼、聚合与宽表化(denormalized) 的数据,面向 BI/分析/机器学习 的高效查询。
这种分层方法促进数据治理、复用与可扩展性,避免数据湖“沦为数据沼泽(data swamp) ”。
配置 RetailMax 的 Hudi 表
RetailMax 将基于这套湖仓做关键业务决策,因此湖仓必须高度可靠,而这并不容易。事件流与关系型数据库是两类截然不同的系统,在规模与一致性上会引入各种复杂性。为确保从一开始就走在正确道路上,我们需要做出一些配置决策,以便提供相应的保证,并对这些保证有足够把握。
Record Keys(记录键)
每张 Hudi 表都需要一种可靠方式来唯一标识记录。这是其高效更新与删除能力的根基。可以把 record key(第 2 章介绍)视作表在后续所有更新中的“锚点”。
有时锚点很直观:例如在 RetailMax 的 Bronze 客户表(hudi_customer_master_bronze)中,record key 就是 customer_id。
有时需要更多工作:在 Bronze 销售表(hudi_sales_transactions_bronze)中,可以将 order_id 与 line_item_id 组合成复合记录键,以粒度精确地跟踪每笔交易。
对于 Bronze 点击流表(hudi_web_clickstreams_bronze),可使用生成的 event_id,或依据上游事件追踪实现,组合 session_id + event_timestamp。
若用例本身并未提供合适的唯一键,用户也可以不配置 record key,Hudi 将为每条记录分配一个高压缩性的自动生成键。
Ordering Field(排序字段)
在流式系统中,同一记录的多个版本时常出现(乱序到达)。这时就需要 ordering field(第 3 章介绍)来决定哪个版本“胜出” 。
常见选择是 updated_at 时间戳:直观易懂,也便于思考去重与数据新鲜度。它还能防范来自不同区域/可用区的过时变更记录让表状态“倒退”或变得不正确。
分区(Partitioning)
分区(第 2 章介绍)有助于下游查询引擎跳过无关数据,并让大规模数据更易管理。但这是一种平衡艺术:要提升性能,同时避免过度分区导致小文件过多。经验法则:仅当表规模 ≥ 约 250 GB 时再考虑分区。
选择合适的分区策略需要综合查询模式、数据分布与分区管理开销。起步方案可以是:
- 将 Bronze 销售表(
hudi_sales_transactions_bronze)按order_date(如year/month/day)分区; - 将 Bronze 点击流表(
hudi_web_clickstreams_bronze)按event_date分区。
该策略契合典型时序查询(如按天分析销售或一段时间内的站点活动),并通过按时间切片来管理数据增长。
表类型(Table Types)
表类型的选择会显著影响湖仓性能(详见第 2 章)。我们将创建多张 Hudi 表,应仔细为每张表选择最合适的类型。
- Merge-on-Read(MOR) :适合高频更新场景。写入快、更新成本低,且可异步执行 compaction。这对以流式为主的 Bronze 表(如
hudi_web_clickstreams_bronze、hudi_inventory_updates_bronze)很有帮助。 - Copy-on-Write(COW) :适合读多写少场景。写入较贵,但读取快速且省心。非常适合 Gold 表(如
hudi_daily_sales_gold,数据已清洁且不常变更),或更新不频繁的 Silver 表。
表 10-2 给出了 RetailMax 湖仓的Hudi 表设计蓝图,把概念性的 Medallion 分层与具体 Hudi 配置关联起来,便于理解 Hudi 在不同数据精炼阶段的落地方式。
表 10-2. RetailMax 湖仓蓝图
| Hudi 表名 | 来源 | 记录键(Record key) | 排序字段(Ordering field) | 分区策略(Partitioning) |
|---|---|---|---|---|
hudi_customer_master_bronze | customer_master(Postgres) | customer_id | updated_ts | country |
hudi_sales_transactions_bronze | sales_transactions(Postgres) | order_id, line_item_id | transaction_ts | dt (YYYY-MM-DD) |
hudi_web_clickstreams_bronze | web_clickstreams(Kafka) | event_id | event_ts | dt (YYYY-MM-DD) |
hudi_inventory_updates_bronze | inventory_updates(Kafka) | sku, location_id | update_ts | dt (YYYY-MM-DD) |
hudi_unified_customer_orders_silver | hudi_sales_transactions_bronze, hudi_customer_master_bronze | order_id, line_item_id | last_updated_ts | order_dt |
hudi_sessionized_clickstreams_silver | hudi_web_clickstreams_bronze | session_id | session_end_ts | session_dt |
hudi_product_daily_inventory_silver | hudi_inventory_updates_bronze, hudi_product_catalog_bronze | sku, date | last_checked_ts | date |
hudi_daily_sales_gold | hudi_unified_customer_orders_silver | date, product_category, region | aggregation_ts | year, month |
hudi_customer_segments_gold | hudi_unified_customer_orders_silver, hudi_customer_master_bronze | customer_id | segmentation_ts | — |
(注:表中 — 表示未设置分区或按实际需要再定。)
Bronze 层:接入上游数据
Bronze 层将作为所有原始数据进入 RetailMax 基于 Hudi 的湖仓的初始落地点。其主要目标是以高保真从多样化上游来源采集数据,尽量保留原始结构,同时也要便于下游 Silver 与 Gold 层进行高效的增量处理。核心目标包括:准确采集源系统数据、保留历史归档、并在需要时支持重放/再处理。
在 Bronze 层,Hudi 充当高效的落地区(landing zone) 。你既可以利用 schema-on-read 的灵活性,也可以让 Hudi 在写入时强制校验模式(Hudi 支持模式演进,可在两者之间取得良好平衡)。对于 Kafka 事件或 CDC 流等流式来源,建议使用 MOR 表类型,以降低写放大与写延迟;配合 记录索引(record index)与异步 compaction,即便在最严苛的负载模式下也能保持平稳。相反,对于批处理且更新不频繁的来源,COW 可能更简洁、更具性价比。此外,可将 Hudi 配置为为表保存足够的版本历史,以便在出现上游脏数据(如写入了错误值)时支持回滚。
搭建上游数据源
RetailMax 的数据生态既包含发生在 PostgreSQL 数据库内的交易型变化,也包含存在于 Kafka 主题中的事件。一些高频变更、支撑关键业务功能的关系表(如 customer_master、product_catalog、sales_transactions)是结构化运营数据的来源;这些变化需要接近实时地被捕获并传播到湖仓。web_clickstreams 与 inventory_updates 等主题承载高吞吐、半结构化事件,反映实时业务活动。
很多公司都会同时依赖事件流与关系库来驱动关键业务。正如你所想,这两类来源需要不同的接入机制,才能高效落地到 Bronze 层。我们先从事务型数据开始。
通过 Debezium、Flink 与 Hudi 流式接入“可变的、事务型”数据
事务型数据库并不会天然地产生事件流,因此我们需要重建变更时间线,使其与 Kafka 主题站到同一赛道。对 RetailMax 的 PostgreSQL 而言,捕获并流式传递 CDC 事件是让湖仓与运营系统保持同步的关键。
从 PostgreSQL 捕获 CDC
Debezium 是一个开源的分布式 CDC 平台。对 RetailMax 来说,将配置 Debezium 连接器去监控 PostgreSQL 中的 sales_transactions、customer_master 与 product_catalog 表。Debezium 常驻在数据库旁,读取 WAL(write-ahead logs) ,并在发生插入/更新/删除时捕获行级变更,生成相应的事件流。
这些事件随后被发布到 Kafka 主题,将数据库变更转化为结构化消息流,供下游处理引擎消费。这样即可确保源库的每次修改都被捕捉,使湖仓能保持与运营数据的一致且最新的视图。
用 Flink 处理 CDC 事件
Flink 作为强大的流处理框架,非常适合消费并处理由 Debezium 产出的 CDC 事件流。它提供低延迟处理、有状态计算以及丰富的连接器。RetailMax 的 Flink 作业将会:
- 从 Debezium 写入的 Kafka 主题消费 CDC 事件;
- 在必要时进行轻量转换/清洗:例如将 Debezium 的事件结构映射到 Hudi 目标 schema、处理更新事件的 before/after 结构、或做基本数据类型转换;
- 将处理后的数据写入 Bronze 层 Hudi 表。
Flink 的 Hudi sink 支持设置记录键、写操作以及合并策略等 CDC 关键配置。常见示例会展示 Flink 处理 Debezium 数据并下沉到 Hudi。
写入 Bronze 层 Hudi 表
处理后的 CDC 事件会写入诸如 hudi_sales_transactions_bronze、hudi_customer_master_bronze 等 Bronze 表。
我们建议对流式 CDC 写入使用 MOR。MOR 通过把变更追加到日志文件(log files) ,并将与列式基文件(base files)的合并推迟到异步 compaction,从而以更低的写放大与延迟优于 COW。
图 10-2 展示了使用 Flink sink 写入 Hudi 表的 CDC 管道。你需要指定若干关键设置:
- 表类型设为 MERGE_ON_READ(便于高效流式写与异步合并);
- base path 指明 Hudi 表的存储位置(如 S3 路径 URL);
- 设置记录键(如销售数据用
order_id)以唯一标识记录; - 设定排序字段(如
update_timestamp)来决定记录合并的先后; - 通过分区字段(常用交易/事件日期)支持高效查询;
- 写操作通常设为 upsert,以主键决定插入或更新。
可靠的数据接入离不开精准一次(exactly-once)语义。Flink 通过其checkpointing 机制(周期性快照应用状态与输入位点)实现精确一次;结合 Hudi 的事务性提交协议(每批写入原子地提交到 Hudi 时间线),即可实现端到端的精确一次,保证即使发生故障,每条 CDC 事件也只影响一次 Hudi 表。
(图 10-2:Flink 驱动的 CDC 接入管线示意)
处理模式演进(Schema Evolution)
源库 schema 随业务演进很少一成不变:会新增字段、类型扩展等。可靠的 CDC 管道必须在不破坏数据流的前提下处理这些变化。Flink 的 CDC 连接器(包括基于 Debezium 的)能够检测上游 schema 变更并向下游传播。
Hudi 推荐向后兼容的模式演进策略。通过在处理引擎(Spark/Flink)中设置表 schema,并开启写时模式演进(见第 3 章),Hudi 能自动接纳新字段,避免管道故障。这种适配力对保持接入稳定性至关重要,使管道能随着源系统增长变化而持续工作,而无需引入脆弱点或大量手工改造。
使用 Hudi Kafka Connect Sink 接入应用事件流
前文我们已接好了事务型表,现在轮到 RetailMax 的高吞吐实时数据。
对来自 Kafka 的应用事件流(如 web_clickstreams 与 inventory_updates),Hudi Sink Connector for Kafka Connect 提供高效、可扩展的接入路径。这些主题中的事件通常是仅追加(append-only) (如点击流),或基于某个键的更新(如某 SKU 的库存变动)。Hudi Kafka Connect sink 被设计为将这些记录流式写入 Hudi 表。
使用 Hudi Sink Connector for Kafka Connect
该 sink 提供一种无需额外处理引擎(如 Spark/Flink)即可把 Kafka 主题直接写入 Hudi 表的直连方式,适用于“简单透传”的场景(见图 10-3)。
关键配置包括:
connector.class='org.apache.hudi.connect.HudiSinkConnector';tasks.max控制并行任务数;- 用
topics或topics.regex指定源主题; target.base.path指向目标存储位置;target.table.name指定目标 Hudi 表名(如hudi_web_clickstreams_bronze)。
大多数流式负载建议使用 MOR;需要定义记录键,选择诸如 event_timestamp 作为排序/合并字段,并设置分区策略。
还需注意几个 Kafka 相关设置:
hoodie.kafka.control.topic用于在任务间协调事务;hoodie.kafka.commit.interval.secs控制 sink 向 Hudi 提交的频率(默认 60 秒)。
配置完成后,connector 便可简洁地写入 Bronze 表(如 hudi_web_clickstreams_bronze、hudi_inventory_updates_bronze),避免不必要的管道复杂度。
(图 10-3:基于 Kafka Connect 的接入架构示意)
事务协调与性能
Hudi Kafka Connect sink 的一项重要能力是分布式事务协调机制:源主题的 partition 0 所在任务充当协调者,通过 hoodie.kafka.control.topic 管理跨 worker 的两阶段提交。该设计在保证高吞吐与低延迟的同时,将 Hudi 时间线上的写动作(commit)限制为每个提交间隔仅一次——与“每个 worker 独立提交”相比,这对大流量写入时控制时间线膨胀(小 commit 泛滥)至关重要。
默认使用 MOR 表型:Kafka 记录被直接追加到 Hudi log files,延迟低;随后由异步 compaction/clustering 将其合并为列式基文件。该方式降低了流式直接写列式文件常见的内存压力。
在性能调优方面,可结合标准的 Kafka Connect worker 配置与 Kafka producer 覆盖项(如 batch.size、linger.ms、compression.type)来优化吞吐。这些通用配置会影响数据送达 Hudi sink 的方式,进而影响整体接入性能。
Silver 层:构建派生数据集
Silver 层为商业智能(BI)、报表与临时分析提供干净、规范、富化后的数据,是面向业务消费的主要来源。
来自 Bronze 层 的数据会在 Silver 层被转换、清洗、校验并规范化。如图 10-4 所示,这一过程包含:过滤坏数据、处理空值、标准化数据类型与格式、解决数据不一致、以及对不同来源的数据集进行关联以形成一体化视图。Silver 表通常会建模为类似企业数仓中的 维度/事实,适合 BI 报表与临时分析。
(图 10-4:在 Silver 层中,利用 Spark 与 Hudi Streamer 将 Bronze 层 Hudi 表转换为 Silver 层 Hudi 表,完成清洗、富化与一致化,并通过 ACID 事务保障数据完整性)
该层涉及大量数据处理(如 join、聚合、数据质量约束),因此 Hudi 的增量处理能力是高效更新这些表的关键。选择 MOR 还是 COW 取决于具体表的特性:若 Silver 表由流式 ETL 作业频繁更新且需近实时查询,MOR 可能更合适;若更新不频繁(如每日批处理)且以读为主,COW 可提供更佳读取性能。Hudi 的 ACID 事务可确保转换操作原子落地,同时在同一存储表上无缝统一批与流两种处理模式,保持一致性。
RetailMax 的 Silver 层目标
Silver 层旨在弥合“原始且常常杂乱”的源数据,与“可靠、结构化”的决策数据之间的鸿沟。对 RetailMax 而言,关键目标包括:
- 数据清洗(Data cleansing)
处理不一致、缺失值(null)、纠正错误数据、标准化格式(如日期格式、枚举值)。 - 数据富化(Enrichment)
跨 Bronze 表进行关联,例如将hudi_customer_master_bronze的客户画像与hudi_sales_transactions_bronze的交易历史进行整合。 - 过滤(Filtering)
去除无关或不符合质量标准的记录。 - 一致化(Harmonization)
将不同来源的数据对齐到统一的 schema 或业务定义(如确保线上/线下的商品分类口径一致)。 - 轻量聚合(Light aggregations)
例如基于原始web_clickstreams生成会话级汇总,或计算每日库存快照。
RetailMax 的一些 Silver 层 Hudi 表示例:
hudi_unified_customer_orders_silver
将线上与门店销售数据与客户、商品信息整合,形成每个订单的全景视图。hudi_sessionized_clickstreams_silver
从 Bronze 层的原始点击流中识别会话并聚合关键会话指标(浏览页数、会话时长、转化事件等)。hudi_product_daily_inventory_silver
基于实时的hudi_inventory_updates_bronze计算每日各商品在不同地点的库存快照。hudi_customer_profiles_silver
客户画像的富化视图,可包含LTV、购买频次等衍生属性。
基于流的转换:使用 Hudi Streamer
鉴于 Bronze 层存在实时/近实时处理需求,RetailMax 可在 Silver 层选择 Hudi Streamer(基于 Spark)或 Flink 进行接入,两者都支持原生流式写入。RetailMax 基于简便性与原生能力选择 Hudi Streamer(第 8 章已介绍其搭建与用法)。由于 Bronze 表本身是 Hudi 表,Streamer 可配置 HoodieIncrSource:它依据源表时间线,仅从上次 checkpoint 之后的 commit 读取数据,因而可对 hudi_web_clickstreams_bronze、hudi_inventory_updates_bronze 等 MOR 表进行持续增量读取。
借助 Hudi Streamer,RetailMax 可以通过 Transformer 完成多表 join 与富化——例如会话化点击流,或将库存更新与商品元数据/其他维表关联——再将结果写入 Silver 表,如 hudi_sessionized_clickstreams_silver、hudi_product_realtime_stock_silver。得益于 Streamer 的 checkpointing 与 Hudi 的事务性 commit,这些管道可以保证一致性。
当转换逻辑变得复杂或与场景高度耦合时,可以添加自定义 Transformer,让工程师精细控制流程与复杂业务规则;多个 Transformer 也可串联。例如,RetailMax 可以从页面浏览、停留时长、加购行为中派生近实时的客户参与度特征,并写入 hudi_customer_engagement_silver。
在该层,Hudi 的 upsert 语义至关重要。随着新数据不断流入 Bronze 层且既有记录被更新,Streamer 作业增量处理这些变化并同步更新 Silver 表,使下游数据集保持实时新鲜与准确。
批处理与增量转换:使用 Spark SQL
并非所有转换都需要实时完成。对批处理工作流与批式来源的数据,RetailMax 选择 Spark SQL 作为首选引擎。
Spark SQL 可覆盖多类批式 ETL 场景。例如,RetailMax 可运行夜间作业,将 hudi_customer_master_bronze 与从 hudi_sales_transactions_bronze 聚合的购买数据(以及低频更新的第三方人口统计数据)进行画像富化;或每日聚合生成 hudi_daily_regional_sales_silver 等汇总表,再把交易数据与相对“慢变”的产品目录参考数据关联。
这些 Spark SQL 作业通常读取 Bronze Hudi 表,应用业务逻辑,然后写入 Silver Hudi 表。对于读为主且更新频率低(如每日一次)的 Silver 表,建议采用 COW,可获得更强读性能与更简单的存储布局。
Hudi 的增量处理进一步提升效率:无需每次批处理都扫描整表/整分区,Spark 可以发起增量查询,仅拉取自上次 checkpoint 以来新增或更新的记录。做法是使用表值函数 hudi_table_changes,并指定起始 commit 时间戳。
对 RetailMax 而言,这意味着构建 hudi_daily_sales_silver 的夜间作业仅需处理来自 hudi_sales_transactions_bronze 的最近 24 小时变更(无需重处理全部销售历史!)。这显著减少扫描数据量、降低计算成本、缩短 ETL 时长,将批处理从“蛮力全量”转变为更精细的增量式工作流。
下面是一个增量 ETL 的 Spark SQL 示例脚本,展示如何从两个 Bronze 表生成 hudi_daily_sales_summary_silver:
首先,确定增量处理的起始 commit 时间(通常来自控制表或上次成功运行的结束 commit,如 20250608000000)。对第一份 Bronze 源 hudi_sales_transactions_bronze 发起增量读取:
CREATE OR REPLACE TEMPORARY VIEW incremental_sales_view AS
SELECT
order_id,
customer_id,
product_id,
quantity,
price,
transaction_ts,
dt AS order_date
FROM
hudi_table_changes(
'hudi_sales_transactions_bronze', 'latest_state', '20250608000000');
接着读取客户数据,便于与交易 join:
CREATE OR REPLACE TEMPORARY VIEW customer_view AS
SELECT
customer_id,
customer_name,
city
FROM
hudi_customer_master_bronze;
进行必要的转换与聚合:
CREATE OR REPLACE TEMPORARY VIEW daily_sales_aggregated_view AS
SELECT
s.order_date,
c.city,
p.category,
SUM(s.quantity * s.price) as total_sales_amount,
COUNT(DISTINCT s.order_id) as total_orders,
MAX(s.transaction_ts) as last_transaction_ts_in_batch
FROM
incremental_sales_view s
JOIN
customer_view c ON s.customer_id = c.customer_id
JOIN
hudi_product_catalog_bronze p
ON s.product_id = p.product_id
GROUP BY
s.order_date,
c.city,
p.category;
最后将结果写入 Silver Hudi 表:
INSERT INTO hudi_daily_sales_summary_silver
-- 若业务逻辑要求可对分区做全量替换,可改用 INSERT OVERWRITE
SELECT
order_date,
city,
category,
total_sales_amount,
total_orders
FROM
daily_sales_aggregated_view;
在 Silver 层维护数据质量与一致性
无论使用 Flink 还是 Spark 做转换,Silver 层的数据质量与一致性都至关重要。RetailMax 可通过 Hudi 的内建保障与规范的管道实践来实现:
- ACID 事务:每次转换(批或流)都以原子方式落地。若作业中途失败,Hudi 会阻止部分写入被提交,确保 Silver 表始终处于一致、可查询状态。
- 校验检查:在每个转换作业中加入校验(数据类型、外键/引用完整性、数值范围等)。不合格记录可标记或隔离,避免污染下游可信资产。
- 失败写入的优雅处理:若 commit 未成功,Hudi 会自动回滚;不完整数据不会对读者可见,并会在下次成功写入或后台清理过程中被清除。这种“自愈”机制对 Silver 层长期健康与可信至关重要。
基于上述能力,RetailMax 能保证 Silver 层成为可靠的分析地基:干净、一致、随时可用。
Gold 层:在 Lakehouse 中查询洞见
RetailMax 的用户(业务分析师、数据科学家与报表工具)需要高效方式来查询在 Silver 层已经整理好的数据。
Gold 层保存高度精炼、聚合、面向业务的数据,正是为此而生。Golden 数据集通常面向具体项目或特定下游应用(如 AI/机器学习模型、高阶分析或管理仪表盘)。Gold 表往往代表关键业务实体或指标,并针对终端用户的易用性与查询性能做过优化。
Gold 层的 Hudi 表以可消费的聚合数据为主,并常按 BI 工具或模型训练的特定读模式进行优化。由于 COW(Copy-on-Write)表把数据存储在列式 base files中、无需在读取时与 log files 合并,读性能更佳,因此 Gold 层(特别是读多写少、每日或每周聚合场景)通常使用 COW。此外,Gold 层的数据模型通常去范式化,并围绕特定业务用例设计。
RetailMax 的 Hudi lakehouse 支持多种查询引擎以满足不同分析需求(见图 10-5)。业务分析师通过 Trino 进行交互式 SQL 与临时分析;数据科学家使用 Spark SQL 做批量分析与复杂转换;自动化应用通过定时报表与 API 访问数据。该 lakehouse 支持 snapshot、read-optimized、incremental 与 time travel 等查询类型,以覆盖组织内的多样化用例。
交互式分析:Trino
Trino 是为数据湖等多源数据提供高速分析查询的分布式 SQL 引擎。对 RetailMax 而言,Trino 是业务分析师的首选:例如从 hudi_customer_profiles_silver 探索客户行为、用 hudi_daily_sales_silver 分析销售趋势、或在 hudi_product_daily_inventory_silver 查看当前库存。
落地时需在协调节点与工作节点配置一个 catalog 属性文件(如 etc/catalog/hudi.properties)。
首先设置 connector.name=hudi 启用 Hudi 连接器;然后通过 hive.metastore.uri 指向 Hive Metastore Service,用于管理表 schema 与分区元数据;按存储后端(如 Amazon S3)补齐相应文件系统配置(如 s3.region、s3.endpoint)。
完成上述配置后,Trino 可高效查询 Bronze/Silver/Gold 全层级的 Hudi 表,分析师与下游系统无需额外管道即可访问最新数据。Hudi–Trino 连接器还支持使用**多模态索引(multimodal index)**加速查询,是交互式分析的高性能选择之一。
连接完成后,Trino 用户可用熟悉的 SQL 查询 Hudi 表:
- COW 表:Trino 直接对最新的 Parquet/ORC base files 做 snapshot 查询,反映最近一次已提交数据。
- MOR 表:Trino 以矢量化方式高效合并 base files 与 log files,同样具备良好查询性能。
若环境支持,还可执行时光回溯(time travel) :在连接器与表版本兼容时,可按指定 commit 时间戳查询历史快照,用于审计、调试或重现实验结果。
批量分析与报表:Spark SQL
虽然 Trino 满足交互查询,Apache Spark SQL 则是 RetailMax 处理复杂转换、支撑定时报表与仪表盘、以及支持数据科学家进行探索分析与特征工程的主力工具。
Spark SQL 能查询 COW 与 MOR 两种表:
SELECT * FROM hudi_table默认执行 snapshot 查询,返回最新视图。对 MOR 表,Spark 会在读时合并 base files 与 log files。- 若对数据新鲜度要求不高,可查询 read-optimized 视图,仅读取经压缩合并的 base files,进一步提升性能。
要让 Spark 查询更快,RetailMax 可以依赖多项优化策略:
- 数据跳读(data skipping) :基于列统计与分区统计(详见第 5 章),Spark 可按谓词中的 min/max 值跳过无关文件,显著减少 I/O。
- 文件尺寸:保持 base files 足够大、与存储块(通常 128MB–1GB)匹配,避免大量小文件带来的执行开销;Hudi 的写时控尺与**后台聚类(clustering)**有助于维持健康的文件形态。
- Spark 调优:如
spark.sql.shuffle.partitions、executor 内存与核数等;同时考虑 Hudi 侧元数据缓存、读并行度等设置。 - 分区与表达式索引:合理分区并构建相关表达式索引,可显著减少扫描量。
综合这些实践,Spark SQL 可在 RetailMax 的分析工作负载中实现可扩展、稳定的性能。
高级查询:Time Travel 与时点分析
在 RetailMax 的日常可靠性保障与长期分析中,Time Travel 十分关键。例如:当排查 hudi_inventory_updates_bronze 的异常时,团队可按历史时间点查询表,精确审计何时、发生了什么变化;同理,在调试 ETL 管道时,可对比某个 Silver 表(如 hudi_unified_customer_orders_silver)在失败作业前后两个时间点的状态,定位转换逻辑或数据损坏的来源。
Time Travel 也支撑 ML 运维:当某模型在 Gold 表(如 hudi_customer_segmentation_features_gold)的特征数据上训练时,RetailMax 可以按训练所用的确切 commit 回溯查询,确保实验可复现,即使已过去数月。
分析师还可做时点(Point-in-Time)查询来理解季节性趋势:直接从销售表按不同年份取一致口径的历史切片。如此粒度的历史精确性是 RetailMax 数据平台的重要战略资产。
Hudi 的时间线(timeline)架构让上述查询既可行又精准。
在 Spark SQL 中,time travel 可用 TIMESTAMP AS OF 语法,例如:
SELECT * FROM
hudi_unified_customer_orders_silver
TIMESTAMP AS OF '2023-01-15 10:30:00.000';
该查询返回 hudi_unified_customer_orders_silver 在 2023-01-15 10:30:00.000 的表状态。Spark 也支持更短的时间格式(如 YYYY-MM-DD)或原始 commit 时间戳。
在 Flink SQL 中,time travel 通常被视为基于 commit 标记的**有界(批式)**查询,通过设置选项指明历史视图:
SELECT * FROM hudi_inventory_updates_bronze
/** OPTIONS('read.end-commit'='20230210120000000') */;
还可配合 read.start-commit 限定变化区间。Flink 将基于精确的 commit 元数据重构表状态。
上述能力的底层支撑来自 Hudi 的时间线:它为每个动作记录对应的 instant time。这份详尽历史是数据可观测性与可复现性的基石——在传统 lakehouse 体系中并不易实现。对 RetailMax 而言,这意味着更好的审计、更清晰的回滚路径,以及在关键“何时发生了什么”的问题上,能自信地给出答案(见图 10-5)。
(图 10-5:Gold 层中的查询能力示意。业务分析、数据科学与自动化应用所用工具的协作,重点展示 Trino 与 Spark SQL 如何在 Silver/Gold 表上支持多种查询类型。)
业务层:为 RetailMax 提供 AI 驱动的洞见
虽然业务层并不属于官方的 Medallion 架构,但它却是我们必须讨论的一层——也是对业务相关方价值感最强的一层。
业务层直接驱动业务价值,例如 AI 驱动的推荐、个性化营销活动,以及提供实时业绩洞察的高管仪表盘。
可以把业务层视为 Gold 层之上的虚拟化层。Gold 表已经将数据转化为高度特定、面向业务的数据集。尽管这些数据集主要供高级分析、AI/机器学习应用与高管仪表盘消费,但按我们的经验,每次启用一个新的数据应用,往往仍需要额外的数据工程工作。如果这些带有试验性质的聚合与转换有机会走向生产,那么通过 Hudi 来实现要远胜于把它们放在某位数据科学家的临时 Jupyter 笔记本里。
在 Gold 层为 AI/机器学习准备数据
Gold 层的首要目标,是为 AI/ML 模型训练、推理与其他专项分析任务构建优化过的数据集。这通常包括:
- 聚合(Aggregations)
将数据汇总到合适粒度(如:客户日度消费、区域维度的周度商品销量)。 - 特征工程(Feature engineering)
从现有数据构造预测特征(如客户的 RFM 分数、产品亲和度分数、用于需求预测的时序滞后与滚动均值)。 - 去范式化(Denormalization)
连接多个 Silver 表,生成更**宽扁(wide, flat)**的表,便于机器学习算法消费。 - 特定格式化(Specific formatting)
组织成特定 ML 库/平台所需的格式(如推荐系统的用户-物品交互矩阵)。
RetailMax 的 Gold Hudi 表示例:
hudi_customer_segmentation_features_gold
包含 RFM 分数、平均购买金额、偏好品类、人口统计等特征,用于训练客户分群模型。hudi_product_recommendation_user_item_gold
存储用户-物品交互数据(浏览、购买、评分)或预计算的嵌入,用作协同过滤/内容推荐的输入。hudi_demand_forecasting_ts_gold
SKU/门店级别的聚合销售时序数据,用于训练需求预测模型。hudi_marketing_campaign_roi_gold
融合投放成本、客户触达与销售增量,计算各营销活动的 ROI。
这些 Gold 表通常用 Spark SQL 或 Flink SQL 基于 Silver 数据做最后一步转换。为兼顾 ML 框架的读取性能,这些表多配置为 COW(读多写少、每日或每周更新、训练/批量推理读取密集)。
用 Ray 与 Hudi 为 LLM 应用构建知识库
市场部准备上线一个面向内部团队的 LLM AI 助手。用户可以用自然语言提问客户趋势、产品表现或活动效果(例如:“上季度加州 25–35 岁客户最畅销的品类是什么?”)。这需要基于 RetailMax 的数据构建一个专用知识库。
为实现规模化,RetailMax 可能使用 Ray(面向 AI 与 Python 应用的分布式计算框架)。通过 ray.data.read_hudi(),团队可将 Gold Hudi 表(如 hudi_customer_segments_gold、hudi_product_summaries_gold)的大量数据载入 Ray Datasets。Ray 的并行执行负责预处理、特征提取与必要的文本处理,为下一阶段做准备。
该内部助手的核心是 RAG(Retrieval-Augmented Generation) :把 LLM 的推理能力与对公司经治理的数据的实时访问结合起来。它让非技术用户(如市场分析师)用自然语言提出复杂数据问题,并得到有根据、可溯源的回答,而无需编写 SQL 或使用 BI 报表。
构建此类系统的关键步骤:
- 数据选择:从 Gold Hudi 表中抽取最相关的结构化/非结构化内容:客户行为聚合、产品描述、销售摘要、用户评价等。
- Ray 预处理:清洗并切分数据(尤其是文本)为适合嵌入的小段。
- 嵌入生成:将各段数据通过高质量嵌入模型(如 sentence transformer 或 OpenAI embeddings)转换为稠密向量。
- 向量库入库:将嵌入与元数据(来源 Hudi 表、主键等)存入向量数据库(FAISS、Milvus、Pinecone 等)。体系中包含一个 “context builder” ,从 Gold 表读取并填充推理阶段使用的知识库。
该助手的可靠性与准确性,取决于输入数据质量。Hudi 在此提供坚实基础:Gold 层不仅新鲜、一致,还能通过时光回溯与增量处理实现审计与可复现性。这让 RetailMax 拥有一套可信、动态的知识库,而不是依赖脆弱且陈旧的临时 RAG 管道。
以下是 AI/RAG 工作流的幕后过程:
当有人提问“对比最近一个月会员与非会员的平均消费”时,系统先用与知识库构建阶段相同的嵌入模型对查询生成向量,这个向量就像问题语义的指纹。
随后用该“指纹”在向量库中检索最相似的上下文切片——它们来自 Gold Hudi 表的小而有意义的片段(例如近期销售摘要、会员指标、客户分群统计)。
检索到的内容与原始问题拼接,形成增强后的提示(prompt),并以模板规范化格式,引导 LLM 正确理解与引用数据。
最后在生成阶段,把增强后的提示发给 LLM(如 GPT-5 或 Llama),产出既流畅又“有据可依”的回答。
这种 RAG 驱动的管道(见图 10-6)为 RetailMax 提供了快速、直观的洞察层,降低了决策摩擦。它打通了结构化数据与自然语言之间的通道,使团队可以随问随答、问得更好、答得更准。
(图 10-6:业务层中的 AI 工作流示意——从 Gold Hudi 表到 AI 助手洞见的全流程。)
让 Hudi Lakehouse 落地并持续优化(Operationalizing and Optimizing the Hudi Lakehouse)
搭建最初的数据摄取管道、转换作业与 AI 应用只是 lakehouse 之旅的开始。要让 RetailMax 的 lakehouse 在长期内保持可靠、高性能且具性价比,接下来是一段持续优化的征途:不仅要做数据工程,还要在时间维度上管理性能、成本与表健康度。
这项工作的核心是第 6 章深入介绍过的 Hudi 后台表服务(table services) 。必须针对不断变化的工作负载进行调优与调度。这些服务还能以不同模式部署(内联 inline、异步 async、独立 standalone),以便根据时延与吞吐要求做出适配。
RetailMax 将依赖 compaction(压缩合并) 、clustering(聚类/重写布局) 与 cleaning(清理) 来维持各层稳定高效。例如:
- Compaction(压缩合并)
为写入压力大的 MOR 表(如hudi_sales_transactions_bronze、hudi_web_clickstreams_bronze)保障快速读取。通过将 delta 日志合并为 Parquet 基础文件,在不压垮查询引擎的前提下持续提供新鲜数据。可用异步、按“新增提交数”触发等策略,在摄取吞吐与读性能间取得平衡。 - Clustering(聚类/布局优化)
改善 Silver/Gold 表(如hudi_unified_customer_orders_silver、hudi_customer_segments_gold)的查询性能,为产品分析与机器学习工作流赋能。RetailMax 可用异步 clustering 与基于 Spark 的策略,按常用过滤列排序并减少小文件问题。 - Cleaning(清理)
通过移除不再需要的旧版本文件,控制存储成本与元数据开销。需要与合规与调试所需的“历史保留”平衡,避免存储膨胀。
每个服务都高度可配置,工程团队可据数据新鲜度/读取时延/成本效率等目标,为摄取、转换、报表等各层量身调优。
并发控制与多写入者场景
随着 RetailMax 的 lakehouse 成熟,可能出现多个进程并发写同一张 Hudi 表的场景。例如:
- 一个 Hudi Streamer 实时更新 Silver 表,同时一个每晚的 Spark 批处理任务向同一表追加更正或富化数据。
- 摄取写入者与**异步表服务(如 compaction 或 clustering)**同时作用于同一张表。
正如第 7 章所述,Hudi 提供了相应的并发控制机制来管理这类情形。
Lakehouse 监控
有效的监控对维持 RetailMax 的 Hudi lakehouse 的健康与性能至关重要。
如第 9 章所述,Hudi 可与 AWS CloudWatch、Datadog、Prometheus 等多种系统集成,覆盖的关键指标包括:提交延迟与时长、插入/更新/删除记录数、compaction/clustering 积压、时长与效率、文件大小与数量、索引查找性能、时间线活动等。建议将这些指标汇入 Prometheus 并用 Grafana 仪表盘可视化,从而掌握表与摄取/转换流水线的运行态势。还应为关键异常配置告警,例如:Hudi 提交失败、compaction/clustering 积压过大、小文件数量快速上升、存储磁盘空间告急、摄取作业错误率升高等。
数据韧性(Data Resilience)
在发生数据损坏、误删或系统故障时保障业务连续性至关重要。Hudi 提供了有助于提交回滚与增强数据韧性的能力(见第 9 章):
- Savepoint(保存点)
在 Hudi 时间线为某个提交打上“保留”标记。Cleaner 不会删除与该保存点提交及其之前提交相关的任何数据文件。等同于在该时间点创建了可恢复的表状态。RetailMax 应基于 RPO(恢复点目标)为关键表(如hudi_unified_customer_orders_silver及关键 Gold 表)定期创建保存点。既可通过 Spark SQL(CALL create_savepoint('table_name','commit_timestamp'))也可用 Hudi CLI/Utilities 创建。 - Restore(恢复)
将 Hudi 表恢复到先前创建的保存点。此操作具“破坏性”:会回滚保存点之后的所有更改。恢复期间应暂停对该表的所有写入。这是从逻辑数据损坏或重大错误中恢复的有力手段。
通过切实落实这些运维实践,RetailMax 能让其 Hudi lakehouse 持续保持高性能、可靠、安全且具韧性,为各类数据驱动举措保驾护航。
性能基准与注意事项
尽管本章聚焦端到端方案构建,但必须承认任何 lakehouse 的性能都至关重要。Hudi 在多种场景下进行了评估,包括与 Apache Iceberg、Delta Lake 的对比,以及 TPC-DS 等行业标准基准。
这些基准通常衡量:数据装载时长、一系列分析查询的执行速度、以及合并类操作(更新/删除)的性能。结果常常取决于具体工作负载(读多/写多、批/流)、表类型(COW/MOR) 、配置选择与查询引擎集成成熟度。例如,TPC-DS 的结果显示:MOR 在合并(merge)方面可能快于 COW,但若compaction 不够积极,查询性能可能下降。装载性能也会受影响:Hudi 侧重键控 upsert 与摄取时的预处理,初次装载可能不如纯追加优化的格式快,但其优势会在后续的增量更新中体现。
与其执着于某一组基准数字,RetailMax 更应聚焦下列直接影响性能的架构特性与调优策略:
- 写入侧索引(Indexing for writes)
索引是规模化下维持读写性能的基石。Hudi 支持多种写入索引(第 5 章详述),如 record、bucket、simple、bloom,用于快速定位需更新的文件组,避免全表扫描。 - 读取侧索引(Indexing for reads)
借助 metadata table 维护的列统计/分区统计,查询引擎可按谓词跳过不相关的文件/分区,显著减少 I/O。启用 record index、secondary index、expression index 还能进一步加速等值或多样谓词查询,释放 Hudi 多模态索引的威力。 - 文件尺寸(File sizing)
调整hoodie.parquet.small.file.limit、hoodie.parquet.max.file.size并结合 clustering,避免小文件问题。 - 表服务(Table services)
异步 compaction 与 clustering 持续优化 MOR/COW 表的物理布局,减少小文件,确保数据增长下查询不降速。 - 增量查询(Incremental queries)
Hudi 支持基于 delta 的查询。下游作业只需处理自上次运行以来的变化,而非重扫全量。
下表总结了影响性能的 Hudi 特性及其在 RetailMax 的关键用例:
表 10-3. 影响 RetailMax Lakehouse 性能的 Hudi 特性与适用场景
| Hudi 特性 | 写入时延 | 更新/删除速度 | 快照查询速度(Trino/Spark) | 增量查询速度(ETL) | RetailMax 关键用例 |
|---|---|---|---|---|---|
| MOR 表型 | 低(日志追加) | 高(高效处理频繁小更新) | 中(需合并 base+log 或读 _ro 视图) | 高(可从日志导出变更) | 流式 CDC(hudi_sales_transactions_bronze)、Kafka 事件(hudi_web_clickstreams_bronze) |
| COW 表型 | 高(更新需重写) | 低(写放大) | 高(直接读列式 base 文件) | 中(需比对快照差异) | Gold(hudi_daily_sales_gold)、更新不频繁的 Silver |
| 写入侧索引(bloom/bucket 等) | 提升 upsert 定位速度 | 关键 | 间接(通过提升写入) | N/A | 需 upsert/删除的表,尤其 hudi_customer_master_bronze、hudi_inventory_updates_bronze |
| Metadata table & 数据跳过 | 对写入有轻微开销 | 轻微 | 很高(减少计划与扫描) | 中(有助定位变更分区) | 频繁被查询的 Silver/Gold,加速 Trino/Spark 查询 |
| 多模态索引加速 | 高 | 高(record index 加速记录级更新) | 高(显著减少扫描量) | N/A | Bronze/Silver 快速写入与 Gold 快速查询 |
| 异步 compaction(MOR) | 将 compaction 与摄取解耦,保持低时延 | N/A | 提升 _ro 新鲜度与性能 | N/A | 维护所有 MOR 表的查询性能、防止日志堆积 |
| 异步 clustering(COW/MOR) | 与摄取解耦 | N/A | 高(优化文件大小/排序) | N/A | 查询密集的 Silver/Gold,如 hudi_unified_customer_orders_silver |
| 增量查询 | N/A | N/A | N/A | 很高(只处理变化数据) | Bronze→Silver、Silver→Gold 的 ETL 作业显著降本提效 |
| 优化文件尺寸 | 合并小文件会略增写延迟 | 通过减少大量小文件的元数据开销而提升 | 高(更少文件、更少任务) | 中(更少文件需检查) | 所有表,避免小文件并提高查询引擎扫描效率 |
小结
本章贯穿了一个贴近真实复杂度的公司——RetailMax Corp. ——构建现代数据 lakehouse 的完整生命周期。Hudi 作为骨干,覆盖从摄取、转换、查询到AI 洞见的 Bronze/Silver/Gold 各层。
- 在摄取层,Hudi 同时支持 Flink + Debezium 的流式 CDC 与 Kafka Connect 的高吞吐事件处理。Schema 演进、事务一致性与端到端 exactly-once 确保管道可靠、面向未来。
- 在转换层,Flink 与 Spark SQL 各擅胜场:前者驱动近实时整形,后者支撑批量与增量 ETL;Hudi 的增量查询让 ETL 从“重扫全量”变为“按变更处理”。
- 在查询层,分析师用 Trino 做交互式洞察,数据科学用 Spark SQL 做深度批量分析。Metadata table、可插拔索引、时光回溯强化性能与可观测性。
- 在 Gold 层,经治理的数据为 AI/ML 供能。RetailMax 借助 Ray 从 Hudi 构建 LLM 就绪的知识库,打造 RAG 系统,为自然语言问题提供有据可依的答案。
贯穿其中的 Hudi 核心能力包括:
ACID 事务、记录级更新/删除、Schema 演进、表服务、广泛的引擎集成、以及增量处理。
对正在启航的读者,基于 RetailMax 的经验有以下要点:
- 理解你的数据:分析数据源、访问模式、更新频率,明智选择 COW vs MOR、record key、ordering field、分区策略。
- 拥抱 Medallion:以 Bronze→Silver→Gold 的层次渐进提升数据质量,并为特定用例定制数据集。
- 运营化表服务:自动化 compaction/clustering/cleaning;这不是“善后”,而是健康、高性能 Hudi 的关键部件。
- 用好 Hudi 生态:结合 Flink/Kafka Connect/Spark 的摄取与处理,以及查询引擎,搭建一体化数据平台。
- 从第一天就关注性能:文件尺寸最佳实践、metadata table + 索引、以及增量式思维,确保随规模增长仍可高效扩展。
以 Hudi 为代表的 lakehouse 范式,提供了统一管理多样数据与工作负载的现代路径,打破数据孤岛,释放数据资产的全部潜能。随着 Hudi 在性能、可扩展性、易用性上的持续创新,它将愈发成为现代数据架构的基石。本章的方法与范式,为你用 Hudi 构建强大而灵活的端到端解决方案提供了可靠蓝本。