Nginx如何处理HTTP/2推送日记的恢复问题

来源:JS脚本作者:李修然头衔:网络博主
导读:本期聚焦于李修然创作的《Nginx如何处理HTTP/2推送日记的恢复问题》,敬请观看详情。HTTP/2推送是Nginx对HTTP/2协议的重要支持特性,但不少配置过codehttp2_push/code指令的人遇到过疑惑:同一个客户端第二次访问时,之前推送过的资源不再重复推送,这背后是推送日记在起作用。本文围绕Nginx中HTTP/2推送日记的大小限制、会话恢复与状态重置行为展开讲解,分析推送日记的工作原理、与TLS会话复用的关系,并给出常用配置示例与排查思路,同时介绍HTTP/2推送逐步被浏览器弃用后可用的preload与Early Hints替代方案,帮助你在Nginx上正确使用或平稳迁移服务器推送能力。

Nginx从1.13.9版本开始正式支持HTTP/2服务器推送,通过http2_pushhttp2_push_preload两个指令,可以把CSS、JS甚至图片资源主动推送给浏览器,省去浏览器解析HTML后再发请求的往返时间。但推送并不是无脑发送的,Nginx内部维护了一个推送日记(push diary),用来记录已经推送过的资源,避免对同一个连接重复推送。理解这个日记机制、它的大小限制以及在连接恢复时的行为,对排查“为什么资源没有推送”这类问题非常关键。

Nginx如何处理HTTP/2推送日记的恢复问题

推送日记的工作原理与大小限制

HTTP/2协议本身规定了客户端可以通过SETTINGS_PUSH_DIARY_SIZE参数告知服务器自己能记录多少条推送。但浏览器厂商并没有真正实现这个参数,大多数客户端都会把该值设为0,表示不限制。Nginx为了在服务端做去重,自己实现了一个固定大小为64条记录的推送日记。

这个日记记录的是已推送资源的SHA-256指纹,而不是资源路径本身。Nginx在每次准备推送前,会把目标资源的路径哈希成256位指纹,然后与日记中的条目比对。如果指纹已经存在,就跳过推送;如果不存在,则执行推送并把指纹写入日记。用哈希而不是字符串的好处是比对速度恒定,且存储空间固定,每条记录占32字节,整个日记最大约2KB,内存开销可以忽略。

需要注意一个边界情况:由于日记容量只有64条,当推送的资源超过64个时,旧记录会被新记录覆盖。假设你的页面推送了第65个资源,它的指纹恰好覆盖了第一个资源的记录,那么第一个资源在下一次判断时就会被视为“未推送过”而再次推送。虽然绝大多数页面推送的资源远不到64个,但在批量推送静态资源清单的场景下,这个上限值得留意。

会话恢复时推送日记的行为

推送日记的生命周期与HTTP/2连接绑定,而不是与TLS会话绑定。这意味着一旦HTTP/2连接关闭,日记就随之销毁。客户端建立新连接后,Nginx会创建一个全新的空日记,之前推送过的资源可以重新推送。所以严格来说,Nginx并不存在跨连接“恢复推送日记”的能力,日记是连接级别的内存结构,不会持久化到磁盘。

这里容易与TLS会话票据混淆。TLS会话恢复可以让客户端复用之前的加密参数快速握手,但HTTP/2层的状态(包括流控窗口、HPACK动态表、推送日记)都是全新的。也就是说,即使浏览器通过会话票据恢复了TLS会话,Nginx依然会为这条新HTTP/2连接建立空的推送日记。如果你在测试中发现浏览器拒绝了推送,原因通常不是日记残留,而是浏览器主动发送了CANCEL帧取消推送流,或者浏览器本身禁用了推送功能。

另外,当Nginx执行nginx -s reload时,旧的worker进程会继续处理存量连接,新连接由新worker接管。旧连接的推送日记随旧worker存活,新连接则使用新配置。因此在验证配置变更时,最好用全新连接测试,可以用curl --http2 -v https://ipipp.com/并注意每次curl都是新连接,行为最可预期。

配置示例与验证方法

下面是一个完整的推送配置示例,包含静态推送和基于preload链接的动态推送两种方式:

server {
    listen 443 ssl http2;
    server_name ipipp.com;

    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # 静态推送:无条件推送指定资源
    location / {
        http2_push /css/main.css;
        http2_push /js/app.js;
        root /var/www/html;
    }

    # 动态推送:根据响应头中的preload link自动推送
    location /api/ {
        http2_push_preload on;
        proxy_pass http://127.0.0.1:8080;
    }
}

验证推送是否生效,最直接的方法是用curl观察PUSH_PROMISE帧。执行下面的命令,如果看到* received PUSH_PROMISE相关的输出,说明Nginx确实发起了推送:

curl -k --http2 -v https://ipipp.com/ 2>&1 | grep -i push

如果输出为空,先排查三点:第一,确认监听指令里包含http2参数;第二,确认推送的资源路径以斜杠开头且是相对路径,写绝对URL是无效的;第三,确认连接确实是HTTP/2,可用curl -sI --http2 https://ipipp.com/ | grepi HTTP检查协议版本。

浏览器弃用推送后的替代方案

一个必须面对的现实是:Chrome从106版本起移除了HTTP/2推送支持,Firefox也在逐步跟进,Safari虽然还保留但默认场景有限。也就是说,即便Nginx的推送和日记机制工作正常,主流浏览器也可能直接取消这些推送流。服务端收到CANCEL帧后不会重试,日记中仍会记录该指纹,这反而解释了部分“推送了但没生效”的困惑。

目前推荐的做法是改用标准的preload机制。在HTML头部写入<link rel="preload" href="/css/main.css" as="style">,浏览器解析到该标签后会立即发起请求,虽然多了一个往返,但兼容性远好于推送。Nginx侧只需保留http2_push_preload on,这样当上游应用输出preload头时,对仍支持推送的客户端(如某些内嵌WebView或代理)依旧可以触发推送,形成平滑过渡。

另一个演进方向是HTTP 103 Early Hints。浏览器收到103状态码后会提前加载提示的资源,再等待最终响应。Nginx本身暂未原生支持发送103,通常需要借助Lua模块或由上游应用直接输出,但作为推送的继任者,它已经被Chrome和Firefox实现,值得在架构规划时纳入考虑。

常见问题排查总结

整理几个高频疑问:资源不重复推送是正常现象,那是推送日记在同一个连接内去重;跨连接推送日记不会恢复,每次新连接都会重新初始化;http2_push可以写在server、location和if上下文中,多条指令会累积生效;路径必须使用相对形式,否则推送会被静默忽略。

最后建议通过访问日志结合$http2变量统计HTTP/2流量占比,同时在灰度环境观察推送资源的服务器端命中率与客户端实际使用率的差距。如果两者偏差巨大,大概率是客户端取消了推送流,此时应果断切换到preload或Early Hints方案,把优化重心放在资源提示的标准实现上,而不是继续依赖服务器推送这一正在退场的能力。

Nginxhttp2_push推送日记修改时间:2026-09-15 12:32:36

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260915/57270.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。