HTTP/2服务器推送允许Nginx在客户端请求HTML时主动推送关键静态资源,减少后续请求的RTT。不过,生产环境中如果缺少专属日志记录,推送成功与否、客户端是否取消、哪些资源命中率低,都很难观测。http2_push_diary模块正是为了解决这一盲区而设计的实践方案。下面从整体架构出发,拆解如何围绕该模块构建一套稳定的日志系统。

一、日志系统整体架构分层
该日志系统可划分为四层:采集层、传输层、存储层、展示层。采集层运行在每台Nginx实例上,通过http2_push_diary暴露出来的变量与log_format组合,将推送事件写成结构化日志文件。传输层使用轻量级采集代理读取日志文件,把数据批量推到消息队列或日志中心。存储层负责按资源、域名、时间等维度持久化,方便后续聚合查询。展示层通过看板订阅存储层索引,呈现推送成功率、取消率、热门推送资源等指标。
这样分层的好处是,Nginx主进程不直接依赖网络存储,不会因为下游接收端抖动而阻塞客户端响应。每一层都能独立扩缩容,尤其当Nginx节点很多时,采集层只关注写本地文件,传输层再统一处理背压和重试,整体可靠性更高。
另外,日志内容需要与普通访问日志隔离,独立使用access_log路径。推送日志的写入频率可能比访问日志更高,单独文件便于做轮转和生命周期管理,也避免污染已有分析链路。
- 采集层:Nginx与http2_push_diary负责生产结构化日志。
- 传输层:Filebeat等代理异步读取、解析并转发日志。
- 存储层:ClickHouse或Elasticsearch按时间分区存储明细。
- 展示层:看板系统聚合展示推送指标并触发告警。
二、Nginx侧http2_push_diary采集实现
http2_push_diary会为每个HTTP/2请求暴露与推送行为相关的变量,例如推送资源数量、资源是否命中缓存、客户端是否取消等。我们可以通过自定义log_format把这些变量写入日志。与直接修改模块源码相比,基于变量组合的采集方式对Nginx侵入小,后续升级模块也更方便。
下面是一段Nginx配置示例,假设站点使用HTTP/2并且已经部署了该模块:
log_format push_diary '$remote_addr [$time_local] "$request" '
'scheme=$scheme host=$host '
'push_uri=$http2_push_uri '
'push_count=$http2_push_count '
'push_status=$http2_push_status '
'cancel_count=$http2_push_cancel_count '
'hit_count=$http2_push_cache_hit_count '
'request_id=$request_id';
server {
listen 443 ssl http2;
server_name ipipp.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location / {
http2_push /assets/app.js;
http2_push /assets/style.css;
access_log /var/log/nginx/http2_push_diary.log push_diary;
}
}
配置中的$http2_push_uri、$http2_push_count等变量来自http2_push_diary模块。实际使用时需要确认模块文档中的变量名,但整体思路一致。不建议把推送日志直接写进主access_log,因为高频推送会显著增加主日志体积。独立文件配合logrotate按小时或按大小轮转,能更好控制磁盘占用。
如果请求中没有发生推送,$http2_push_count会返回0,因此每一行日志仍然能保留上下文,方便分析推送覆盖率。采集层还可以在日志路径上挂载tmpfs以降低磁盘IO,但这需要结合机器内存和日志重要性来权衡。
三、日志字段与存储建模
推送日志的价值取决于字段是否足够表达业务问题。推荐至少包含以下字段:时间戳、客户端地址、请求路径、推送资源URI、推送数量、成功数量、取消数量、缓存命中数量、HTTP状态码。如果有多台Nginx,还要增加实例标识和区域标识,否则聚合时无法区分来源。
| 字段 | 含义 | 分析用途 |
|---|---|---|
| request_id | 请求唯一ID | 关联访问日志与推送日志 |
| push_uri | 推送资源URI | 统计资源级别表现 |
| push_count | 推送总次数 | 覆盖率计算 |
| cancel_count | 客户端取消次数 | 衡量推送浪费 |
| cache_hit_count | 缓存命中次数 | 修正推送策略 |
传输层可以将文件中的每一行解析为JSON,再发送到Kafka、ClickHouse或Elasticsearch。如果选用ClickHouse,表结构可以按时间分区,使用Nullable类型处理部分字段。Push URI可能非常多,使用LowCardinality优化字符串存储。下面是一个简单的Filebeat采集配置示例:
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/nginx/http2_push_diary.log
fields:
log_type: http2_push_diary
multiline.pattern: '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
multiline.negate: true
multiline.match: after
output.kafka:
hosts: ["kafka1:9092", "kafka2:9092"]
topic: "nginx_http2_push_log"
partition.round_robin:
reachable_only: false
required_acks: 1
compression: gzip
max_message_bytes: 1000000
在解析层,推送URI和请求URI可能包含空格或特殊字符,需要统一做URL编码。解析失败的日志不要简单丢弃,应保留到死信文件,方便后续补数据。存储层按天分片,能降低历史数据保留成本。如果每日推送日志量很大,可以只保留近30天明细,聚合结果保留更长时间。
四、监控看板与推送策略优化
日志系统最终的用途是发现问题并指导HTTP/2推送策略调整。看板上可以展示三类核心指标:推送成功率、取消率、缓存命中率。推送成功率等于成功次数除以推送总次数,反映服务器推送是否真正被客户端接受。取消率过高通常说明推送了客户端已经缓存的资源,或者推送时机太晚,客户端已经发起普通请求。
缓存命中率可以进一步拆成客户端缓存命中与服务器端缓存命中。如果是客户端缓存命中,客户端会在推送到达前发送RST_STREAM取消,此时取消率会升高,但并不是错误。此时应结合日志中的cancel_count和cache_hit_count一起判断。比如一个资源推送总量1000次,取消900次,同时客户端缓存命中800次,说明这个资源长期缓存,推送收益很低,可以从http2_push配置中移除。
下面是一段调整后的Nginx配置,只对未登录且首次访问的请求推送核心资源,减少无意义推送:
map $cookie_session $do_push {
default 1;
~.+ 0;
}
server {
listen 443 ssl http2;
server_name ipipp.com;
location = /index.html {
if ($do_push = 1) {
http2_push /assets/app.js;
http2_push /assets/style.css;
}
}
}
还可以根据日志分析结果对推送资源做分级:核心资源、次级资源、不建议推送资源。核心资源保持持续推送,次级资源只在特定页面或时段推送,不建议推送资源直接移除。回到日志系统,调整配置后需要继续观察一到两个版本周期,确认取消率和缓存命中率变化,避免过度优化导致首屏性能下降。
如果结合A/B测试,可以在Nginx变量中标记实验分组,把分组ID写进日志,对比不同推送策略下的页面加载耗时。这样日志系统不仅记录行为,还能直接服务于性能优化决策。
围绕Nginx与http2_push_diary构建日志系统,核心是分层解耦和字段建模。采集层用模块变量与log_format组合,传输层用Filebeat等代理异步缓冲,存储层用面向分析的时间序列或列式存储,展示层用指标看板辅助策略调整。该架构在不侵入业务代码的前提下,为HTTP/2服务器推送提供了完整的可观测能力。
NginxHTTP/2服务器推送日志系统修改时间:2026-10-02 16:23:52