应使用slab管理的共享内存(如resp_time_sec:16m)存储“域名+秒级时间戳”整型key数据,用$server_name而非$host保证域名可信,每域名限存60秒滑动窗口数据并自动淘汰,初始化时清零slot防脏数据,且需设置content_length_n避免chunked延迟。

要用共享内存块统计全站各域名的秒级响应时间平均值,核心是把“域名 + 秒级时间戳”作为唯一统计维度,在 Nginx worker 进程间共享、原子更新,并支持快速聚合计算。不能依赖日志落盘或单进程变量,必须用 slab 管理的共享内存(shm)配合合适的时间切片策略。
一、定义带时间粒度的共享内存区
在 http 块中声明一个足够大的共享内存区域,用于存储滑动窗口内的响应时间数据:
- 推荐命名如
resp_time_sec:16M,16MB 可支撑约 20 万个独立 key(每个 key 占用 ~80 字节) - 不建议直接用
$server_name:$time_iso8601拼接作 key——字符串哈希开销大且易冲突;应归一化为整型 key,例如:(crc32($server_name) (截取最近约 11.5 天的秒级精度) - 每个 slot 存储:域名哈希值、秒级时间戳、累计响应时间总和(uint64_t)、请求数(uint32_t),共 16 字节对齐
二、在 log 阶段安全写入响应时间
响应时间($request_time)只在 log_phase 才完全确定,此时调用自定义 handler 写入共享内存最可靠:
- 注册
ngx_http_log_handler_pt类型模块,挂载到NGX_HTTP_LOG_PHASE - 使用
ngx_shmtx_lock保护写操作,或更轻量地用ngx_atomic_fetch_add更新计数器(需结构体字段对齐) - 避免在 handler 中做字符串格式化或内存分配;用
ngx_current_msec替代os.time()获取毫秒级时间,再除以 1000 取整得秒级戳
三、按域名聚合计算平均值
对外提供 /status/avg_resp 接口时,不实时遍历全部 slot,而是按需聚合最近 N 秒数据:
- 遍历共享内存所有已初始化 slot,过滤出匹配目标
$server_name的记录 - 对同一域名、同一秒内多个请求,累加
sum_time和count,最后用sum_time / count得该秒平均值 - 返回示例:
{"api.example.com":{"20260528170522":128.4,"20260528170523":96.7}} - 注意:若某秒无数据,不补零;平均值保留一位小数,避免浮点运算开销
四、避免常见陷阱
实际部署中这几个细节最容易导致统计失真或性能抖动:
- 未在
shm_zone->init回调中用ngx_memzero清零新分配 slot,导致脏数据参与计算 - 用
$host替代$server_name做域名维度——前者可能被客户端篡改,后者由 server 块精确匹配,更可信 - 未限制单个域名最大并发秒级 key 数量,导致共享内存碎片化;建议每域名最多缓存最近 60 秒数据,超时自动淘汰
- 接口响应时未设置
r->headers_out.content_length_n,造成 chunked 编码延迟,影响监控系统拉取时效










