Moleculer 內建的 Metrics 可以收集系統內部大量的流程指標,而且可以很簡單的定義你自己的客製化 Metrics。系統內建了幾個 Metrics 報表產生器,如: Console
、 Prometheus
、 Datadog
等。
注意, Moleculer 作者 icebob 在此討論串提到[2] ,近期的趨勢 OpenTelemetry[3] 即將成為業界標準,而它與目前內建的 Metrics 與 Tracing 模組功能幾乎相同,作者可能會在下一個版本 v0.15 切換到 OpenTelemetry ,因此你可能需要考慮是否要使用目前內建的模組。
更新,在同一個討論串作者說明 OpenTelemetry 不會在 v0.15 實施,但是會以 middleware 方式支援,範例在 next branch [7] 。
以下介紹的各種報表產生器,其中細部設定筆者未測試或確認過的部分,本文將暫時保留官方提供的範例註解說明。
假如你想使用舊版的 Metrics (小於 v0.14) ,請使用
EventLegacy
的追蹤輸出器[4] 。
範例:啟用 Metrics 功能,並設定報表產生器
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
enabled |
<Boolean> | false |
啟用 Metrics 功能. |
reporter |
<Object> | <Object[]> | null |
Metric 報表產生器設置,詳情文章後面會說明。 |
collectProcessMetrics |
<Boolean> | process.env.NODE_ENV !== "test" |
收集流程與系統相關 metrics 。 |
collectInterval |
<Number> | 5 |
收集時間區段(秒) |
defaultBuckets |
<Number[]> | [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] |
直方圖的桶值 |
defaultQuantiles |
<Number[]> | [0.5, 0.9, 0.95, 0.99, 0.999] |
直方圖分位數 |
defaultMaxAgeSeconds |
<Number> | 60 |
分位數的最大壽命(秒) |
defaultAgeBuckets |
<Number> | 10 |
分位數計算的桶數 |
defaultAggregator |
<String> | sum |
數值聚合方法 |
moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
"Console"
]
}
};
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
includes |
<String> | <String[]> | null |
要輸出的 Metrics 清單 |
excludes |
<String> | <String[]> | null |
不輸出的 Metrics 清單 |
metricNamePrefix |
<String> | null |
Metrics 名稱前綴 |
metricNameSuffix |
<String> | null |
Metrics 名稱後綴 |
metricNameFormatter |
<Function> | null |
Metric 格式化函數 |
labelNameFormatter |
<Function> | null |
Metric 標籤名稱格式化函數 |
範例:選項設定方式
moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "Console",
options: {
includes: ["moleculer.**.total"],
excludes: ["moleculer.broker.**", "moleculer.request.**"],
// 原始 "moleculer.node.type" ,加上前綴: "mol:moleculer.node.type"
metricNamePrefix: "mol:",
// 原始 "moleculer.node.type" ,加上後綴: "moleculer.node.type.value"
metricNameSuffix: ".value",
metricNameFormatter: name => name.toUpperCase().replace(/[.:]/g, "_"),
labelNameFormatter: name => name.toUpperCase().replace(/[.:]/g, "_")
}
}
]
}
};
這是一個除錯用的報表產生器,它會定期將 metrics 輸出至主控台。
moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "Console",
options: {
// 輸出頻率(秒)
interval: 5,
// 客製化 logger
logger: null,
// 使用顏色
colors: true,
// 不要輸出完整清單,只輸出變更的 metrics
onlyChanges: true
}
}
]
}
};
報表產生器會將變更的內容儲存至 Comma-Separated Values (CSV) 檔案。
moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "CSV",
options: {
// CSV 檔案輸出目錄
folder: "./reports/metrics",
// CSV 欄位分隔符號
delimiter: ",",
// CSV 段落符號
rowDelimiter: "\n",
// 儲存模式
// [metric] 儲存至個別的檔案
// [label] 按標籤儲存至個別的檔案
mode: "metric",
// 儲存的 metrics 類型
types: null,
// 儲存間隔時間(秒)
interval: 5,
// 客製化檔名格式化器
filenameFormatter: null,
// 客製化 CSV 行格式化器
rowFormatter: null,
}
}
]
}
};
事件報表產生器會將 metric 值發送至 Moleculer 事件。
moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "Event",
options: {
// 事件名稱
eventName: "$metrics.snapshot",
// 是否為廣播事件
broadcast: false,
// 事件群組
groups: null,
// 只輸出變更的 metrics
onlyChanges: false,
// 輸出頻率(秒)
interval: 5,
}
}
]
}
};
Datadog 報表產生器會將 metric 發送至 Datadog 伺服器。
moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "Datadog",
options: {
// Hostname
host: "my-host",
// Base URL
baseUrl: "https://api.datadoghq.com/api/",
// API 版本
apiVersion: "v1",
// 伺服器 URL 路徑
path: "/series",
// Datadog API Key
apiKey: process.env.DATADOG_API_KEY,
// 附加到所有 metrics 標籤的預設標籤
defaultLabels: (registry) => ({
namespace: registry.broker.namespace,
nodeID: registry.broker.nodeID
}),
// 輸出頻率(秒)
interval: 10
}
}
]
}
};
Prometheus 報表產生器會將 metric 以 Prometheus 的格式暴露輸出,Prometheus 伺服器可以利用它收集資料,預設連接埠為 3030。
moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "Prometheus",
options: {
// HTTP 連接埠
port: 3030,
// HTTP URL 路徑
path: "/metrics",
// 附加到所有 metrics 標籤的預設標籤
defaultLabels: registry => ({
namespace: registry.broker.namespace,
nodeID: registry.broker.nodeID
})
}
}
]
}
};
StatsD 報表產生器會透過 UDP 將 metric 發送至 StatsD 伺服器。
moleculer.config.js
module.exports = {
metrics: {
enabled: true,
reporter: [
{
type: "StatsD",
options: {
// 伺服器 host
host: "localhost",
// 伺服器 port
port: 8125,
// 最大酬載大小
maxPayloadSize: 1300
}
}
]
}
};
你也可以建立客製化的報表產生器,官方建議可以參考 Console Reporter[5] 的原始碼來修改,再實作 init
、 stop
、 metricChanged
方法。
範例:建立客製化報表產生器
my-metrics-reporter.js
const BaseReporter = require("moleculer").MetricReporters.Base;
class MyMetricsReporter extends BaseReporter {
init() { /*...*/ }
stop() { /*...*/ }
metricChanged() { /*...*/ }
}
module.exports = MyMetricsReporter;
範例:使用客製化報表產生器
moleculer.config.js
const MyMetricsReporter = require("./my-metrics-reporter");
module.exports = {
metrics: {
enabled: true,
reporter: [
new MyMetricsReporter(),
]
}
};
counter
計數器是對 Metric 單純的累加,它只能遞增或是歸零。例如你可以使用計數器來表示服務的請求數、任務完成數或錯誤數。速率為每分鐘。
計數器提供的方法:
increment(labels?: GenericObject, value?: number, timestamp?: number)
set(value: number, labels?: GenericObject, timestamp?: number)
gauge
測量是 metric 的一個可任意增減的單純數值。通常用於測量某個值,例如目前的記憶體使用量,但也可以用在會上下浮動的計數,例如併發請求時的數量。速率為每分鐘。
測量器提供的方法:
increment(labels?: GenericObject, value?: number, timestamp?: number)
decrement(labels?: GenericObject, value?: number, timestamp?: number)
set(value: number, labels?: GenericObject, timestamp?: number)
histogram
直方圖會採樣觀察結果並且在可配置的桶中做計數(通常是觀察請求的時間或響應的大小)。另外也提供觀察值的總和,並且可以在時間視窗內計算配置分位數。速率為每分鐘。
直方圖提供的方法:
observe(value: number, labels?: GenericObject, timestamp?: number)
info
提供關於處理程序的參數、主機名稱或版本號的字串資料或數字。
set(value: any | null, labels?: GenericObject, timestamp?: number)
你可以輕易的建立客製化 metrics 。
範例:建立一個計數器
posts.service.js
module.exports = {
name: "posts",
actions: {
// Get posts
get(ctx) {
// 遞增 metric
this.broker.metrics.increment("posts.get.total", 1);
return this.posts;
}
},
created() {
// 註冊一個新的計數器 metric
this.broker.metrics.register({
type: "counter",
name: "posts.get.total",
description: "Number of requests of posts",
unit: "request",
rate: true // 設定速率為每分鐘
});
}
};
範例:建立帶有標籤的測量
posts.service.js
module.exports = {
name: "posts",
actions: {
// 建立的 Action
create(ctx) {
// 更新 metrics
this.broker.metrics.increment("posts.total", { userID: ctx.params.author }, 1);
return posts;
},
// 刪除的 Action
remove(ctx) {
// 更新 metrics
this.broker.metrics.decrement("posts.total", { userID: ctx.params.author }, 1);
return posts;
},
},
created() {
// 註冊一個新的測量 metric
this.broker.metrics.register({
type: "gauge",
name: "posts.total",
labelNames: ["userID"]
description: "Number of posts by user",
unit: "post"
});
}
};
範例:建立包含桶與分位數設定的直方圖
posts.service.js
module.exports = {
name: "posts",
actions: {
// 建立的 Action
async create(ctx) {
// 測量建立 post 的時間
const timeEnd = this.broker.metrics.timer("posts.creation.time");
const post = await this.adapter.create(ctx.params);
const duration = timeEnd();
this.logger.debug("Post created. Elapsed time: ", duration, "ms");
return post;
}
},
created() {
// 註冊新的直方圖 metric
this.broker.metrics.register({
type: "histogram",
name: "posts.creation.time",
description: "Post creation time",
unit: "millisecond",
// 產生線性桶值
linearBuckets: {
start: 0,
width: 100,
count: 10
},
quantiles: [0.5, 0.9, 0.95, 0.99],
maxAgeSeconds: 60,
ageBuckets: 10
});
}
};
[1] Metrics, https://moleculer.services/docs/0.14/metrics.html
[2] Switching to OpenTelemetry from built-in metrics and tracing?, https://github.com/moleculerjs/moleculer/discussions/1125
[3] OpenTelemetry, https://opentelemetry.io/
[4] Tracing Event (legacy), https://moleculer.services/docs/0.14/tracing.html#Event-legacy
[5] Moleculer Console Reporter, https://github.com/moleculerjs/moleculer/blob/master/src/metrics/reporters/console.js
[6] Creating Buckets or Clusters for Numeric Column Values in Exploratory, https://blog.exploratory.io/d04901b32d35
[7] examples/opentelemetry, https://github.com/moleculerjs/moleculer/tree/next/examples/opentelemetry