使用 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 的監控數據等等。

最後

咱們正在招聘!

[北京/武漢] 石墨文檔 作最美產品 - 尋找中國最有才華的工程師加入

相關文章
相關標籤/搜索