Go语言中HTTP/2必须通过TLS启用,纯HTTP无法协商;标准库自1.6起原生支持,1.8起自动注册h2 ALPN,无需手动配置NextProtos,仅需使用ListenAndServeTLS并确保证书合法。

Go语言框架本身不“处理”HTTP/2协议——net/http 包在底层自动协商并实现HTTP/2,框架(如Gin、Echo、Fiber)只是构建在其上的路由和中间件层。真正影响HTTP/2性能的,是TLS配置、连接生命周期管理、响应体处理方式,以及你是否无意中破坏了多路复用的条件。
为什么你的Gin/Echo服务没走HTTP/2?
常见错误是只监听HTTP端口:http.ListenAndServe(":8080", handler)。Go的net/http对HTTP/2有硬性要求:必须使用TLS,且TLS配置需显式支持ALPN中的"h2"。即使你用了ListenAndServeTLS,若证书不合法、或系统不信任CA,客户端(如curl、浏览器)会降级回HTTP/1.1。
- 用
curl -I --http2 https://localhost:8443/ping验证是否真走HTTP/2;若返回HTTP/2 200且无重定向,说明成功 - 避免自签名证书被忽略:Chrome/Firefox会直接拒绝,建议用
mkcert生成本地可信证书 - Gin/Echo等框架无需额外配置HTTP/2开关,但必须通过
ListenAndServeTLS启动,不能混用ListenAndServe+ 反向代理转发
Transport配置不当导致HTTP/2多路复用失效
客户端(比如微服务间调用)若复用http.Client但Transport未正确设置,会退化为每个Host建一个TCP连接,失去HTTP/2核心优势。关键不是“是否启用HTTP/2”,而是“是否让多个请求真正共享同一个连接”。
-
ForceAttemptHTTP2默认为true,不用手动设;但若TLSConfig里NextProtos被误删(如只留[]string{"http/1.1"}),就会强制降级 -
MaxIdleConnsPerHost必须 ≥ 1(建议设为100),否则连接池会按Host隔离,无法跨请求复用 -
IdleConnTimeout建议设为30~90秒:太短导致频繁重连,太长则空闲连接占满文件描述符 - 别在每次请求都新建
http.Client——它内部的Transport是线程安全的,应全局复用
响应体过大或阻塞写入破坏流并发
HTTP/2的多路复用依赖帧级调度,但如果一个Handler长时间阻塞Write(比如读大文件后一次性w.Write(buf)),该流会独占连接带宽,其他并发流被迫等待——这和HTTP/1.1队头阻塞表现类似,只是粒度更细。
立即学习“go语言免费学习笔记(深入)”;
- 对大响应体,用
io.Copy或io.CopyN分块传输,避免全量加载进内存 - 不要在Handler里做耗时同步IO(如直连数据库查10万行再JSON序列化);应提前超时控制,或用
context.WithTimeout包装下游调用 - 框架中间件(如日志、鉴权)若读取
r.Body又没还原,会导致后续Handler读不到Body,引发静默失败 - 禁用
gzip中间件对流式响应(如SSE、gRPC-Web)——压缩器会缓冲全部数据才能输出,破坏流特性
服务端推送(Server Push)在Go中基本不可用
虽然HTTP/2标准定义了Pusher接口,但Go从1.19起已将其标记为Deprecated,且主流浏览器(Chrome 110+、Firefox 115+)已完全移除对Server Push的支持。强行调用r.Pusher().Push()会返回ErrNotSupported,不会报错但也不生效。
别浪费时间适配这个特性。真实收益更高的做法是:用HTTP/2 + Link预加载头(Link: </style.css>; rel=preload; as=style),由浏览器自主决定是否发起预请求——它更可控、兼容性更好,且不增加服务端复杂度。



















