Metabase (https://github.com/metabase/metabase)是一个非常优秀且流行的开源商业智能(BI)工具。它的源码结构清晰,技术选型也很有特色。我会为你深入浅出地讲解 Metabase 的源码。
我们将从以下几个方面来剖析它:
-
高层概览与技术栈:Metabase 是什么?它用了哪些主要技术?
-
核心架构:它是如何设计的?各个部分如何协同工作?
-
关键目录结构解析:最重要的代码都在哪里?每个目录是做什么的?
-
一个核心工作流程的剖析:当用户“提问”时,背后发生了什么?
-
如何开始:如果你想为它贡献代码或进行二次开发,应该从何入手?
1. 高层概览与技术栈
Metabase 的定位是“一个让公司里每个人都能轻松从数据中学习和决策的工具”。它允许用户连接到各种数据库,通过简单的图形界面(或者直接写 SQL)来查询数据、创建图表和仪表盘。
核心技术栈:
-
后端 (Backend): Clojure。这是一个运行在 Java 虚拟机(JVM)上的 Lisp 方言。这是 Metabase 技术栈中最具特色的部分。选择 Clojure 带来了函数式编程的简洁性、强大的并发能力和与 Java 生态的无缝集成。
-
前端 (Frontend): React + TypeScript。这是现代 Web 应用非常主流的组合,用于构建用户交互界面。
-
数据库 (Application Database): Metabase 自身需要一个数据库来存储用户信息、仪表盘、问题(Questions)、数据源连接信息等。默认使用 H2(一个嵌入式 Java 数据库),但生产环境强烈建议使用 PostgreSQL 或 MySQL/MariaDB。
-
构建工具:
-
后端使用 Leiningen,这是 Clojure 的标准项目管理和构建工具。
-
前端使用 Yarn / Webpack,这是 JavaScript 生态的标准工具。
-
2. 核心架构
Metabase 的架构可以理解为一个经典的 单体 Web 应用 (Monolithic Application),但内部模块化做得很好。
可以分为以下几个关键部分:
-
Web 服务器 (Backend):
-
由 Clojure 编写,运行在 JVM 上。
-
负责处理所有 API 请求(来自前端)。
-
管理用户认证、权限控制。
-
连接并管理“应用数据库”。
-
-
前端应用 (Frontend):
-
一个单页面应用 (SPA),由 React 构建。
-
用户在浏览器中看到的所有界面,如图表构建器、仪表盘、管理后台等。
-
它通过调用后端的 REST API 与后端进行数据交互。
-
-
应用数据库 (Application DB):
-
存储 Metabase 自身的元数据(Metadata)。注意:这里不存储你的业务数据。
-
例如:你创建了一个名为“用户增长仪表盘”的 Dashboard,这个 Dashboard 的定义、包含哪些图表、布局如何等信息就存在这里。
-
-
数据源 (Data Warehouse / Source Databases):
-
这是你的业务数据所在的地方,比如公司的 PostgreSQL, MySQL, BigQuery, Snowflake 等。
-
Metabase 通过 “驱动” (Drivers) 来连接这些不同的数据库。
-
-
驱动 (Drivers):
-
这是 Metabase 架构的精髓之一。每个数据库(如 Postgres, MySQL)都有一个对应的 Driver。
-
Driver 的核心职责是 “翻译官”:
-
将 Metabase 内部的查询语言(MBQL)翻译成对应数据库的原生 SQL。
-
处理特定数据库的连接、数据类型转换等细节。
-
-
这种设计使得 Metabase 可以轻松地支持新的数据库,只需要编写一个新的 Driver 即可。
-
3. 关键目录结构解析
打开 GitHub 仓库,你会看到很多文件和目录。我们重点关注以下几个:
-
src/:后端 Clojure 源码的核心
-
src/metabase/:这是主要的命名空间。
-
api/:所有 REST API 的路由和处理逻辑都在这里。比如 card.clj 对应的是图表(Card/Question)相关的 API。这是前端和后端交互的入口。
-
query_processor/:查询处理器 (Query Processor, QP),这是 Metabase 的“大脑”。它负责接收查询请求,进行预处理、权限检查、编译、执行,并返回结果。
-
driver/:定义了所有数据库驱动需要实现的接口(协议)。比如,一个驱动必须能 can-connect?(测试连接)、execute-query(执行查询)等。具体的驱动实现代码在 modules/drivers/ 目录下。
-
models/:定义了 Metabase 应用数据库中的数据模型,如 User, Card, Dashboard 等,以及相关的数据库操作。
-
mbql/:Metabase Query Language (MBQL) 的相关代码,用于解析和处理这种内部查询语言。
-
-
-
frontend/:所有前端 React/TypeScript 源码
-
src/metabase/:前端应用的主要代码。
-
query_builder/:核心的“提问”界面,即图形化查询构建器的实现。
-
visualizations/:各种图表(表格、折线图、地图等)的渲染组件。
-
components/:可复用的 UI 组件。
-
services/:用于调用后端 API 的服务层。
-
-
-
resources/:非代码资源
-
migrations/:应用数据库的表结构变更脚本。当你升级 Metabase 版本时,它会运行这些脚本来更新数据库。
-
public/:前端打包后的静态资源(JS, CSS, 图片)会放在这里。
-
i18n/:国际化语言文件(.po 文件)。
-
-
modules/:模块化代码,主要是数据库驱动
-
drivers/:存放所有官方支持的数据库驱动。例如:
-
modules/drivers/postgres/:PostgreSQL 驱动的全部实现。
-
modules/drivers/mysql/:MySQL 驱动的实现。
-
-
这种模块化设计让社区可以方便地开发第三方驱动。
-
-
enterprise/:企业版功能的代码
-
Metabase 采用 Open Core 商业模式,核心功能开源,高级功能(如 SSO、审计日志等)闭源或需要付费。这个目录存放的就是企业版特有的功能代码。
-
-
bin/:一些有用的脚本,比如 run-metabase-tests.sh 用来运行测试。
-
project.clj:后端项目的定义文件 (类似 package.json 或 pom.xml)。它定义了项目依赖、插件、配置等。
4. 一个核心工作流程的剖析:用户“提问”
为了把上面的概念串起来,我们来看一个最常见的场景:用户通过图形界面查询“过去30天,按来源统计的用户注册数”。
-
前端 (UI 操作):
-
用户在 Metabase 的查询构建器 (Query Builder) 界面上选择 “Users” 表。
-
选择聚合操作为 “Count of rows”。
-
选择分组依据为 “Source” 字段。
-
添加一个过滤条件:“Created At” is in the “Previous 30 Days”。
-
-
前端 (构建请求):
-
当用户点击“获取答案”时,前端 React 应用会将用户的这些点击操作转换成一个 JSON 对象。这个 JSON 对象就是 MBQL (Metabase Query Language) 格式。它看起来可能像这样:
code JSONdownloadcontent_copy
expand_less{ "database": 2, "type": "query", "query": { "source-table": 12, "aggregation": [["count"]], "breakout": [["field", 34, null]], "filter": ["time-interval", ["field", 35, null], -30, "day"] } }
-
-
前端 (发送 API 请求):
-
前端将这个 MBQL 的 JSON 作为 payload,向后端发送一个 POST 请求,例如 POST /api/dataset。
-
-
后端 (API 层):
-
src/metabase/api/dataset.clj 文件中的路由接收到这个请求。
-
它会进行一些基本的校验,比如用户是否有权限查询这个数据库。
-
-
后端 (查询处理器 - Query Processor):
-
API 层将 MBQL 查询传递给查询处理器 (src/metabase/query_processor.clj)。
-
QP 是整个流程的核心,它会做几件事:
code SQL
a. 预处理 (Preprocessing):注入一些默认值,展开宏等。
b. 选择驱动 (Driver Selection):根据 MBQL 中的 database ID,找到对应的数据库驱动,比如 PostgreSQL 驱动。
c. 编译 (Compilation):调用 PostgreSQL 驱动,将 MBQL 翻译成原生的 PostgreSQL SQL 语句。这是驱动最重要的工作。翻译结果可能如下:downloadcontent_copy
expand_lessIGNORE_WHEN_COPYING_START
IGNORE_WHEN_COPYING_END
SELECT "source", count(*) FROM "public"."users" WHERE "created_at" >= (now() - interval '30 day') GROUP BY "source" ORDER BY "source" ASC;d. 执行查询 (Execution):驱动使用 JDBC 连接到用户的 PostgreSQL 数据库,并执行上面生成的 SQL 语句。
-
-
后端 (返回结果):
-
数据库返回查询结果给 Metabase 后端。
-
QP 对结果进行一些后处理(比如数据类型格式化),然后将其封装成一个标准的 JSON 格式返回给前端。
-
-
前端 (渲染):
-
前端接收到包含数据的 JSON 响应。
-
React 组件 (src/metabase/visualizations/) 根据数据和用户选择的图表类型(比如条形图),将数据渲染成可视化的图表。
-
整个过程就完成了!从用户点击到图表展示,代码在前后端、驱动之间完美协作。
5. 如何开始
如果你想深入研究代码或参与贡献:
-
环境搭建:
-
官方文档有非常详细的开发环境搭建指南。
-
你需要安装 Java JDK、Node.js、Yarn 和 Clojure 的命令行工具。
-
按照指南,你可以同时启动后端服务和前端开发服务器。
-
-
从哪里读起:
-
想了解后端逻辑:从 src/metabase/api/ 开始,选择一个你感兴趣的 API(比如 card.clj 或 dashboard.clj),看看它的路由是如何定义的,然后顺藤摸瓜找到它调用的核心服务,很可能会进入 query_processor 或 models。
-
想了解数据库支持:深入 modules/drivers/ 目录,选择一个你熟悉的数据库(如 postgres/),阅读 src/metabase/driver/sql_jdbc/execute.clj 和 src/metabase/driver/sql/query_processor.clj 等文件,看看它是如何将 MBQL 翻译成 SQL 的。
-
想了解前端:从 frontend/src/metabase/query_builder/ 或 frontend/src/metabase/dashboard/ 开始,这些是核心功能页面的入口组件。
-
-
贡献代码:
-
Metabase 的 GitHub Issues 列表里有很多标记为 Good first issue 的问题,非常适合新手入门。
-
在动手之前,最好先阅读他们的贡献指南。
-
希望这个详细的讲解能帮助你理解 Metabase 的源码结构和工作原理!它是一个设计精良的项目,非常值得学习。
2648

被折叠的 条评论
为什么被折叠?



