一套数据监控方案Prometheus/Grafana 与 SkyWalking/Elasticsearch 的选型与搭建

一、先说清楚这四个东西到底是什么

写之前先把概念摆正,不然后面配置容易串。这四个组件里其实只有两种角色:采集/存储,和展示。用四张卡片一次看明白:

① 采集 + 存储指标库

Prometheus

CNCF 毕业项目,定位是「指标(metrics)监控 + 时间序列数据库」。核心工作方式一句话:主动去抓(scrape)。配置里写好一批目标地址,它每隔 15 秒自己跑过去把 /metrics 接口的数据拉回来,存进自己的时序数据库(TSDB)。数据模型是「指标名 + key-value 标签」,查询用 PromQL,告警规则也是 PromQL 写的。Go 静态编译的单文件,部署几乎零依赖。

⚠️ 默认本地存 15 天(可配),单机为主;集群高可用得靠 Thanos、Cortex。它不擅长存日志和链路。

② 展示可视化

Grafana

纯可视化层,自己不采集、不存储任何数据。把各种数据源(Prometheus、MySQL、Elasticsearch、InfluxDB……)接进来,画成仪表盘。关键点:只要数据源支持,什么都能画——接 Prometheus 画指标,接 MySQL 写 SQL 查业务表,接 ES 做日志检索面板,全行。所以跟 Prometheus 是「黄金搭档」,但两者不绑定,本文里它同时出现在两个组合中就是这个道理。

③ 采集 + 存储APM

SkyWalking

Apache 顶级项目,APM(应用性能监控)+ 分布式链路追踪。盯的东西比 Prometheus 更「应用层」:一次请求穿过 网关 → 订单 → 支付 → 数据库,整条调用链各节点耗时、哪里慢、哪里报错,它能还原出来。架构分三块:

  • Agent 探针:挂应用进程里,gRPC 上报,Java 用 -javaagent,无需改代码;
  • OAP:接收端,分析聚合后再写存储;
  • UI + 存储:自带 Booster UI,存储可插拔(推荐 ES7/8)。
④ 存储搜索引擎

Elasticsearch

本质是搜索引擎(分布式、倒排索引、近实时),在监控方案里通常当存储引擎用:写快、按时间建索引、聚合强。配 Logstash + Kibana 叫 ELK。但在「SkyWalking + ES」里 Kibana 不是必须的——SkyWalking UI 直接读 ES,链路图、拓扑图都是它自己画的,ES 只是安静存数据。

⚠️ 7.11 起改用 SSPL/Elastic License 2.0,与 Apache 2.0 不兼容。有顾虑可选 7.11 前版本或换 OpenSearch。


二、两个组合怎么选:一张表看明白

比较项 Prometheus + Grafana SkyWalking + Elasticsearch
核心定位 基础设施 + 业务指标监控 应用性能 + 分布式链路追踪
数据采集方式 Pull,服务端主动去抓 /metrics Push,Agent 探针主动上报 gRPC
采集端形态 Exporter(node / mysqld / cadvisor 等) 各语言 Agent(Java/Go/Python…)+ 插件生态
数据类型 指标(metrics)为主,时间序列数值 指标 + 链路 trace + 日志 + 拓扑关系
存储 Prometheus 自带 TSDB(本地磁盘) SkyWalking OAP 写入 Elasticsearch
查询语言 PromQL,灵活强大,能算速率、分位、聚合 OAL/MAL(内部聚合),UI 直接出图,一般不直接写查询
可视化能力 Grafana 仪表盘生态成熟,模板海量,可叠加多数据源 SkyWalking 自带 UI,拓扑图、服务地图、trace 瀑布图开箱即用
链路追踪 需配合 OpenTelemetry/Tempo/Jaeger,本身不强项 原生强项,自动还原调用链,慢 SQL、瓶颈一目了然
服务拓扑 需手动配 + 依赖 Service Mesh 元数据 自动生成服务依赖拓扑
告警 内建 Alertmanager,PromQL 规则,路由分组抑制齐全 需外接(Webhook、Prometheus 兼容格式或自身告警规则)
接入成本 低,加个 Exporter + 配 scrape 即可 中高,需挂载 Agent、调字节码增强、规划 ES 集群
资源开销 单进程几百 MB 起步,很轻 ES 是内存大户,需 bootstrap.memory_lockvm.max_map_count 调优,OAP 也要 JDK
典型场景 CPU/内存/磁盘、MySQL 连接数、Redis、Docker、K8s 资源监控 微服务调用链排查、接口耗时分析、慢 SQL 定位、服务依赖梳理
一句话总结 「系统健不健康」的指标大盘 「请求为什么慢」的链路诊断
我的实际经验:这两个组合不是二选一,而是互补。小团队先做 Prometheus + Grafana,把机器、中间件、数据库的指标大盘先跑起来,告警接企业微信/钉钉;等业务上了微服务、开始有人问「这个接口到底卡在哪个服务」,再加 SkyWalking + ES。最终形态是 Prometheus 抓指标、SkyWalking 抓链路、Grafana 统一出图(Grafana 能同时挂 Prometheus 和 ES 两个数据源)。如果硬要选一个:纯运维监控 → Prometheus 组合;纯 Java 微服务平台排查 → SkyWalking 组合。

三、Prometheus + Grafana 搭建

3.1 环境说明

前置就一条:sudo apt install docker.io docker-compose-plugin,然后把当前用户加进 docker 组。

3.2 工作流程图

被监控端
node-exporter:9100
cadvisor:8080
mysqld-exporter:9104
MySQL业务库
查 performance_schema
监控服务端
Prometheus:9090 · TSDB 本地存储
Grafana:3000
Alertmanager:9093
用户端
浏览器用户 / 开发
▼ Pull 方向:Prometheus 每 15s 主动 scrape /metrics;浏览器 → Grafana(3000) → Prometheus(9090)

箭头方向的三个要点:
1. Pull 架构:Prometheus 是主动方,Exporter 被动暴露 HTTP 接口,这是它和 SkyWalking 最大的架构差异;
2. mysqld-exporter → MySQL:它不是被抓的,而是去查 MySQL 的 performance_schema,把结果转成 metrics 再被 Prometheus 抓,所以要先建监控账号(见 3.6);
3. Grafana 代理模式(access: proxy):浏览器不直接连 Prometheus,只暴露 3000 端口,运维友好。

3.3 目录结构

~/monitoring/
├── docker-compose.yml
├── prometheus/
│   ├── prometheus.yml
│   └── rules/
│       └── alert.yml
└── grafana/
    └── provisioning/
        ├── datasources/
        │   └── prometheus.yml
        └── dashboards/
            └── provider.yml

3.4 docker-compose.yml

version: "3.8"

services:
  prometheus:
    image: prom/prometheus:v3.14.0
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./prometheus/rules:/etc/prometheus/rules:ro
      - prom_data:/prometheus
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.path=/prometheus
      - --storage.tsdb.retention.time=30d
      - --web.enable-lifecycle
    networks: [mon]

  mysqld-exporter:
    image: prom/mysqld-exporter:v0.15.1
    container_name: mysqld-exporter
    restart: unless-stopped
    ports:
      - "9104:9104"
    environment:
      DATA_SOURCE_NAME: "exporter:ExporterPass123@(192.168.1.20:3306)/"
    command:
      - --collect.info_schema.innodb_metrics
      - --collect.info_schema.processlist
      - --collect.info_schema.tables
    depends_on: [mysql]
    networks: [mon]

  # MySQL 不在本机可删掉这一段,只把上面 IP 改成实际地址即可
  mysql:
    image: mysql:8.0
    container_name: mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: RootPass123
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks: [mon]

  grafana:
    image: grafana/grafana:12.0.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123
    volumes:
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - grafana_data:/var/lib/grafana
    depends_on: [prometheus]
    networks: [mon]

  alertmanager:
    image: prom/alertmanager:latest
    container_name: alertmanager
    restart: unless-stopped
    ports:
      - "9093:9093"
    volumes:
      - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
    networks: [mon]

networks:
  mon:
    driver: bridge

volumes:
  prom_data:
  grafana_data:
  mysql_data:
几点实战提醒:

3.5 prometheus.yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

rule_files:
  - /etc/prometheus/rules/*.yml

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'docker'
    static_configs:
      - targets: ['cadvisor:8080']

  - job_name: 'mysql'
    static_configs:
      - targets: ['mysqld-exporter:9104']
        labels:
          instance: 'main-mysql'
这是全文最容易出错的文件:

3.6 MySQL 监控账号(必做)

mysqld-exporter 需要专门权限,用 root 会报权限错误,而且建议限流:

CREATE USER 'exporter'@'%' IDENTIFIED BY 'ExporterPass123' WITH MAX_USER_CONNECTIONS 10;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
GRANT SELECT ON performance_schema.* TO 'exporter'@'%';
FLUSH PRIVILEGES;

MAX_USER_CONNECTIONS 10 这条很重要——监控抓取在高并发下可能把数据库连爆,这是生产环境踩过的坑。

3.7 Grafana 数据源配置(provisioning)

用 provisioning 的好处是:数据源写进文件,容器重启不丢,还能进 Git 版本管理。

grafana/provisioning/datasources/prometheus.yml

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: false
    jsonData:
      timeInterval: 15s
      httpMethod: POST

如果还想把 MySQL 业务库直接挂进来做报表(文章要求里提到了 MySQL 数据源),再追加一段:

  - name: MySQL-Business
    type: mysql
    access: proxy
    url: mysql:3306
    database: orders
    user: grafana_ro
    secureJsonData:
      password: GrafanaRoPass
    jsonData:
      maxOpenConns: 10

grafana/provisioning/dashboards/provider.yml

apiVersion: 1

providers:
  - name: 'default'
    orgId: 1
    folder: ''
    type: file
    disableDeletion: false
    editable: true
    options:
      path: /etc/grafana/provisioning/dashboards

注意:provisioning 的数据源在 UI 里是锁定的(editable: false),想改要去改文件。另外 provisioning 目录挂载时只挂载到 provisioning 这一层,不要覆盖整个 /etc/grafana,否则默认配置丢了 Grafana 起不来。

3.8 启动与验证

cd ~/monitoring
docker compose up -d

# 看状态
docker compose ps
docker compose logs -f prometheus

# 验证 exporter 有数据输出
curl http://localhost:9104/metrics | grep mysql_up

浏览器打开 http://host:3000,默认账号 admin/admin123,左侧 Connections → Add new connection → Prometheus → Save & test,看到 Successfully queried the Prometheus API 就通了。

仪表盘建议直接导入官方模板:MySQL 用 ID 7362,Node Exporter 用 ID 1860,在 Dashboards → Import 里填数字即可。自己写面板时 PromQL 常用套路:

# CPU 使用率(node-exporter)
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# MySQL 当前连接数
mysql_global_status_threads_connected

# 慢查询率
rate(mysql_global_status_slow_queries[5m])

到这里 Prometheus + Grafana 这条线就跑通了:指标采集 → 存储 → 查询 → 图表 → 告警 全链路闭环。


四、SkyWalking + Elasticsearch 搭建

4.1 工作流程图

应用侧
订单服务-javaagent:agent.jar
支付服务Python / Go Agent
服务端
SkyWalking OAP:11800 gRPC · :12800 API
Elasticsearch:9200 · sw_* 索引
SkyWalking UI:8080
使用方
浏览器开发 / 运维
Kibana:5601 可选
▲ Push 方向:Agent → OAP(11800) → 写 ES;UI/Kibana → 读 sw_* 索引

对比 Prometheus 的三个关键差异:
1. 数据流是 Push:Agent 主动往 OAP 推,不是 OAP 来抓。网络策略上是应用 → 服务端开放 11800;
2. Agent 挂在应用进程内:Java 是字节码增强,无需改代码,但会占用一定堆内存,JIT 预热期有轻微性能损耗;
3. ES 是核心依赖:OAP 本身不存数据(默认 H2 仅用于试玩),所有 trace/metric/log 都写进 ES 索引,命名规则如 sw_metrics-all-YYYYMMDDsw_segment-YYYYMMDDsw_log-YYYYMMDD

4.2 版本兼容(必看,踩坑重灾区)

SkyWalkingElasticsearch说明
10.xES 7.x / 8.x统一用 selector: elasticsearch自动识别版本,不用再区分 es7/es8 安装包
9.x 及更早ES 6 用 selector: elasticsearch;ES 7 用 selector: elasticsearch7下载时也要选对应 -es7
结论:直接上 SkyWalking 10.x + ES 8.x,配置文件里 selectorelasticsearch 就行,不用纠结 es7 还是 es8。旧教程里大量「改 elasticsearch7」的写法,对 10.x 已经过时了。本例版本:apache-skywalking-apm:10.4.0docker.elastic.co/elasticsearch/elasticsearch:8.15.0kibana:8.15.0

4.3 docker-compose.yml

version: "3.8"

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
    container_name: es01
    restart: unless-stopped
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=true
      - xpack.security.http.ssl.enabled=false
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms1g -Xmx1g"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es_data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    networks: [sw]

  kibana:
    image: docker.elastic.co/kibana/kibana:8.15.0
    container_name: kibana
    restart: unless-stopped
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://es01:9200
    depends_on: [elasticsearch]
    networks: [sw]

  oap:
    image: apache/skywalking-oap-server:10.4.0
    container_name: oap
    restart: unless-stopped
    environment:
      - SW_STORAGE=elasticsearch
      - SW_STORAGE_ES_CLUSTER_NODES=http://es01:9200
      - SW_ES_USER=elastic
      - SW_ES_PASSWORD=ElasticPass123
      - SW_STORAGE_ES_SSL_VERIFICATION=false
      - SW_NAMESPACE=skywalking
    ports:
      - "11800:11800"   # gRPC,Agent 上报
      - "12800:12800"   # HTTP,UI 查询
    depends_on: [elasticsearch]
    networks: [sw]

  ui:
    image: apache/skywalking-ui:10.4.0
    container_name: skywalking-ui
    restart: unless-stopped
    environment:
      - SW_OAP_ADDRESS=http://oap:12800
    ports:
      - "8080:8080"
    depends_on: [oap]
    networks: [sw]

networks:
  sw:
    driver: bridge

volumes:
  es_data:
ES 单机调优三个必做项(不做基本起不来或很快崩):
  1. bootstrap.memory_lock=true + ulimits.memlock = -1:锁内存防 swap。宿主机还要 echo '* soft memlock unlimited' >> /etc/security/limits.conf
  2. vm.max_map_countsysctl -w vm.max_map_count=262144,并写进 /etc/sysctl.conf 持久化,ES 官方硬性要求;
  3. ES_JAVA_OPTS 堆不超过物理内存一半、且 ≤ 32G:这里给 1g 是开发环境,生产建议 4-8g。

4.4 OAP 存储配置(application.yml 关键段)

容器方式其实不需要手动改配置文件——上面 compose 里的环境变量会覆盖默认值。但用二进制部署或想看清结构时,核心就是这一段:

storage:
  selector: ${SW_STORAGE:elasticsearch}
  elasticsearch:
    namespace: ${SW_NAMESPACE:"skywalking"}
    clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}
    protocol: ${SW_STORAGE_ES_HTTP_PROTOCOL:"http"}
    user: ${SW_ES_USER:""}
    password: ${SW_ES_PASSWORD:""}
    sslVerification: ${SW_STORAGE_ES_SSL_VERIFICATION:true}
    indexShardsNumber: ${SW_STORAGE_ES_INDEX_SHARDS_NUMBER:1}
    indexReplicasNumber: ${SW_STORAGE_ES_INDEX_REPLICAS_NUMBER:1}
    bulkActions: ${SW_STORAGE_ES_BULK_ACTIONS:1000}
    flushInterval: ${SW_STORAGE_ES_FLUSH_INTERVAL:10}
    concurrentRequests: ${SW_STORAGE_ES_CONCURRENT_REQUESTS:2}

4.5 应用接入 Agent(Java 示例)

这是 SkyWalking 真正发挥价值的地方。以 Spring Boot 为例:

# 1. 下载 agent 包,解压得到 skywalking-agent/ 目录
wget https://archive.apache.org/dist/skywalking/10.4.0/apache-skywalking-java-agent-9.5.0.tgz
tar -xzf apache-skywalking-java-agent-9.5.0.tgz

# 2. 启动应用时挂载 agent
java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar \
     -Dskywalking.agent.service_name=order-service \
     -Dskywalking.collector.backend_service=192.168.1.10:11800 \
     -jar order-service.jar

其它语言:Go 用 go2sky,Python 用 skywalking-python,原理一样,都是应用内 SDK + gRPC 上报。

4.6 启动与验证

cd ~/skywalking
docker compose up -d

# 1. 确认 ES 健康(首次启动约 30 秒)
curl -u elastic:ElasticPass123 http://localhost:9200/_cluster/health

# 2. 看 OAP 是否开始建索引(出现 sw_ 开头索引就成功)
curl -u elastic:ElasticPass123 http://localhost:9200/_cat/indices | grep sw_

# 3. 查看 OAP 日志
docker compose logs -f oap

浏览器打开 http://host:8080,左侧菜单:Dashboard(服务概览)、Topology(拓扑图,微服务依赖自动画出)、Trace(链路列表)、Log(日志,需单独配置)。

验证数据通不通最简单:随便调几次业务接口,再去 Trace 页面刷新,能看到调用链就说明 Agent → OAP → ES 全链路 OK。Kibana 可选——想直接在 ES 查原始 trace 做定制分析,打开 http://host:5601,用索引模式 sw_segment-* 建 Data View 用 KQL 检索即可,日常监控用 SkyWalking UI 足够。

4.7 常见坑速查

现象原因解决
ES 启动后自动退出max_map_count 未设置sysctl -w vm.max_map_count=262144
ES 集群状态 Yellow单节点但副本数=1indexReplicasNumber: 0
OAP 连不上 ES,报 SSL 错8.x 默认开 SSLxpack.security.http.ssl.enabled=false(测试)或配证书
Agent 无数据上报gRPC 端口写错确认是 11800,不是 12800
-javaagent 不生效参数位置在 -jar 之后移到 -jar 之前
SkyWalking UI 空白OAP 未就绪等 OAP 完全启动,日志出现 OAP starts up successfully

五、落地建议

两套都搭完后,我建议的监控分层是这样:

接入层
Nginx / 网关blackbox_exporter 探活
应用层
SkyWalking Agent → OAP → ES链路 + 拓扑 + 日志
资源层
node + cadvisor + mysqld → Prometheus指标
展示 / 告警
Grafana统一出图(双数据源)
Alertmanager企业微信 / 钉钉

优先级的经验之谈:

  1. 第一周:先把 Prometheus + Grafana 跑起来,接 node-exporter + mysqld-exporter,CPU/内存/磁盘/连接数/慢查询这五块先有数;
  2. 第二周:配告警规则,把「磁盘 > 85%」「MySQL 连接数打满」「服务 down」这几条最救命的规则先建上;
  3. 第三周:微服务上规模再加 SkyWalking,先把 Agent 接进 1-2 个核心服务试水,确认 ES 容量够再全量铺开;
  4. ES 容量要提前规划:链路全量采集数据量很大,SkyWalking 默认按天建索引,配合 ES 的 ILM 做热温冷架构,或干脆设采样率。中小团队 10%-20% 采样 + 错误/慢请求全采通常够用。
最后一句实在话:监控方案最容易犯的错误是「采集了很多,没人看」。别一上来就铺几十个仪表盘,先把核心 3-5 个黄金指标(延迟、流量、错误、饱和度,即 RED/USE 方法论)盯牢,告警能真正触达人,这套系统才算落地。剩下的是迭代的事。
本文为程序员视角的技术总结,版本号请以各组件官方当前发布页为准。配置示例中的密码、IP 均为演示,生产环境请替换为实际值并使用密钥管理。