第 8 部分导论 · Grafana 与数据源抽象

Grafana 自己不存任何数据,它凭什么能一统可视化?答案是数据源抽象。这一章读 pkg/plugins 的插件接口,讲清 QueryData 这个统一契约、后端插件为什么是独立进程,以及 Grafana 在链条里的真正位置。

作者 David更新于 第 47 篇(共 52 篇)

第 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 和业务。本部分只挑三个真正值得学的:

小结与下一章

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;关联三个主角。

本页目录