使用 StatsD + Grafana + InfluxDB 搭建 Node.js 监控系统

在石墨,咱们以前使用 ELK 搭了一套监控图表,因为一些缘由,好比:node

  • Kibana 常常查日志查挂git

  • Kibana 的图表不太美观、不够灵活github

因此调研了一下,选择用 StatsD + Grafana + InfluxDB 搭建一套新的监控系统。web

工具简介

StatsD 是一个使用 Node.js 开发的简单的网络守护进程,经过 UDP 或者 TCP 方式侦听各类统计信息,包括计数器和定时器,并发送聚合信息到后端服务,例如 Graphite、ElasticSearch、InfluxDB 等等,这里 列出了支持的 backend。docker

Grafana 是一个使用 Go 开发的开源的、功能齐全的、好看的仪表盘和图表的编辑器,可用来作日志的分析与展现曲线图(如 api 的请求日志),支持多种 backend,如 ElasticSearch、InfluxDB、OpenTSDB 等等。在线 DEMO。shell

InfluxDB 是一个使用 Go 语言开发的开源分布式时序、事件和指标数据库,无需外部依赖,其设计目标是实现分布式和水平伸缩扩展。数据库

启动 docker-statsd-influxdb-grafana

我使用的 Docker 镜像 docker-statsd-influxdb-grafana 一键启动 StatsD + Grafana + InfluxDB,省去不少麻烦(此处省略一万字)。后端

由于我本机是 Mac,因此如下演示如何在 Mac 下使用 Docker 搭建(其余系统用法应该差很少)。在此以前,先安装 Docker,Mac 下虽然有 Kitematic,咱们仍是用命令行来演示。api

启动一个 docker-machine:数组

➜  Desktop docker-machine start
Starting "default"...
(default) Check network to re-create if needed...
(default) Waiting for an IP...
Machine "default" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.
➜  Desktop docker-machine env
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/nswbmw/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell:
# eval $(docker-machine env)
➜  Desktop eval $(docker-machine env)
➜  Desktop docker ps

记住分配的 DOCKER_HOST 后面会用,我这里为 192.168.99.100。

启动 docker-statsd-influxdb-grafana,命令以下:

docker run -d \
  --name docker-statsd-influxdb-grafana \
  -p 3000:9000 \
  -p 8083:8083 \
  -p 8086:8086 \
  -p 22022:22 \
  -p 8125:8125/udp \
  samuelebistoletti/docker-statsd-influxdb-grafana:latest

运行 docker ps 查看:

clipboard.png

配置 InfluxDB

浏览器打开:http://你的ip:8083。点击右上角设置的图标,添加 username 和 password 都为 datasource,点击 Save 保存,以下所示:

clipboard.png

注意:用户名和密码这里先填 datasource,后面会说明。此外,这个 docker 镜像自动为咱们在 InfluxDB 建立了名为 datasource 的 db。

配置 Grafana

浏览器打开:http://你的ip:3000。

  1. 输入 user 和 password 都为 root,登陆

  2. 点击左上角图标 -> Data Source -> Add data source,进入配置数据源页面,以下填写,点击 Save:

clipboard.png

注意:url 中替换成你分配的 ip。

使用 node-statsd

node-statsd 是一个 statsd 的 Node.js client。建立如下测试代码:

'use strict';

const StatsD = require('node-statsd'),
client = new StatsD({
  host: '192.168.99.100',
  port: 8125
});

setInterval(function () {
  const responseTime = Math.floor(Math.random() * 100);
  client.timing('api', responseTime, function (error, bytes) {
    if (error) {
      console.error(error);
    } else {
      console.log(`Successfully sent ${bytes} bytes, responseTime: ${responseTime}`);
    }
  });
}, 1000);

注意:host 改成你分配的 ip。

运行以上代码,每一秒钟产生一个 0-99 之间的随机值(模拟响应时间,单位毫秒),发送到 StatsD,StatsD 会将这些数据写入 InfluxDB 的 datasource 数据库。

建立 Grafana 图表

回到 Grafana 页面。

  1. 点击左上角图标 -> Dashboards -> +New 进入建立图表页

  2. 点击左侧的绿块 -> Add Panel -> Graph 建立一个图表

建立 API 请求量图表:

一、点击 General -> Title 修改成 "API 请求量"
二、点击 Metrics -> Add query,点击如图所示位置,选择 "api.timer.count",ALIAS BY 填写 "tps",以下所示:

clipboard.png

三、点击左上角保存(或用 ctrl + s),我选择了显示 5 分钟内的数据,每 5s 刷新一次,以下所示:

clipboard.png

建立 API 响应时间图表

  1. 点击 +ADD ROW -> 点击左侧的绿块 -> Add Panel -> Graph,建立一个图表

  2. 点击 General -> Title 修改成 "API 响应时间"

  3. 点击 Metrics -> Add query,点击如图所示位置,选择 "api.timer.mean",ALIAS BY 填写 "mean"

  4. 点击 Add query,选择 "api.timer.mean_90",ALIAS BY 填写 "mean_90"

  5. 点击 Add query,选择 "api.timer.upper_90",ALIAS BY 填写 "upper_90"

最终以下所示:

clipboard.png

clipboard.png

讲解一下:

  1. mean: 全部请求的平均响应时间

  2. mean_90: 去除最高响应时间的 10% 请求后,剩余的 90% 请求的响应时间的平均值

  3. upper_90: 去除最高响应时间的 10% 请求后,响应时间最大的那个值

固然这个 90% 是能够配置的,好比也能够设置为 95%,更多信息见:

  1. https://github.com/etsy/stats...

  2. https://github.com/etsy/stats...

注意事项

  1. docker-statsd-influxdb-grafan 这个 docker 镜像里配置 StatsD 的配置在 /opt/statsd/config.js,里面写死了 InfluxDB 的配置,因此若是改 InfluxDB 的 db 或者 username 或者 password,别忘了改这个配置。

  2. 在 InfluxDB 的 web 管理页使用查询语句,如你在 node-statsd 使用 client.timing('api') 并不会建立 api 的表,会建立如 api.timer.count 等等这样的表,因此以下查询是没有结果的:select from api,能够在 datasource 下使用:select from /.*/ 查看 datasource 下全部数据。

  3. 在使用 node-statsd 时,只发送了 timing 类型的数据,此类型也会额外建立 counting 类型的数据,因此这样是多余的 client.increment('api')。

在 Koa 中使用

lib/statsd.js

'use strict';

const StatsD = require('node-statsd');
const config = require('config');

module.exports = new StatsD({
  host: config.statsd.host,
  port: config.statsd.port
});

middlewares/statsd.js

'use strict';

const statsdClient = require('../lib/statsd');

module.exports = function () {
  return function *statsd(next) {
    const routerName = this.route ? this.route.handler.controller + '.' + this.route.handler.action : null;
    const start = Date.now();

    yield next;

    const spent = Date.now() - start;
    if (routerName) {
      statsdClient.timing(`api.${routerName}`, spent);
      statsdClient.timing('api', spent);
    }
  };
};

app.js

app.use(require('./middlewares/statsd')());

咱们用了 bay 框架(基于 Koa 二次开发),因此能够用 Koa 的全部中间件,bay 有一个 route 属性,包含 handler 和 action,因此能够直接拿来用,切记上面 routerName 不要直接用 this.path 等等(如: /users/:userId 这个 api 每次 userId 都会不同,致使 InfluxDB 建立不一样的表)。若是你用的 Koa 的话,能够在每一个 controller 或 route 里添加 routerName 属性,如:this.routerName = 'xxx',而后将上面修改成:

const routerName = this.routerName;

一键导入数据

咱们 API 有近百个接口,要是每次都去手动建立并配置图表那就费老劲了,并且每次建立的图表的配置都差很少,因而我寻思寻找一些捷径。我发现 Grafana 有 Template 的功能,然而尝试了下并无搞明白怎么用。。我又发现 Grafana 有 Import 的功能,因而先把配置好的图表先导出 JSON,而后不断复制粘贴修改,保存尝试 Import 看下效果 ,最后成功。

注意:导出的 JSON 中 rows 表明了每一行,每一个 row 中有一个 panels 数组存储了每个 Graph(下图一个 row 有两个 Graph),每一个 Graph 有一个 id 字段是递增的(如:一、二、3...),targets 下每一个曲线的 refId 是递增的(如:A、B、C...),记得修正过来,不然没法正常显示图表。

最终我写了个脚本,运行后生成了每一个接口的 JSON 文件,30 多个接口导出了 30 多个文件,每次 Import 那也要 30 几回。机智的我怎么可能就此放弃(实际上是懒),应该还有更省事的方法,我在浏览器中导入的时候,在控制台看了下 Grafana 的网络请求,发现导入时调用的是:

POST https://xxx:3006/api/dashboards/import

并且 JSON 文件的数据直接放在 post 请求体里,那这样就好办了,也不用生成文件了,最后生成的配置放到了一个数组里,用 co + co-request 循环调用上面那个接口导入就行了,真正作到一键导入数据。

如下是一个 dashboard 及对应的 JSON 配置:

clipboard.png

{
  "id": 32,
  "title": "API file",
  "tags": [],
  "style": "dark",
  "timezone": "browser",
  "editable": true,
  "hideControls": false,
  "sharedCrosshair": false,
  "rows": [
    {
      "collapse": false,
      "editable": true,
      "height": "250px",
      "panels": [
        {
          "aliasColors": {},
          "bars": false,
          "datasource": "api-influxdb",
          "editable": true,
          "error": false,
          "fill": 2,
          "grid": {
            "threshold1": null,
            "threshold1Color": "rgba(216, 200, 27, 0.27)",
            "threshold2": null,
            "threshold2Color": "rgba(234, 112, 112, 0.22)"
          },
          "id": 1,
          "isNew": true,
          "legend": {
            "avg": false,
            "current": false,
            "max": false,
            "min": false,
            "show": true,
            "total": false,
            "values": false
          },
          "lines": true,
          "linewidth": 1,
          "links": [],
          "minSpan": 6,
          "nullPointMode": "connected",
          "percentage": false,
          "pointradius": 5,
          "points": false,
          "renderer": "flot",
          "seriesOverrides": [],
          "span": 6,
          "stack": false,
          "steppedLine": false,
          "targets": [
            {
              "alias": "tps",
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "$interval"
                  ],
                  "type": "time"
                },
                {
                  "params": [
                    "null"
                  ],
                  "type": "fill"
                }
              ],
              "measurement": "api.file.show.timer.count",
              "policy": "default",
              "refId": "A",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "mean"
                  }
                ]
              ],
              "tags": []
            }
          ],
          "timeFrom": null,
          "timeShift": null,
          "title": "api.file.show.count",
          "tooltip": {
            "msResolution": true,
            "shared": true,
            "sort": 0,
            "value_type": "cumulative"
          },
          "type": "graph",
          "xaxis": {
            "show": true
          },
          "yaxes": [
            {
              "format": "short",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            },
            {
              "format": "short",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            }
          ]
        },
        {
          "aliasColors": {},
          "bars": false,
          "datasource": "api-influxdb",
          "editable": true,
          "error": false,
          "fill": 1,
          "grid": {
            "threshold1": null,
            "threshold1Color": "rgba(216, 200, 27, 0.27)",
            "threshold2": null,
            "threshold2Color": "rgba(234, 112, 112, 0.22)"
          },
          "id": 2,
          "isNew": true,
          "legend": {
            "avg": false,
            "current": false,
            "max": false,
            "min": false,
            "show": true,
            "total": false,
            "values": false
          },
          "lines": true,
          "linewidth": 2,
          "links": [],
          "minSpan": 5,
          "nullPointMode": "connected",
          "percentage": false,
          "pointradius": 5,
          "points": false,
          "renderer": "flot",
          "seriesOverrides": [],
          "span": 6,
          "stack": false,
          "steppedLine": false,
          "targets": [
            {
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "$interval"
                  ],
                  "type": "time"
                },
                {
                  "params": [
                    "null"
                  ],
                  "type": "fill"
                }
              ],
              "measurement": "api.file.show.timer.mean",
              "policy": "default",
              "refId": "A",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "mean"
                  }
                ]
              ],
              "tags": [],
              "alias": "mean"
            },
            {
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "$interval"
                  ],
                  "type": "time"
                },
                {
                  "params": [
                    "null"
                  ],
                  "type": "fill"
                }
              ],
              "measurement": "api.file.show.timer.mean_90",
              "policy": "default",
              "refId": "B",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "mean"
                  }
                ]
              ],
              "tags": [],
              "alias": "mean_90"
            },
            {
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "$interval"
                  ],
                  "type": "time"
                },
                {
                  "params": [
                    "null"
                  ],
                  "type": "fill"
                }
              ],
              "measurement": "api.file.show.timer.upper_90",
              "policy": "default",
              "refId": "C",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "mean"
                  }
                ]
              ],
              "tags": [],
              "alias": "upper_90"
            }
          ],
          "timeFrom": null,
          "timeShift": null,
          "title": "api.file.show.timer",
          "tooltip": {
            "msResolution": true,
            "shared": true,
            "sort": 0,
            "value_type": "cumulative"
          },
          "type": "graph",
          "xaxis": {
            "show": true
          },
          "yaxes": [
            {
              "format": "short",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            },
            {
              "format": "short",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            }
          ]
        }
      ],
      "title": "Row"
    }
  ],
  "time": {
    "from": "now-1h",
    "to": "now"
  },
  "timepicker": {
    "refresh_intervals": [
      "5s",
      "10s",
      "30s",
      "1m",
      "5m",
      "15m",
      "30m",
      "1h",
      "2h",
      "1d"
    ],
    "time_options": [
      "5m",
      "15m",
      "1h",
      "6h",
      "12h",
      "24h",
      "2d",
      "7d",
      "30d"
    ]
  },
  "templating": {
    "list": []
  },
  "annotations": {
    "list": []
  },
  "refresh": "5s",
  "schemaVersion": 12,
  "version": 2,
  "links": [],
  "gnetId": null
}

Grafana 更多用法

目前我只简单地用 Grafana 来统计:

  1. api 总的平均响应时间

  2. api 每一个接口的 tps 和平均响应时间

  3. 将来还会加入 cpu 和内存的使用状况等等

Grafana 还支持各类 plugin,如 grafana-zabbix 接入 zabbix 的监控数据等等。

最后

咱们正在招聘!

[北京/武汉] 石墨文档 作最美产品 - 寻找中国最有才华的工程师加入

相关文章
相关标签/搜索