爱折腾的架构师头像
Apache Superset 架构深度解析(二):一次查询从点击到渲染的完整旅程(附架构图 + 时序图)封面图

Apache Superset 架构深度解析(二):一次查询从点击到渲染的完整旅程(附架构图 + 时序图)

Apache Superset 架构深度解析(二):一次查询从点击到渲染的完整旅程(附架构图 + 时序图)

本文是「Apache Superset 深度解析」系列的第 2 篇。上一篇我们讲清了"Superset 是什么 + 核心概念";这一篇往下钻一层:当你在浏览器点开一张图,到画面渲染出来,这中间 1 秒到几秒里,后端到底发生了什么。

系列目录(持续更新):


📌 TL;DR · 30 秒速览

  1. 一条查询的两条路:Explore 同步直出 / SQL Lab 大查询走 Celery 异步,最终都汇聚到同一套「QueryContext 生成 SQL → 执行 → 缓存 → 渲染」。
  2. SQL 是谁生成的:QueryContext(构建查询)+ db_engine_spec(方言适配)+ SQLAlchemy(连接执行),三者各司其职。
  3. 性能命门是缓存:Superset 有五级缓存(筛选状态/表单/元数据/图表数据/结果集),生产必配 Redis。
  4. 生产形态: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 里,业务数据全在你自己的数仓里。

④ 数据层(被查询对象,不归 Superset 管)

③ 中间件层

② 异步层(有状态)

① Web 层(无状态,可水平扩展)

SQLAlchemy 直连

SQLAlchemy 直连

SQLAlchemy 直连

投递异步任务

投递异步任务

投递异步任务

执行 SQL

结果写回

定时触发

负载均衡 / Ingress

Gunicorn / Flask
副本 1

Gunicorn / Flask
副本 2

Gunicorn / Flask
副本 N

Celery Worker
× N

Celery Beat
(单副本)

Flower
(Celery 监控)

WebSocket 服务
(Node.js,Redis Streams)

Redis
缓存 + Celery broker
(不同 DB 编号隔离)

PostgreSQL
元数据库(唯一事实源)

ClickHouse / BigQuery /
Snowflake / MySQL / ...

这张图透露了三个关键设计决策(后面会逐一展开):

  1. Web 层完全无状态——所以能无限水平扩展,扩了也不怕会话丢失(会话状态在 Redis);
  2. Redis 一鱼两吃——既是"缓存"又是"Celery 消息队列",一个中间件扛两份活;
  3. PostgreSQL 是唯一事实源——图表定义、数据集、用户、权限、日志都在这里,数据仓库里只有业务数据。

三、核心:一次查询的完整时序

这是全文的"题眼"。把上面四个问题串成一条完整链路(含同步、异步两条分支):

数据仓库(ClickHouse) WebSocket 服务(Node.js) Celery Worker Redis 元数据库(PostgreSQL) 安全模型(RBAC+RLS) Gunicorn / Flask 浏览器(React SPA) 数据仓库(ClickHouse) WebSocket 服务(Node.js) Celery Worker Redis 元数据库(PostgreSQL) 安全模型(RBAC+RLS) Gunicorn / Flask 浏览器(React SPA) alt [④ 缓存命中(同步快路径)] [⑤ 缓存未命中(同步慢路径)] —— 异步路径:SQL Lab 大查询 / 开启 async —— 打开 Dashboard / Chart POST /api/v1/chart/data ① 鉴权 RBAC + 收集 RLS 规则 放行(或 403 拒绝) ② 读 chart 定义 + dataset 定义 (含 metric / 计算列 / 认证信息) 返回图表配置 ③ 查数据缓存(DATA_CACHE_CONFIG) 返回缓存结果 JSON → ECharts 渲染 ✅ ⑥ QueryContext 构建 SQL (db_engine_spec 方言适配 + 注入 RLS) ⑦ SQLAlchemy 执行 SQL 返回结果集 ⑧ 写缓存(TTL) JSON → ECharts 渲染 ✅ SQL Lab 提交大查询 创建 Query 记录,生成 query_id 立即返回 query_id(页面不卡) 投递任务到 Celery 队列 Worker 执行 SQL 返回结果 结果写回 RESULTS_BACKEND 发布「完成」事件到 Redis Streams WebSocket 服务订阅到事件 推送「完成」通知 用 query_id 请求结果 读 RESULTS_BACKEND 返回结果 返回结果 → 渲染 ✅

把这张图读三遍,你就掌握了 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 里的"图纸")

鉴权通过后,后端去元数据库读两样东西:

  1. Chart 定义:这个图是什么类型(折线/柱状/表格)、绑定了哪个 Dataset、用了哪些 metric、筛选条件、颜色、坐标轴配置;
  2. 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"这件事,拆成了三个职责清晰的组件:

提供方言规则
(时间粒度/limit/类型转换)

构建 SQLAlchemy 查询对象

编译并执行 SQL

QueryContext
(含 QueryObject + datasource)

db_engine_spec
(方言适配层)

SQLAlchemy 查询对象
(编译 + 执行)

数据仓库

三个组件的分工:

组件职责一句话
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 异步:

  1. 提交查询 → 后端立刻返回一个 query_id(页面不卡,用户可以去干别的);
  2. 任务进 Redis 队列 → Celery Worker 抢到任务 → 去数仓执行;
  3. 结果写回 RESULTS_BACKEND;
  4. 完成时通过 WebSocket + Redis Streams 推送"完成"事件 → 前端收到后拿 query_id 拉结果。

这就是第 ③ 个问题的答案:Web 层无状态 + 异步 Worker 隔离,让"页面响应"和"重查询执行"彻底解耦。

4.6 元数据模型:四张业务表 + FAB 权限表

把元数据库的核心关系画出来,理解会更清晰:

1 : N

1 : N

N : N

RBAC 授权

RBAC 授权

RBAC 授权(database access)

Database
数据库连接

Dataset
数据集

Chart
图表

Dashboard
仪表盘

User / Role
用户 / 角色

  • 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✅原生筛选器的选中值
2Explore 表单数据EXPLORE_FORM_DATA_CACHE_CONFIG✅图表编辑态的表单状态
3元数据缓存CACHE_CONFIG可选元数据、权限等热数据
4图表数据缓存DATA_CACHE_CONFIG可选数据集查询结果(性能核心)
5SQL 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(或等价的多机拆分):

Init Job(一次性)

Beat Pod(必须单副本)

Worker Pod(replicaCount=N)

Web Pod(无状态,replicaCount=N)

入口

Ingress / LB

Gunicorn × web.workers
(默认每 Pod 2 进程)

Celery worker
(concurrency=2)

Celery Beat

db upgrade + superset init
+ 建 admin

PostgreSQL 元数据库

Redis(缓存/队列)

数据仓库

五个必须记住的生产要点:

  1. Web 无状态,随便扩——web.replicaCount 想调几调几,会话状态在 Redis;
  2. Beat 必须单副本——否则周期任务(报表/告警)会被重复调度;
  3. Init Job 只跑一次——db upgrade(Alembic 建表迁移)+ superset init(初始化 Flask-AppBuilder 的角色权限)+ 建 admin;Web/Worker 的 Pod 都靠 wait-for-init 等它完成,避免并发建表死锁;
  4. Redis 双角色用不同 DB 编号隔离——缓存和 Celery broker 别共用;
  5. 元数据库和生产别偷懒——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 的架构打通了,不妨:

  1. 点个赞 + 收藏——后续 3 篇会一篇比一篇实战;
  2. 评论区告诉我——你部署 Superset 时踩过哪些坑?是单机 Docker 还是 K8s/Helm?Redis 有没有分库?
  3. 关注我——专注数据架构 / 开源 BI / 广告数据工程的实战派。

声明:文中组件名、配置项、默认值(如 Gunicorn 8088、web.timeout=120s)基于 Superset 6.x 与官方 Helm Chart,开源项目迭代快,如有出入请以官方文档为准,欢迎指正。


下一篇:Superset 深度解析(三):重要特性详解——语义层 / 权限 / 缓存 / 嵌入

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_43128865/article/details/170247719

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--