
简介本资源为基于云原生架构的大数据分析 Lakehouse 服务架构设计源码面向大数据开发工程师、架构学习者及需要搭建数据湖分析平台的技术人员帮助理解数据湖与数据仓库融合的工程实现路径。压缩包共546个文件约2.75MB以310个Java文件为核心业务代码辅以66个ts与49个tsx构建前端界面28个Scala支撑大数据处理逻辑另有scss样式、xml与yaml配置、dockerfile容器化文件及sql脚本等覆盖从后端服务到前端展示的完整链路。项目按lakehouse-common、lakehouse-ui、lakehouse-api等模块划分封装通用能力、界面展示与数据交互接口并集成Maven构建、Docker容器化与GitLab CI持续集成流程兼容主流云厂商对象存储。目前已有396人学习下载适合作为云原生大数据架构设计的参考范例便于读者研究多语言协作、模块拆分与部署配置的落地方式。1. 云原生 Lakehouse 到底在解决什么从一次凌晨三点的数据对账说起凌晨三点被叫起来对账是很多数据团队都经历过的场景。业务方要一份「昨天全渠道 GMV」结果 Hive 里跑出来一个数实时链路里又是另一个数两边差了几十万。排查到最后发现离线链路用的是 T1 全量快照实时链路用的是增量事件流口径压根没对齐。这不是 SQL 写错了而是架构本身把「存储」和「计算」拆得太散元数据、权限、事务各管一摊谁也没法给出一份可信的单一事实来源。云原生大数据处理下的 Lakehouse 服务架构本质就是冲着这个痛点去的用对象存储做统一底座用开放表格式Iceberg / Hudi / Delta Lake 这类补上 ACID 事务、时间旅行和 schema 演进再让 Spark、Flink、Trino 这些引擎都能读同一份数据。它既想要数据湖的便宜和弹性又想要数据仓库的一致性和性能。适合谁适合那些数据量已经上到 PB 级、离线实时两条链路打架、又不想被单一厂商锁死的团队。这一篇不讲虚的从架构选型一路拆到能跑起来的最小代码把源码里真正要改的地方标出来。2. 云原生 Lakehouse 服务架构的分层设计与选型理由2.1 存储层、表格式层、计算层、服务层各自管什么一套能落地的 Lakehouse 服务架构我一般按四层来切。存储层是对象存储S3 兼容接口、OSS、MinIO 都行负责最底层的廉价持久化它只认对象不认表。表格式层是灵魂Iceberg、Hudi、Delta Lake 三选一它把一堆 Parquet 文件组织成有事务语义的表管快照、管 manifest、管元数据。计算层是 Spark、Flink、Trino、StarRocks 这些引擎它们通过表格式的 SDK 读写数据彼此不直接耦合。服务层才是「服务架构」这四个字真正落地的地方——Catalog 服务、权限服务、元数据服务、查询网关对外提供统一的 SQL 入口和治理能力。为什么强调「服务」而不是「一套脚本」因为一旦多团队共用Catalog 就成了兵家必争之地。Hive Metastore 能凑合用但它在云原生场景下并发和隔离都吃力常见做法是换成 REST Catalog 或者 Nessie 这类带版本控制的 Catalog。选型上Iceberg 的开放性和引擎兼容性最好Hudi 在 upsert 和增量读上更顺手Delta Lake 和 Spark 生态贴得最近。没有绝对最优看你的写入模式和团队栈。2.2 用 Docker Compose 在本地拉起最小可跑环境光说架构没用先把它跑起来。下面这份 compose 文件拉起 MinIO对象存储、Postgres存 Catalog 元数据和一个 Spark 容器这是本地验证 Lakehouse 最小闭环的常见组合。# docker-compose.yml version: 3.8 services: minio: image: minio/minio:latest command: server /data --console-address :9001 environment: MINIO_ROOT_USER: admin MINIO_ROOT_PASSWORD: password123 ports: - 9000:9000 # S3 API 端口 - 9001:9001 # 控制台端口 volumes: - ./minio-data:/data postgres: image: postgres:15 environment: POSTGRES_USER: lakehouse POSTGRES_PASSWORD: lakehouse POSTGRES_DB: catalog ports: - 5432:5432 volumes: - ./pg-data:/var/lib/postgresql/data spark: image: bitnami/spark:3.5 environment: - SPARK_MODEmaster ports: - 8080:8080 - 7077:7077 volumes: - ./jars:/opt/bitnami/spark/jars/extra逻辑说明MinIO 提供 S3 兼容接口Lakehouse 的「湖」就落在这里Postgres 给 Iceberg 的 JDBC Catalog 存表元数据Spark 作为计算引擎。参数上MINIO_ROOT_PASSWORD至少 8 位否则 MinIO 起不来./jars目录要提前放好iceberg-spark-runtime和hadoop-aws两个 jar版本必须和 Spark 大版本对齐这是新手最容易翻车的地方——jar 版本错一位报错信息能让你查半天。启动后进 MinIO 控制台建一个 bucket比如叫warehouse。这一步别省Iceberg 不会自动帮你建 bucket。2.3 建表、写数据、时间旅行一段能直接抄的 PySpark 代码环境起来后用 PySpark 建一张 Iceberg 表并写入数据验证整条链路。from pyspark.sql import SparkSession spark (SparkSession.builder .appName(lakehouse-demo) # 挂载 Iceberg 的 Spark 扩展 .config(spark.sql.extensions, org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions) # 注册一个名为 local 的 catalog类型是 hadoop 或 jdbc .config(spark.sql.catalog.local, org.apache.iceberg.spark.SparkCatalog) .config(spark.sql.catalog.local.type, hadoop) .config(spark.sql.catalog.local.warehouse, s3a://warehouse/iceberg) # S3 兼容配置指向本地 MinIO .config(spark.hadoop.fs.s3a.endpoint, http://minio:9000) .config(spark.hadoop.fs.s3a.access.key, admin) .config(spark.hadoop.fs.s3a.secret.key, password123) .config(spark.hadoop.fs.s3a.path.style.access, true) .getOrCreate()) # 建表指定 Iceberg 格式按天分区 spark.sql( CREATE TABLE IF NOT EXISTS local.db.orders ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10,2), dt STRING ) USING iceberg PARTITIONED BY (dt) ) # 写入两条数据 spark.sql( INSERT INTO local.db.orders VALUES (1, 1001, 99.50, 2024-06-01), (2, 1002, 200.00, 2024-06-01) ) # 时间旅行查快照 spark.sql(SELECT * FROM local.db.orders.snapshots).show()逻辑说明spark.sql.catalog.local注册了一个名为local的 Catalog之后所有local.db.xxx的表都走 Iceberg。typehadoop表示元数据直接存文件系统适合本地验证生产上换成jdbc指向 Postgres 或 REST Catalog。path.style.accesstrue是 MinIO 必须的否则 S3A 会用虚拟主机风格去解析域名直接连不上。写完数据后orders.snapshots这张元数据表能查到每次提交的快照这就是时间旅行的基础——SELECT * FROM orders VERSION AS OF 快照ID就能回到任意历史版本。参数上DECIMAL(10,2)别图省事写成DOUBLE金额字段用浮点迟早出精度问题。3. 把服务层做厚Catalog、权限与查询网关的落地3.1 为什么 Hive Metastore 在云原生场景下会拖后腿很多团队从 Hive 迁过来第一反应是继续用 Hive Metastore 当 Catalog。本地跑没问题一上生产就暴露HMS 的 Thrift 接口在高并发建表、改分区时容易成为瓶颈而且它和对象存储的 eventual consistency 配合得并不好偶尔出现「表已建但文件还没可见」的玄学问题。更麻烦的是权限HMS 的授权模型是库表级的粗粒度做不到行级、列级。云原生下的常见做法是上 REST Catalog。Iceberg 社区定义的 REST 协议把 Catalog 抽象成一个 HTTP 服务任何实现了这套协议的 Catalog 都能被 Spark、Flink、Trino 统一访问。好处是解耦——计算引擎不再直连元数据库权限、审计、限流都能在 Catalog 服务这一层做。落地时可以用开源的 Nessie 或 Polaris也可以自己基于 Iceberg 的Catalog接口写一个薄服务。3.2 行级、列级权限在 Lakehouse 里怎么设计「大数据行、列权限设计」是搜索里高频出现的诉求落到 Lakehouse 上思路和传统数仓不太一样。传统做法是在查询引擎里做视图或者 SQL 改写Lakehouse 更推荐在 Catalog 服务层做统一拦截。列级权限相对好做在 Catalog 返回表 schema 时根据调用者身份裁剪掉无权访问的列。行级权限复杂一些常见做法是给表打上「行过滤表达式」的标签查询网关在解析 SQL 时把过滤条件下推。比如用户只能看自己区域的数据网关自动在 WHERE 里拼上region 调用者所属区域。这套逻辑不要散落在各个引擎里否则 Spark 一套、Trino 一套维护成本爆炸。下面是一个权限校验的伪代码骨架展示拦截点在哪def check_and_rewrite(user, table, sql_ast): # 1. 列级裁剪无权列 allowed_cols get_allowed_columns(user, table) sql_ast prune_columns(sql_ast, allowed_cols) # 2. 行级注入过滤条件 row_filter get_row_filter(user, table) # 如 region east if row_filter: sql_ast inject_where(sql_ast, row_filter) # 3. 审计留痕 audit_log(user, table, sql_ast) return sql_ast逻辑说明get_allowed_columns和get_row_filter从权限服务读取策略策略可以存在 Postgres 或专门的策略引擎里。关键是这个函数要在查询网关统一调用而不是让每个引擎自己实现。参数上行过滤表达式要预编译校验防止注入列裁剪要在 SQL 解析阶段做别等到结果集返回再过滤那样数据已经落过盘了等于没防住。3.3 查询网关选型Trino 还是 StarRocks服务层对外总得有个统一入口。Trino 的强项是联邦查询能同时读 Iceberg、Hive、MySQL适合做「一个 SQL 查所有」的网关StarRocks 的强项是向量化执行和物化视图对 Iceberg 的查询加速明显适合 BI 场景。我的经验是如果团队查询模式偏 ad-hoc、数据源杂选 Trino如果主要是固定报表、对延迟敏感选 StarRocks 做加速层底下还是 Iceberg 存一份。两者都不排斥很多架构是 Trino 做探索、StarRocks 做服务化查询共享同一个 Iceberg Catalog。要注意的是多引擎共享 Catalog 时表格式的版本兼容性要盯紧Iceberg 的 format-version 升级要所有引擎都支持了再动否则会出现「Spark 能写、Trino 读不了」的尴尬。4. 避坑与排查Lakehouse 落地最常见的 5 个翻车现场4.1 小文件越写越多查询越来越慢现象跑了一个月warehouse目录下全是几十 KB 的 Parquet 文件Trino 查一次要开几千个 task慢得离谱。原因流式写入或者高频微批提交每次 commit 都生成新文件Iceberg 不会自动合并。解决定期跑rewrite_data_files和rewrite_manifests这两个维护动作或者用 Flink 的 compaction 自动触发。参数上目标文件大小一般设 128MB512MB太小合并没意义太大单 task 内存扛不住。4.2 元数据服务成了单点一挂全挂现象Catalog 服务重启所有引擎查询全部超时。原因把 Catalog 当无状态服务部署了单实例没有高可用。解决REST Catalog 至少两副本前面挂负载均衡底层元数据库Postgres做主从。另外客户端要配重试和超时别一个请求卡死整个查询。4.3 schema 演进后老数据读不出来现象给表加了一列新数据能读历史分区查询报错。原因Iceberg 支持 schema 演进但前提是引擎版本要匹配老版本 Spark 读新 schema 会懵。解决升级引擎到支持对应 format-version 的版本或者用ALTER TABLE ... WRITE ORDERED BY之类的方式做兼容。血泪经验是schema 变更前先在测试环境用所有引擎各读一遍别只测 Spark。4.4 对象存储的 eventual consistency 导致「数据消失」现象刚写完的数据换个引擎查不到过几分钟又出现了。原因部分对象存储的 list 操作有延迟Iceberg 的 manifest 已经提交但文件列表还没刷新。解决优先选强一致的对象存储如果没法换查询侧加短时重试。这个坑很隐蔽日志里看不出错就是「时有时无」。4.5 权限配了但没生效现象在 Catalog 里配了列级权限用户换个引擎还是能查到敏感列。原因权限只在网关做了但用户直连了底层引擎绕过了网关。解决网络层限制只有网关能访问计算引擎引擎本身不对外暴露端口。权限这东西只要有一条旁路等于没配。5. 进阶技巧用快照和标签做数据回滚与灰度验证Lakehouse 最被低估的能力是快照管理。每次写入都会生成一个快照这天然就是「后悔药」。我一般会养成两个习惯一是重要批次写入后打一个 tag比如tag_batch_20240601出问题直接SELECT * FROM orders VERSION AS OF tag_batch_20240601回滚验证二是上线新逻辑时先写到影子表用快照对比新旧结果确认无误再切流量。-- 给当前快照打标签方便日后回滚 ALTER TABLE local.db.orders CREATE TAG batch_20240601 AS OF VERSION 3; -- 查询标签对应的历史数据 SELECT * FROM local.db.orders VERSION AS OF batch_20240601; -- 对比两个快照的行数差异做灰度验证 SELECT (SELECT COUNT(*) FROM local.db.orders VERSION AS OF batch_20240601) AS old_cnt, (SELECT COUNT(*) FROM local.db.orders VERSION AS OF batch_20240602) AS new_cnt;逻辑说明CREATE TAG把某个快照钉住不会被过期清理策略删掉适合做发布节点标记。VERSION AS OF支持快照 ID、时间戳和 tag 名三种写法tag 名最稳因为 ID 会变、时间戳有歧义。灰度验证那段 SQL 是我常用的土办法——先比行数再抽样比金额汇总两个都对得上才敢切。参数上要注意快照过期策略history.expire.max-snapshot-age-ms默认保留 5 天生产上按合规要求调但别设太短否则想回滚时发现快照没了那才是真的欲哭无泪。另外 tag 不会自动过期要定期清理无用的 tag不然元数据会膨胀。最后说个习惯我每次改完表结构或者写入逻辑都会先在一个隔离的 namespace 里跑一遍全流程确认快照、权限、查询都正常再动生产。Lakehouse 的灵活性是把双刃剑元数据一乱排查成本比传统数仓高得多。希望帮到你。本文还有配套的精品资源点击获取