Metabase二次开发教程-03-Clojure运行解析
-
一、程序启动
Clojure有多种启动方式,推荐使用 lein run
- lein ring server
- 加载 ring 环境,包括 exclude-tests、include-all-drivers、lein-ring
include-all-drivers 会对所有数据库驱动进行加载,并在终端输出进度,耗时较长; 即使在环境中注释掉include-all-drivers,后续init时依然会加载,即注释掉不会产生影响;
- 加载metabase.core引用的命名空间,耗时较长。某些命名空间在加载过程中会输出到终端;
示例:2020-12-11 18:21:38,879 INFO metabase.util :: Maximum memory available to JVM: 3.6 GB
- 加载 metabase.core,加载过程中有输出
示例:Copyright © 2020 Metabase, Inc.
- 运行 metabase.core/init!
- 启动ring服务,监听端口(默认3000),并将收到的请求交给handler处理
- 加载 ring 环境,包括 exclude-tests、include-all-drivers、lein-ring
- lein run
- 加载 run 环境,包括 exclude-tests、include-all-drivers
- 加载 :main 指定的入口命名空间,此处为metabase.core
- 运行 metabase.core/-main,如果带有cmd指令会进入 metabase.cmd/run-cmd 运行相应指令,否则进入metabase.core/start-normally 启动程序
- 在 start-normally 中,会运行 metabase.core/init!并通过jetty启动服务器,相关运行配置在 metabase.config中配置
- lein repl
- 打开一个交互服务器,可以执行输入的代码并实时返回结果。
- 启动后命名空间为user,即不会主动加载项目代码,需要手动 require 以加载环境
- 在project.clj中配置 :repl-options { :init-ns test.core }, 可以指定默认加载空间
二、API请求
API官方文档可参阅 API Document
如需追踪工作流,可以在 resources/log4j2.xml 中调低相关日志等级- 统一API请求入口
- 收到API请求后,统一调用 metabase.handler/app 处理
- 调用 metabase.routes/routes 进行请求分流,最核心的是处理数据请求的 /api 接口,其它接口主要是辅助功能
- 对于api请求,进入 metabase.api.routes/routes 对于不同的接口进行二次分流
- 回到metabase.handler/app,对结果进行一系列的封装,如捕获异常、输出日志、转JSON等等
- 数据查询接口
临时查询基本都走/api/dataset接口,保存的图表会有不同的接口入口,但是都会从card表中查SQL,然后通过qp/process-query-and-save-execution!执行- GET /api/pulse/preview_card_info/4
- 在pulse中查看数据预览时调用该接口
- GET接口,有效参数仅card_id,后端通过数据库获取query
调用 metabase.api.pulse 中 GET "/preview_card_info/:id" 路由; 将card_id传入 api/read-check 函数,查询card信息(包括sql语句) 将card传入 pulse-card-query-results 函数,获取数据; 调用 qp/process-query-and-save-execution! 执行查询 将数据直接转换成HTML格式的图表,放在 pulse_card_html中返回给前端; 前端直接渲染,无需调用图表方法(邮件中也可以直接渲染)。
- POST /api/card/1/query
- 打开已保存的数据看板/Question时调用该接口,但如果对card条件进行更改,刷新数据时会调用dataset
- 接口调用上传参数为空,有效参数仅card_id,后端通过数据库获取query
调用 metabase.api.card 中 POST "/:card-id/query" 路由; 将card_id传入 run-query-for-card-async 函数,获取数据; 将card_id传入 api/read-check,查询card信息(包括sql语句) 调用 qp/process-query-and-save-execution! 执行查询
- POST /api/dataset
- 所有其它调用都使用该接口,包括x-ray,ask question, SQL取数等
调用 metabase.api.dataset 中 POST "/" 路由; 增加中间件 :js-int-to-string? 调用 qp/process-query-and-save-with-max-results-constraints! 组装SQL语句 增加中间件 :add-default-userland-constraints? 调用 qp/process-query-and-save-execution! 组装SQL语句 调用 qp.streaming/streaming-response 执行并组装返回结果
- GET /api/pulse/preview_card_info/4
- DP流程
- qp/process-query-and-save-execution!之后进入DP流程,以下为流程详解
qp/process-query-and-save-execution!,此时收到的query可能为SQL语句,也可能是HoneySQL 调用qp/process-userland-query,在此区分同步和异步查询,但之后其实都是调异步查询函数qp/process-userland-query-async,只是同步查询会在这里先加上一个 wait-for-async-result 方法 调用 qp/base-qp,这里定义并调用了一个内部函数qp,里面运行了两个最关键的函数: qp.reducible/combine-middleware,这个函数会先用 qp.reducible/pivot 生成一个基础执行器,然后用 qp/userland-middleware 中定义的40个中间件逐个去包装它,最后返回包装好的执行器 qp.reducible/async-qp, 这里定义了一个内部函数 thunk 用于把query放入执行器执行,并捕获错误,并且可以另起一个线程来运行执行器。
- 出于二次开发需要,需进一步了解执行器的流程,展开如下:
当qp.reducible/async-qp调用执行器之后,会将原始query传递给qp.reducible/pivot, 44个中间件对执行器进行加工 调用qp.context/runf,然后再调用qp.context/executef进行取数,此时的query已经被组装为可执行的SQL 由于数据可能存在不同的数据源中,因此实际取数要调用driver去执行,主程序中只调用了REST接口,然后等待数据返回 主程序中虽然引入了toucan,但只用于读取元数据,并不用于业务取数,因此主流程中找不到toucan操作。
- Middleware
- 一个普通的查询会遍历44个middleware,先遍历3个userland附加的middleware,如下表从下往上执行:
metabase.query-processor.middleware.constraints/add-default-userland-constraints # 如果参数中 :add-default-userland-constraints? 为 true,则在query中添加 :constraints {:max-results 10000, :max-results-bare-rows 2000} metabase.query-processor.middleware.process-userland-query/process-userland-query # 对于面向用户的查询(userland query),记录查询日志并包装返回的结果 metabase.query-processor.middleware.catch-exceptions/catch-exceptions # 捕获qp过程中的异常
- 然后是41个 process-query 默认的middleware, 从下往上执行
#'metabase.query-processor.middleware.mbql-to-native/mbql->native #'metabase.query-processor.middleware.check-features/check-features #'metabase.query-processor.middleware.optimize-datetime-filters/optimize-datetime-filters #'metabase.query-processor.middleware.auto-parse-filter-values/auto-parse-filter-values #'metabase.query-processor.middleware.wrap-value-literals/wrap-value-literals #'metabase.query-processor.middleware.annotate/add-column-info #'metabase.query-processor.middleware.permissions/check-query-permissions #'metabase.query-processor.middleware.pre-alias-aggregations/pre-alias-aggregations #'metabase.query-processor.middleware.cumulative-aggregations/handle-cumulative-aggregations #'metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions/apply-row-level-permissions #'metabase.query-processor.middleware.resolve-joined-fields/resolve-joined-fields #'metabase.query-processor.middleware.resolve-joins/resolve-joins #'metabase.query-processor.middleware.add-implicit-joins/add-implicit-joins #'metabase.query-processor.middleware.large-int-id/convert-id-to-string #'metabase.query-processor.middleware.limit/limit #'metabase.query-processor.middleware.format-rows/format-rows #'metabase.query-processor.middleware.desugar/desugar #'metabase.query-processor.middleware.binning/update-binning-strategy #'metabase.query-processor.middleware.resolve-fields/resolve-fields #'metabase.query-processor.middleware.add-dimension-projections/add-remapping #'metabase.query-processor.middleware.add-implicit-clauses/add-implicit-clauses #'metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions/apply-row-level-permissions #'metabase.query-processor.middleware.add-source-metadata/add-source-metadata-for-source-queries #'metabase-enterprise.sandbox.query-processor.middleware.column-level-perms-check/maybe-apply-column-level-perms-check #'metabase.query-processor.middleware.reconcile-breakout-and-order-by-bucketing/reconcile-breakout-and-order-by-bucketing #'metabase.query-processor.middleware.auto-bucket-datetimes/auto-bucket-datetimes #'metabase.query-processor.middleware.resolve-source-table/resolve-source-tables #'metabase.query-processor.middleware.parameters/substitute-parameters #'metabase.query-processor.middleware.resolve-referenced/resolve-referenced-card-resources #'metabase.query-processor.middleware.expand-macros/expand-macros #'metabase.query-processor.middleware.add-timezone-info/add-timezone-info #'metabase.query-processor.middleware.splice-params-in-response/splice-params-in-response #'metabase.query-processor.middleware.resolve-database-and-driver/resolve-database-and-driver #'metabase.query-processor.middleware.fetch-source-query/resolve-card-id-source-tables #'metabase.query-processor.middleware.store/initialize-store #'metabase.query-processor.middleware.cache/maybe-return-cached-results #'metabase.query-processor.middleware.validate/validate-query #'metabase.query-processor.middleware.normalize-query/normalize #'metabase.query-processor.middleware.add-rows-truncated/add-rows-truncated #'metabase-enterprise.audit.query-processor.middleware.handle-audit-queries/handle-internal-queries #'metabase.query-processor.middleware.results-metadata/record-and-return-metadata
转载自:https://blog.csdn.net/weixin_43821438/article/details/111051486
- lein ring server