第 8 部分导论 · Grafana 与数据源抽象
Grafana 自己不存任何数据,它凭什么能一统可视化?答案是数据源抽象。这一章读 pkg/plugins 的插件接口,讲清 QueryData 这个统一契约、后端插件为什么是独立进程,以及 Grafana 在链条里的真正位置。
第 8 部分导论 · Grafana 与数据源抽象
数据采集了、存好了、能查了、能告警了、三支柱也串起来了。最后一个问题:怎么把它变成人能看懂的东西?
这是 Grafana 的活。而它有个反常识的起点。
它自己不存数据
Grafana 不是一个数据库。它不存任何时序数据、日志、trace。它是一个前端 + 查询代理**。**
你在 Grafana 上画的每一条曲线:
Grafana 前端 → Grafana 后端 → [Prometheus](/manual/observability/promql-model) → 返回数据 → 画图
↑ 它只是转发和渲染这个“不存数据”的定位,恰恰是它能一统可视化江湖的原因:
- 它不和任何后端竞争 —— 它不想取代 Prometheus/ES/Loki,它想成为它们所有人的前端。于是每个后端都乐意为它写数据源插件。
- 它可以支持一切 —— 因为不存数据,它对“数据长什么样”没有偏见。SQL 数据库、时序库、日志库、云厂商 API、甚至一个 CSV 文件,都能被抽象成“数据源”。
- 换后端不用换看板 —— 一定程度上。
对比第 0 部分讲的三个主角:Prometheus 是“存储引擎”问题、Collector 是“数据管道”问题、Grafana 是“统一抽象”问题——它要把千差万别的后端,统一成一个人能理解的心智模型。这是三类完全不同性质的工程难题。
核心契约:QueryData
看后端插件接口(真实源码 pkg/plugins/backendplugin/ifaces.go):
// Plugin is the backend plugin interface.
type Plugin interface {
PluginID() string
Start(ctx context.Context) error
Stop(ctx context.Context) error
// ...
backend.QueryDataHandler // ← 核心:查数据
backend.CheckHealthHandler // ← 健康检查(界面上那个"Save & Test")
backend.CallResourceHandler // ← 自定义 HTTP 端点(如 PromQL 自动补全)
backend.StreamHandler // ← 流式数据(实时推送)
backend.CollectMetricsHandler // ← 插件自己的指标
}QueryDataHandler 是整个 Grafana 的窄腰**:**
面板问:"给我这个时间范围的数据"
↓ QueryData(请求里带:查询语句、时间范围、最大点数、间隔)
插件答:"给你一组 DataFrame"所有数据源——Prometheus、MySQL、Loki、CloudWatch、Elasticsearch——都只需实现这一个方法。 它们内部千差万别(PromQL vs SQL vs LogQL vs 某云的 REST API),但对上只暴露 QueryData。
返回的 DataFrame 是另一半关键:它是个列式的表格结构(类似 Apache Arrow 的思路)——不管你查的是时序、日志还是表格,都被表达成 DataFrame。于是 Grafana 的面板只需要认识 DataFrame,不用认识 20 种后端格式。
这是“接口即边界”的又一个范例,和 Prometheus 的 storage.Querier、Collector 的 consumer 完全同构:一个小接口 + 一个统一的数据表示 = 生态可以无限长。
这三个项目,在三个层面,得出了同一个结论。
后端插件是独立进程
一个容易忽略但很重要的设计:
Grafana 的后端数据源插件不是动态库,而是独立进程——通过 gRPC 和 Grafana 主进程通信(用的是 HashiCorp 的 go-plugin 框架)。**
为什么这么麻烦?
- 隔离崩溃 —— 一个写得烂的第三方插件崩了,不会带走整个 Grafana。如果是动态库,一个 panic 就全完了。
- 隔离依赖 —— 插件可以用任何 Go 版本、任何依赖,不用和 Grafana 主程序的依赖打架。
- 可以用别的语言写 —— 理论上任何能说 gRPC 的语言。
- 能限制资源 —— 插件进程可以被单独限制 CPU/内存。
代价是:进程间通信有开销、部署更复杂、调试更麻烦。
Target 那个枚举(TargetNone/TargetInMemory/TargetLocal)透露了另一件事:有些插件其实跑在进程内(in_memory)——核心自带的数据源为了性能不走 gRPC。这是个务实的妥协:第三方插件要隔离,自家的可以信任。
Grafana 在链条里的位置
注意“告警”也在 Grafana 里 —— 这是近年的重要变化(unified alerting,源码 pkg/services/ngalert/,README 里叫它 “Next generation alerting”)。
Grafana 的告警和 Prometheus 的告警是两套并存的东西,容易混淆:
| Prometheus 告警 | Grafana 告警 | |
|---|---|---|
| 在哪求值 | Prometheus 自己(rules/) | Grafana 后端 |
| 能跨数据源吗 | ❌ 只能查自己的数据 | ✅ 能(它能查任何数据源) |
| 依赖 | 无外部依赖,单机自足 | 依赖 Grafana 活着 |
| 配置 | YAML 规则文件 | UI 或 provisioning |
怎么选?一个务实的判据:
- 核心的、必须最可靠的告警 → 放 Prometheus。理由就是第 1 部分那条:监控必须比被监控更可靠,而 Prometheus 无外部依赖。如果 Grafana 挂了,你的 SLO 告警不该跟着瞎。
- 需要跨数据源的、或者业务方要自助配的 → 放 Grafana。比如“指标异常 且 数据库里的订单表也异常”这种,只有 Grafana 能做。
别把所有告警都堆进 Grafana——那等于给你的告警链路加了一个新的单点。
这一部分怎么安排
Grafana 有 1.5 万个 Go/TS 文件,绝大部分是 UI 和业务。本部分只挑三个真正值得学的:
- 数据源抽象(本章)——
QueryData那个窄腰。 - 看板与查询 —— 变量、模板、以及“看板即代码”。
- Explore 与关联 —— 三支柱的桥在 UI 上怎么落地,收尾第 8 部分。
小结与下一章
Grafana 自己不存数据——它是前端 + 查询代理,而这个定位正是它能一统可视化的原因:不和任何后端竞争(所以人人乐意写插件)、对数据形态无偏见(什么都能抽象成“数据源”)。核心契约是 QueryDataHandler(pkg/plugins/backendplugin/ifaces.go):所有数据源不管内部是 PromQL/SQL/LogQL/REST,对上只暴露这一个方法,返回统一的列式 DataFrame——一个小接口 + 一个统一数据表示 = 生态无限长;这和 storage.Querier、consumer 完全同构,三个项目在三个层面得出同一结论。后端插件是独立进程(gRPC):隔离崩溃、隔离依赖、可换语言、可限资源;但 Target 枚举透露自家插件跑进程内以求性能——第三方要隔离,自家可信任。Grafana 也有告警(ngalert),和 Prometheus 告警并存:核心告警放 Prometheus(无外部依赖,Grafana 挂了 SLO 告警不该瞎)、跨数据源的放 Grafana——别把所有告警堆进 Grafana,那是给告警链路加单点。
看板怎么做才有用?下一章。👉 看板与查询。
参考资料
- [1] Grafana 源码
pkg/plugins/backendplugin/ifaces.go(Plugin接口与各 handler)、pkg/services/datasources/。 - [2] Grafana 源码
pkg/services/ngalert/README.md(unified alerting)。 - [3] Grafana 官方文档:Backend plugins、Data frames;关联三个主角。