Apache Superset 架构深度解析(二):一次查询从点击到渲染的完整旅程(附架构图 + 时序图)
本文是「Apache Superset 深度解析」系列的第 2 篇。上一篇我们讲清了"Superset 是什么 + 核心概念";这一篇往下钻一层:当你在浏览器点开一张图,到画面渲染出来,这中间 1 秒到几秒里,后端到底发生了什么。
系列目录(持续更新):
📌 TL;DR · 30 秒速览
- 一条查询的两条路:Explore 同步直出 / SQL Lab 大查询走 Celery 异步,最终都汇聚到同一套「QueryContext 生成 SQL → 执行 → 缓存 → 渲染」。
- SQL 是谁生成的:
QueryContext(构建查询)+db_engine_spec(方言适配)+ SQLAlchemy(连接执行),三者各司其职。- 性能命门是缓存:Superset 有五级缓存(筛选状态/表单/元数据/图表数据/结果集),生产必配 Redis。
- 生产形态:Gunicorn(Web 无状态多副本)+ Celery Worker(异步)+ Beat(单副本)+ PostgreSQL(元数据)+ Redis(缓存&队列)。
一、先抛出那个"细思极恐"的问题
上一篇文章最后,我留了一个引子。现在正式回答它:
你在 Superset 里点开一张图表,从 HTTP 请求发出,到 ECharts 把折线画到屏幕上,后端经历了几次网络往返?涉及哪几个进程?SQL 是谁拼出来的?权限在哪一步拦截?缓存有没有命中?
这串问题的答案,就是 Superset 架构的全部秘密。搞懂它,你才能回答那些更实战的问题:
- 为什么 Superset 自己不存业务数据,却敢叫"数据平台"?
- 为什么生产环境必须上 Redis,不上会怎样?
- 为什么 SQL Lab 跑大查询页面不卡死,而某些 BI 工具会?
- 为什么 K8s 部署里有个"Init Job",还有一堆
wait-for-init?
带着这四个问题往下读。
二、全景架构:从"6 组件"升级到"三层五服务"
上一篇给了 6 组件总览,这一篇把它升级成生产级全景。先记住一句话:
Superset 是"无状态 Web + 有状态中间件 + 外部数据源"的经典三层结构。 Web 层随便扩,状态全在 PostgreSQL / Redis 里,业务数据全在你自己的数仓里。
这张图透露了三个关键设计决策(后面会逐一展开):
- Web 层完全无状态——所以能无限水平扩展,扩了也不怕会话丢失(会话状态在 Redis);
- Redis 一鱼两吃——既是"缓存"又是"Celery 消息队列",一个中间件扛两份活;
- PostgreSQL 是唯一事实源——图表定义、数据集、用户、权限、日志都在这里,数据仓库里只有业务数据。
三、核心:一次查询的完整时序
这是全文的"题眼"。把上面四个问题串成一条完整链路(含同步、异步两条分支):
把这张图读三遍,你就掌握了 Superset 的全部主链路。下面把 ①–⑧ 每一步展开讲透。
四、逐步拆解:每一步发生了什么
4.1 第 ① 步:RBAC 鉴权 + RLS 行级过滤(为什么是"生成 SQL 时注入")
请求进来的第一道关卡是 RBAC + RLS,由 Flask-AppBuilder 的安全模型负责。
- RBAC 回答"能不能看":这个用户所属角色有没有访问该 Dataset / Dashboard 的权限?没有直接 403。
- RLS 回答"能看哪些行":角色配置了行级规则后,Superset 会在后续生成 SQL 时,把
WHERE region = '华东'这类条件强制注入进去。
⚠️ 关键:RLS 的注入发生在 SQL 生成阶段,而不是在前端"把按钮藏起来"。即使用户绕过前端直接调 API,也躲不开这层过滤——具体到 SQL Lab 场景,RLS 的强制程度存在版本差异,请以你所用版本的实测为准。这是它和"玩具型 BI"最本质的区别。
4.2 第 ② 步:读元数据(PostgreSQL 里的"图纸")
鉴权通过后,后端去元数据库读两样东西:
- Chart 定义:这个图是什么类型(折线/柱状/表格)、绑定了哪个 Dataset、用了哪些 metric、筛选条件、颜色、坐标轴配置;
- Dataset 定义:这个数据集对应哪张物理表/哪段 Virtual SQL、有哪些列、哪些 metric 被"认证"、哪些计算列。
记住这个模型:图表是"图纸",数据集是"原料清单",数据是"原材料"。图纸和清单存在 PostgreSQL,原材料在你自己的数仓里。这就是 Superset "不存业务数据"却能工作的原因。
这就是第 ① 个问题的答案:Superset 之所以"不存业务数据"却敢叫数据平台,是因为它存的是更值钱的东西——分析的定义与血缘(图纸+清单),而"原材料"永远留在离数据最近的地方(数仓)。
4.3 第 ③④⑤ 步:缓存——性能的命门
读完配置,先查缓存,而不是急着打数仓。这就是五级缓存里最关键的一级——图表数据缓存(DATA_CACHE_CONFIG)。
命中就直接返回,一次数仓查询都不发起。这也是上一篇预告过的那个数字的由来:广告场景里,配合 ClickHouse 侧的物化视图 + Superset 侧的 Redis 结果缓存,一次查询能从 45 秒压到 200 毫秒。
缓存**超时时间(TTL)**有一套优先级,值得背下来:
chart 自定义 TTL > dataset TTL > database TTL > 全局 DATA_CACHE_CONFIG 默认值
设 -1 表示关闭缓存(比如实时性要求极高的场景)。
这就是第 ② 个问题的答案:Redis 不是可选项,而是"缓存 + 异步队列"的双重底座——没有它,多副本会话、五级缓存、Celery 异步全都无从谈起。
4.4 第 ⑥⑦ 步:SQL 是怎么被"生"出来的
这是 Superset 架构里最精巧的一环。它把"从可视化配置到 SQL"这件事,拆成了三个职责清晰的组件:
三个组件的分工:
| 组件 | 职责 | 一句话 |
|---|---|---|
| QueryContext / QueryObject | 把图表配置(选了哪些指标、怎么过滤、按什么聚合)封装成结构化查询,同时携带 datasource 与结果格式化/缓存元数据 | “我要查什么” |
| QueryContextProcessor | 编排整个生命周期:查缓存 → 执行 → 后处理 | “谁来调度” |
| db_engine_spec(方言适配层) | 提供方言规则:时间粒度、limit 写法、类型转换、错误映射 | “翻成哪种 SQL 方言” |
| SQLAlchemy | 连接、编译、执行、事务 | “把 SQL 送到引擎执行” |
这套抽象是 Superset 能支持 40+ 种数据源的底层原因:新增一种数据库,主要就是写一个 db_engine_spec 子类,上层"我要查什么"的逻辑完全不用改。
补充:这套
QueryContext执行引擎取代了旧版的BaseViz(superset/viz.py),是 Superset 近几个大版本一次重要的执行引擎重构。
补充两个硬核细节(架构师会关心):
- 时间粒度(
granularity,比如"按天/按小时聚合")是通过 db_engine_spec 里的 Jinja2 模板翻译的——ClickHouse 的toStartOfDay()和 BigQuery 的TIMESTAMP_TRUNC()各不相同,就靠这一层抹平。 - SQL Lab 现在用 sqlglot 做 SQL 解析(已从 sqlparse 迁移),能识别语句里的聚合(
has_aggregate),供图表查询构建时自动补GROUP BY。
4.5 异步路径:为什么 SQL Lab 跑大查询页面不卡死
同步路径适合"图表秒开",但 SQL Lab 里动辄几亿行、跑几十秒的大查询,如果同步执行,Gunicorn 的 worker 进程会被占满,整个站点都被拖垮。
所以 Superset 把大查询切到 Celery 异步:
- 提交查询 → 后端立刻返回一个
query_id(页面不卡,用户可以去干别的); - 任务进 Redis 队列 → Celery Worker 抢到任务 → 去数仓执行;
- 结果写回
RESULTS_BACKEND; - 完成时通过 WebSocket + Redis Streams 推送"完成"事件 → 前端收到后拿
query_id拉结果。
这就是第 ③ 个问题的答案:Web 层无状态 + 异步 Worker 隔离,让"页面响应"和"重查询执行"彻底解耦。
4.6 元数据模型:四张业务表 + FAB 权限表
把元数据库的核心关系画出来,理解会更清晰:
- Database → Dataset(1:N):一个数据库连接可以建很多数据集;
- Dataset → Chart(1:N):一个数据集可以做很多图;
- Chart → Dashboard(N:N):一张图能出现在多个仪表盘,一个仪表盘含多张图;
- User/Role → Database / Dataset / Dashboard(RBAC 授权):权限关系是独立的边(含 database access),不跟数据血缘混在一起。
五、五级缓存体系:Redis 缓存 + Celery 异步的完整地图
前面讲了最核心的"图表数据缓存",但 Superset 其实有五级缓存,生产配置都在 superset_config.py 里:
| # | 缓存层 | 配置键 | 必填 | 缓存什么 |
|---|---|---|---|---|
| 1 | 仪表盘筛选状态 | FILTER_STATE_CACHE_CONFIG | ✅ | 原生筛选器的选中值 |
| 2 | Explore 表单数据 | EXPLORE_FORM_DATA_CACHE_CONFIG | ✅ | 图表编辑态的表单状态 |
| 3 | 元数据缓存 | CACHE_CONFIG | 可选 | 元数据、权限等热数据 |
| 4 | 图表数据缓存 | DATA_CACHE_CONFIG | 可选 | 数据集查询结果(性能核心) |
| 5 | SQL Lab 结果集 | RESULTS_BACKEND | — | 异步查询结果(cachelib,Flask 官方缓存对象库) |
几个生产经验(第 ③ 篇会展开):
- 第 1、2 级即使不配也不会崩——会兜底到
SupersetMetastoreCache(存元数据库),但并发高了会有性能损耗,建议显式配 Redis; - Redis 既做缓存又做 Celery 队列时,务必用不同 DB 编号(如
/0和/1),否则缓存淘汰可能误伤任务队列; - 缩略图缓存(
THUMBNAILS+ S3)会交给 Celery 异步生成,量大了记得单独隔离存储。
六、生产部署拓扑:从单机 Docker 到 K8s/Helm
单机开发一个 docker run 就够(Gunicorn 默认监听 8088);上生产,标准做法是 Helm 部署到 Kubernetes(或等价的多机拆分):
五个必须记住的生产要点:
- Web 无状态,随便扩——
web.replicaCount想调几调几,会话状态在 Redis; - Beat 必须单副本——否则周期任务(报表/告警)会被重复调度;
- Init Job 只跑一次——
db upgrade(Alembic 建表迁移)+superset init(初始化 Flask-AppBuilder 的角色权限)+ 建 admin;Web/Worker 的 Pod 都靠wait-for-init等它完成,避免并发建表死锁; - Redis 双角色用不同 DB 编号隔离——缓存和 Celery broker 别共用;
- 元数据库和生产别偷懒——SQLite 只配本地 quickstart,生产用 PostgreSQL(或 MySQL),并做好备份(它是唯一事实源)。
Superset 6.1 还引入了 Redis 分布式协调后端,进一步让多副本 Web 之间能共享一些协调状态——多副本部署的健壮性在持续加强。
这就是第 ④ 个问题的答案:Init Job 负责"一次性"的建表迁移与权限初始化,
wait-for-init让所有 Pod 等它跑完再启动——既避免多副本并发建表死锁,也保证元数据 schema 一定就绪。
七、架构决策与避坑清单
把这一路串起来,提炼成 6 条"架构师视角"的决策与坑:
| 决策 / 坑 | 正确姿势 | 踩了会怎样 |
|---|---|---|
| Superset 不存业务数据 | 数据留在数仓,只存"图纸+清单" | 硬塞进 Superset 反而破坏它直连即席的架构 |
| 生产必上 Redis | 缓存 + Celery 队列,分 DB 编号 | 没 Redis,多副本会话/缓存/异步全废 |
| 元数据库别用 SQLite | 生产用 PostgreSQL/MySQL + 备份 | SQLite 并发写会锁死、易损坏 |
| Gunicorn 超时要调 | web.timeout 默认 120s,按最慢查询调 | 慢查询把 worker 占满 → 全站假死 |
| Beat 单副本 | beat.enabled 且 replicaCount=1 | 报表/告警重复触发 |
| 大查询走异步 | SQL Lab 开 async + Celery Worker | 同步跑长查询拖垮整个 Web 层 |
八、系列导航与下篇预告
到这里,你已经有了一张"点图 → 出图"的完整心智地图。但"看懂架构"和"用好架构"之间,还差一层特性的最佳实践:
| 篇 | 主题 | 你将收获 |
|---|---|---|
| ① | Superset 是什么 + 核心概念 | ✅ 已完成 |
| ② | 架构深度解析 | ✅ 本文 |
| ③ | 重要特性详解 | 语义层建模最佳实践、RLS 实战、缓存调优、嵌入方案 |
| ④ | 同类竞品横评 | 选型决策树 + TCO 成本账 |
| ⑤ | 实战:Superset + ClickHouse | 从 0 搭建广告 Adhoc 分析平台 |
下一篇(第 ③ 篇)我会把这套架构落到具体配置与最佳实践:怎么设计语义层才不炸、RLS 怎么配才安全、五级缓存怎么调 TTL、怎么把仪表盘嵌入到你自己的系统里。
写在最后(求个三连 🙏)
如果这张"完整时序图"帮你把 Superset 的架构打通了,不妨:
- 点个赞 + 收藏——后续 3 篇会一篇比一篇实战;
- 评论区告诉我——你部署 Superset 时踩过哪些坑?是单机 Docker 还是 K8s/Helm?Redis 有没有分库?
- 关注我——专注数据架构 / 开源 BI / 广告数据工程的实战派。
声明:文中组件名、配置项、默认值(如 Gunicorn 8088、
web.timeout=120s)基于 Superset 6.x 与官方 Helm Chart,开源项目迭代快,如有出入请以官方文档为准,欢迎指正。
下一篇:Superset 深度解析(三):重要特性详解——语义层 / 权限 / 缓存 / 嵌入
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_43128865/article/details/170247719




