Statsd 0.10.2 配置参数详解
Statsd 参考实现 config.js(JS对象字面量)全参数详解:exampleConfig.js 参数总表+12组实测实验(TCP server、legacyNamespace新命名空间、tagged metrics、percentThreshold/calculatedTimerMetrics裁剪、histogram分桶、deleteIdleStats闲置治理、repeater双写、automaticConfigReload热加载、title/healthStatus/dumpMessages/keyNameSanitize),含 config 管理口回显与抓包排障实录。
Statsd 0.10.2 配置参数详解
目标:把 StatsD 参考实现(statsd/statsd)唯一的配置文件
config.js讲透——每个参数的默认值、语义、生效方式,并用 12 组实测实验逐个验证关键行为(TCP 接收、新命名空间、分位数裁剪、闲置治理、repeater 转发、直方图分桶、热加载、进程标题、键名清洗等)。全部回显为 2026-11-01 在 Ubuntu 24.04.5 + Node 18.19.1(statsd01)/ Node 22.23.4(statsd02)源码 v0.10.2 实例上实测。安装过程见 《Ubuntu 24.04 安装 Statsd 0.10.2 指南》,组件背景与架构见 《Statsd 介绍与选型指南》。
一、配置文件形态与加载
1.1 是 JS 对象字面量,不是 JSON
StatsD 配置是一个 Node 可 require 的 JS 模块,文件里直接写对象字面量(允许尾逗号式换行、允许 require('os') 之类的表达式)——这是它与多数 YAML/JSON 配置守护进程最大的差异。官方权威参数文档就是源码根目录的 exampleConfig.js:整段参数说明写在文件头部注释里,文件末尾给最小可用配置:
{
graphitePort: 2003
, graphiteHost: "graphite.example.com"
, port: 8125
, backends: [ "./backends/graphite" ]
}
启动时第一行日志确认读取:
1 Nov 10:36:07 - [8404] reading config file: /tmp/b1cfg.js
1.2 用管理口回看生效配置
配置写了一堆,运行时到底生效了什么?TCP 8126 管理口的 config 命令直接 dump(对主实例实测):
root@statsd01:~# echo config | nc -w1 127.0.0.1 8126 | head -16
graphitePort: 2003
graphiteHost: 127.0.0.1
port: 8125
backends > 0: ./backends/graphite
backends > 1: ./backends/console
debug: true
gaugesMaxTTL: 1
deleteIdleStats: false
flushInterval: 10000
deleteCounters: false
deleteTimers: false
deleteSets: false
deleteGauges: false
注意输出里出现了配置文件里没写的 flushInterval: 10000、gaugesMaxTTL: 1、delete* 系列——管理口 dump 的是"默认值 + 显式配置"合并后的生效值,这是核对默认参数的最快手段。
1.3 热加载(automaticConfigReload,默认开)
automaticConfigReload 默认为 true:进程 watch 配置文件,内容变化即自动重载,无需重启。实测(配置端口不变、touch 触发):
1 Nov 09:45:25 - server is up INFO
1 Nov 09:45:27 - [7400] reading config file: /work/cfgs/cfg_reload.js
第二行 reading config file 就是重载证据。生产改端口/后端等敏感项时若想避免自动生效,显式设 automaticConfigReload: false。
二、参数总表(默认值取自 exampleConfig.js 与 config 命令实测)
| 参数 | 默认值 | 语义 |
|---|---|---|
port / address / address_ipv6 | 8125 / 0.0.0.0 / false | UDP 监听端口/地址/IPv6 开关 |
servers | 未设时用顶层 port/address 建单个 udp server | server 数组,可换 ./servers/tcp,支持 unix socket(socket/socket_mod) |
mgmt_address / mgmt_port | 0.0.0.0 / 8126 | 管理 TCP 口 |
graphiteHost / graphitePort / graphitePicklePort / graphiteProtocol | 未设则不发 Graphite / 2003 / 2004 / text | Graphite 后端地址与协议 |
backends | ["./backends/graphite"] | 后端数组,可多值(graphite/console/repeater/第三方 npm 插件) |
debug | false | 打印加载与错误 DEBUG 日志 |
flushInterval | 10000(ms) | 聚合刷出周期 |
percentThreshold | 90 | timer 分位数,可列表 [50,90,99],负值取 top 分位 |
calculatedTimerMetrics | 全部输出 | 裁剪 timer 输出项;按分位过滤时指标名追加 _percent |
histogram | [] | 对匹配 metric 名的 timer 输出 histogram.bin_* 频次,inf 为兜底桶 |
deleteIdleStats | false | 闲置 counter/set/gauge/timer 不再刷 0(gauge 变 unset);可被 deleteCounters/deleteTimers/deleteSets/deleteGauges 单项覆盖 |
gaugesMaxTTL | 1 | gauge 空闲多少个 flush 周期后视为闲置(配合 deleteGauges) |
flush_counts | true | 是否发送 stats_counts.* 绝对值指标 |
keyNameSanitize | true | 入口清洗指标名;false 时由后端自行清洗 |
prefixStats | statsd | statsd 自观测指标前缀(新旧命名空间均生效) |
keyFlush | 未设 | 周期性打印最频繁 key(interval/percent/log) |
dumpMessages | 未设 | 逐条打印收到的原始消息(排障利器) |
title | "statsd" | 改写进程标题;false 保持 OS 原样 |
healthStatus | up | 启动时管理口 health 默认值(实测有出入,见 8.2) |
repeater / repeaterProtocol | 未设 / udp4 | 原始包复制转发到其他 statsd 列表;协议 udp4/udp6/tcp |
graphite.legacyNamespace | true | true=旧命名空间 stats.key;false=新命名空间 stats.counters.key.rate |
graphite.globalPrefix/prefixCounter/prefixTimer/prefixGauge/prefixSet/globalSuffix | stats/counters/timers/gauges/sets/空 | Graphite 路径拼装;globalSuffix 常用 require('os').hostname().split('.')[0] 做按主机区分 |
console.prettyprint | true | console 后端多行美化输出开关 |
log.backend/application/level | stdout/statsd/LOG_INFO | 日志去向 syslog 时的设置 |
automaticConfigReload | true | 配置文件变更自动重载 |
以下按主题给出关键参数的实测证据(实验实例均跑在偏移端口,与生产主实例互不干扰)。
三、接收层:UDP 与 TCP(servers)
同一时刻只启用一种接收 server。切 TCP 的实测配置 cfg_tcp.js:
{
servers: [ { server: "./servers/tcp", address: "0.0.0.0", port: 19125 } ]
, mgmt_port: 19126
, backends: [ "./backends/console" ]
, debug: true
}
启动日志(注意 DEBUG 行不再出现 Loading server: ./servers/udp):
1 Nov 09:43:55 - [7194] reading config file: /work/cfgs/cfg_tcp.js
1 Nov 09:43:55 - server is up INFO
1 Nov 09:43:55 - DEBUG: Loading backend: ./backends/console
经 TCP 19125 发送 tcpon:1|c 与 tcpon2:5|ms 后,flush 输出:
tcpon: 1
},
timers: { tcpon2: [ 5 ] },
gauges: {},
timer_data: {
tcpon2: {
count_90: 1,
mean_90: 5,
upper_90: 5,
sum_90: 5,
sum_squares_90: 25,
std: 0,
TCP 模式下每条指标以 \n 结尾(跨包可靠传输),聚合结果与 UDP 完全一致;代价是连接与确认开销。TCP server 还支持 socket/socket_mod 走本机 unix domain socket,适合 sidecar 场景。
四、Graphite 后端与命名空间
4.1 legacyNamespace=false 的新命名空间(实测)
配置 cfg_ns.js 关键行 graphite: { legacyNamespace: false },用 nc -l 2003 冒充 carbon 抓到的明文行:
stats.statsd.processing_time 0 1793526251
stats.statsd.processing_time 0 1793526261
stats.counters.cnt.rate 0.1 1793526262
stats.counters.cnt.count 1 1793526262
stats.timers.tmr.count_90 1 1793526262
stats.timers.tmr.mean_90 42 1793526262
stats.timers.tmr.upper_90 42 1793526262
stats.timers.tmr.sum_90 42 1793526262
stats.timers.tmr.sum_squares_90 1764 1793526262
stats.timers.tmr.std 0 1793526262
stats.timers.tmr.upper 42 1793526262
stats.timers.tmr.lower 42 1793526262
stats.timers.tmr.count 1 1793526262
stats.timers.tmr.count_ps 0.1 1793526262
stats.timers.tmr.sum 42 1793526262
stats.timers.tmr.sum_squares 1764 1793526262
stats.timers.tmr.mean 42 1793526262
stats.timers.tmr.median 42 1793526262
stats.gauges.gg 9 1793526262
stats.sets.ss.count 1 1793526262
stats.statsd.processing_time 0 1793526262
对照默认 legacyNamespace: true(旧命名空间):计数器刷成 stats.key(速率)+ stats_counts.key(绝对值)两条,如主实例抓到的:
stats.bad_key 0.1 1793526361
stats_counts.bad_key 1 1793526361
新命名空间则把类型放进路径段(stats.counters.cnt.rate/count),timer 的 _90 后缀由 percentThreshold 决定。切换命名空间会改变 Graphite 侧所有指标路径,仪表盘与聚合规则需同步迁移,这不是一个可以随手翻开的开关。
4.2 Tagged metrics(v0.9.0+)
协议行带 #tag:val 时,Graphite 输出分号标签格式(实测发送 tagged:1|c|#env:prod,host:web01):
stats.tagged;env=prod;host=web01 0.1 1793526834
stats_counts.tagged;env=prod;host=web01 1 1793526834
stats.tagged;env=prod;host=web01 0 1793526844
stats_counts.tagged;env=prod;host=web01 0 1793526844
4.3 后端连接失败的行为
graphiteHost 指向的 carbon 未监听时,每个 flush 周期日志打一条(实测):
1 Nov 09:31:54 - DEBUG: Error: connect ECONNREFUSED 127.0.0.1:2003
UDP 接收与内存聚合不受影响、进程不退出——fire-and-forget 语义的另一面:指标会静默丢失,所以 debug: true + 监控日志里的 graphite.last_exception(管理口 stats 可见)是上线必备。
五、Timer 统计:percentThreshold、calculatedTimerMetrics、histogram
5.1 分位数列表 + 输出裁剪 + 短周期(实测)
配置 cfg_pct.js:
{
port: 19129
, mgmt_port: 19130
, backends: [ "./backends/graphite" ]
, graphiteHost: "127.0.0.1"
, graphitePort: 2003
, flushInterval: 2000
, percentThreshold: [ 50, 90, 99 ]
, calculatedTimerMetrics: [ "count", "median", "upper_99" ]
}
抓到的输出(节选,可见 2 秒一个 flush 周期:时间戳 269→271):
stats.timers.rt.count 10 1793526269
stats.timers.rt.median 55 1793526269
stats.timers.rt.count 0 1793526271
另一组用 upper_percent 过滤写法时,输出按配置的三个分位展开:
stats.timers.rt.upper_50 50 1793526449
stats.timers.rt.upper_90 90 1793526449
stats.timers.rt.upper_99 100 1793526449
stats.timers.rt.count 10 1793526449
stats.timers.rt.median 55 1793526449
calculatedTimerMetrics 是高基数 timer 的流量阀门:默认全量输出十余条派生指标(见 4.1 的 tmr 全套),裁剪到 count/median/upper_99 后 Graphite 侧写入量成倍下降。
5.2 histogram 分桶(实测)
配置 cfg_hist.js 关键行 histogram: [ { metric: "h", bins: [ 10, 100, "inf" ] } ],对 timer h 发送 5/50/500 三个样本后抓到:
stats.timers.h.histogram.bin_10 1 1793526316
stats.timers.h.histogram.bin_100 1 1793526316
stats.timers.h.histogram.bin_inf 1 1793526316
每个桶输出"落在该区间的样本数",inf 兜底离群值;metric: "" 可作通配匹配所有 timer。
六、闲置指标治理:deleteIdleStats 与 gaugesMaxTTL
默认行为:停更的计数器每个周期继续刷 0,gauge 继续刷上一次值。高基数场景这会永久污染 Graphite。配置 cfg_idle.js 开 deleteIdleStats: true,只发一次 idle:1|c 后不再发送,连续三个 flush 周期的抓包(复测轮):
stats.idle 0.1 1793526461
stats_counts.idle 1 1793526461
第二、三个周期(时间戳 271、281)的抓包里 idle 两行彻底消失,只剩自观测指标:
stats.statsd.bad_lines_seen 0 1793526471
stats_counts.statsd.bad_lines_seen 0 1793526471
stats.statsd.packets_received 0 1793526471
stats_counts.statsd.packets_received 0 1793526471
stats.statsd.metrics_received 0 1793526471
stats_counts.statsd.metrics_received 0 1793526471
stats.gauges.statsd.timestamp_lag 0 1793526471
statsd.numStats 4 1793526471
stats.statsd.graphiteStats.calculationtime 1 1793526471
stats.statsd.processing_time 0 1793526471
stats.statsd.graphiteStats.last_exception 0 1793526471
stats.statsd.graphiteStats.last_flush 1793526461 1793526471
stats.statsd.graphiteStats.flush_time 0 1793526471
stats.statsd.graphiteStats.flush_length 682 1793526471
对 gauge,deleteIdleStats 的效果是"unset 而非继续刷旧值",配合 gaugesMaxTTL(默认 1 个周期)控制判定闲置的等待数。deleteCounters/deleteTimers/deleteSets/deleteGauges 可按类型单项开启。
顺带从抓包认识 statsd 的自观测指标(前缀由 prefixStats 控制,默认 statsd):statsd.numStats(当前桶数,基数监控点)、stats.statsd.processing_time(聚合耗时 ms)、stats.statsd.graphiteStats.last_flush/last_exception/flush_time/flush_length(后端健康)。
七、repeater:原始包复制转发
配置 cfg_rep.js:
{
port: 19133
, mgmt_port: 19134
, backends: [ "./backends/repeater" ]
, repeater: [ { host: "statsd02", port: 18125 } ]
, debug: true
}
本机发 3 条 rep:1|c 后,在 statsd02 的 console 实例(18125,见安装篇第五节)flush 里抓到转发流量:
n090: 0,
rep: 0,
repsent: 3
},
timers: {},
--
n090: 0,
rep: 0,
repsent: 0.3
},
sets: {},
repsent: 3 即对端收到的复制包计数。repeater 是协议层双写(原始 UDP 包照抄转发),用途:灰度迁移后端、旁路观测、给两套聚合器供数;repeaterProtocol: "tcp" 可换 TCP 转发。
八、进程自省类参数
8.1 title:进程标题改写(实测对照)
title: false 时不重写进程标题,ps 看到原始 node 命令行;默认改写为 statsd:
PID CMD
7435 node stats.js /work/cfgs/cfg_title.js
--- 主实例(默认 title 生效):
PID CMD
7008 statsd /etc/statsd/config.js
改写便于 ps/监控识别进程,但标题长度不能超过"二进制名+参数"原长,且 macOS 老 Node 不生效(exampleConfig 注明)。
8.2 healthStatus:文档与实测的差异(如实记录)
配置 cfg_health.js 设 healthStatus: "down"(exampleConfig 语义:启动时 health 的默认状态),实测管理口:
root@statsd01:~# echo health | nc -w1 127.0.0.1 19142
health: up
配置 down、命令仍回 up——v0.10.2 实测该配置项没有改变 health 命令返回值(摘流仍应使用运行时命令 health down,见运维篇)。排查口径:以 echo health 实时回显为准,不要依赖启动配置。
8.3 debug / dumpMessages
debug: true 输出加载明细与错误(Loading server/backend、Bad line、后端异常)。dumpMessages: true 逐条打印收到的原始消息,实测:
1 Nov 09:48:22 - DEBUG: dbgline:1|c
客户端协议对没对,开 dumpMessages 看一眼就清楚;排障完记得关掉,高流量下它会打爆日志。
8.4 console.prettyprint
console 后端默认 prettyprint: true(多行美化 JSON);设 console: { prettyprint: false } 后输出压缩为单行对象,便于被日志采集器按行解析。
九、keyNameSanitize:指标名清洗归属
默认 true:入口把非法字符重写(空格等→下划线),实测主实例收到含空格键名后 console 桶为 bad_key2;关闭后(cfg_sanitize.js,console 后端实测):
{
'statsd.bad_lines_seen': 0,
'statsd.packets_received': 1,
'statsd.metrics_received': 1,
'bad key': 1
}
END
原始名 'bad key' 完整保留。但同一份 keyNameSanitize: false 配置换 graphite 后端时,落到 Graphite 的行仍是清洗过的(后端"按各自存储要求"清洗,实测):
stats.bad_key 0.1 1793526519
stats_counts.bad_key 1 1793526519
stats.c-met 0.2 1793526519
stats_counts.c-met 2 1793526519
结论:keyNameSanitize=false 只是把清洗责任下放给后端,不是"Graphite 里会出现空格键名";多后端异构(如 influx 允许 -)时才值得关。
十、生产配置模板(带注释)
结合以上实测,给一份可直接抄改的 /etc/statsd/config.js:
{
graphiteHost: "127.0.0.1" // 不设则指标聚合后丢弃(干跑模式)
, graphitePort: 2003
, port: 8125
, mgmt_port: 8126
, backends: [ "./backends/graphite" ]
, debug: true // 生产建议开:Bad line/后端异常只在此模式可见
, flushInterval: 10000
, percentThreshold: [ 50, 90, 99 ]
, calculatedTimerMetrics: [ "count", "median", "upper_90", "upper_99" ]
, deleteIdleStats: true // 防停更指标刷 0 污染
, keyNameSanitize: true
, automaticConfigReload: true // 敏感环境改 false
}
💡 技术拓展:
backends/servers的值是相对源码目录的模块路径,第三方后端以 npm 包安装后同样写进数组(如 influxdb、elasticsearch 插件)。多后端并发刷出互不阻塞,某个后端挂了(如 4.3 的 ECONNREFUSED)不影响其他后端收数。
十一、配置实验排障实录
- 抓包为空(CASE4 首测 0 行):验证
deleteIdleStats首轮用nc -l 2003抓包得到 0 行——不是参数没生效,而是监听器竞争:2003 端口已被另一实验实例的 graphite 连接路径占用/提前退出,抓包进程根本没吃到数据。复测(CASE4b)先确认监听存活再发指标,才拿到 6.1 的完整三周期输出。教训:用nc -l冒充 carbon 做抓包时,先ss -lntp | grep 2003确认监听方唯一且存活。 - 端口全家桶规划:12 组实验用 19125–19148 成对偏移(port/mgmt_port 各占一个),与生产 8125/8126 及 npm 实例 18125/18126 全部错开;忘记错开的直接症状是启动日志
server is up缺失或 EADDRINUSE。 - healthStatus 与 health 命令不一致:见 8.2,以运行时命令为准。
参数都动过之后,下一篇把管理口命令、行协议发送与 utils/ 检查脚本做成运维手册。