Fundebug网站升级HTTP2真的变快了

36次阅读

共计 6440 个字符,预计需要花费 17 分钟才能阅读完成。

作为新一代的 HTTP 协议,HTTP/ 2 可以提高网站性能,优化用户体验,Fundebug 也是时候升级 HTTP/ 2 了,虽然已经有点晚了。

升级 HTTP/ 2 是一件很简单的事情,改 1 行 Nginx 配置就好了,但是,工程师只知道 How 是远远不够的,还需要理解 Why,这就要求我们需要足够的事先调研(1. 什么是 HTTP/2?)以及事后分析(4. 升级 HTTP/ 2 真的提高性能了吗?)。

1. 什么是 HTTP/2?

HTTP/ 2 是新一代的 HTTP 协议,于 2015 正式发布。

与其他众多 Web 技术标准一样,推动 HTTP/ 2 标准的依然是 Google。发布 Chrome 的时候 Google 说过要推动 Web 技术的发展,然后它真的做到了。(JavaScript 深入浅出第 5 课:Chrome 是如何成功的?)

根据 W3Techs 的统计,截止 2019 年 10 月 26 日,全世界 41.3% 的网站已经使用了 HTTP/2。

根据 Can I use,绝大多数浏览器都支持了 HTTP/2:

HTTP/ 2 主要有以下几个特性:

  • HTTP/ 2 为二进制协议


图片来源:Valentin V. Bartenev

由上图可知,HTTP/1.1 传输的是文本数据,而 HTTP/ 2 传输的是二进制数据,提高了数据传输效率。

  • HTTP/ 2 支持 TCP 连接多路复用


图片来源:Factory.hr

由上图可知,HTTP 1.1 需要为不同的 HTTP 请求建立单独的 TCP 连接,而 HTTP/ 2 的多个 HTTP 请求可以复用同一个 TCP 连接。

要知道,建立 TCP 连接时需要 3 次握手,再加上 TLS 的 4 次握手,加起来就是 7 次握手,如果可以复用 TCP 连接的话,则可以减少这些多余的开销。

  • HTTP/ 2 会压缩请求 Header


图片来源:运维实谈

如上图所示,第 2 个请求的 Header 只有:path 不一样,因此压缩空间非常可观。

Headers 压缩的算法 HPACK 本身似乎很复杂(其实也不难),但是算法思想其实非常简单的,假设我们在浏览器发起 100 个请求,它们的 user-agent 是不会变的,那我们为什么需要重复传输这个长长的字符串呢?用 dictionary 记录一次不就行了!

  • HTTP/ 2 支持服务器推送(Server Push)

图片来源:lujjjh

由上图可知,当客服端向服务端请求 HTML 时,Server Push 服务端可以提前返回 HTML 所依赖的 css、js 等资源,这样可以节省解析 HTML 以及请求资源的时间,从而缩短页面的加载时间。

2. 如何升级 HTTP/2?

我们使用了 Nginx 作为前端页面与后端接口的反向代理服务器(Reverse Proxy),只需要修改一下 Nginx 配置文件就可以升级 HTTP/ 2 了,非常简单。

注意,在 Nginx 上 开启 HTTP/2 需要 Nginx 1.9.5 以上版本(包括 1.9.5),并且需要 OpenSSL 1.0.2 以上版本 (包括 1.0.2)。使用nginx -V 命令可以查看 Nginx 的版本信息:

nginx -V
nginx version: nginx/1.12.1
built by gcc 6.3.0 20170516 (Debian 6.3.0-18)
built with OpenSSL 1.1.0f  25 May 2017
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin3-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.12.1/debian/debuild-base/nginx-1.12.1=. -specs=/usr/share/dpkg/no-pie-compile.specs -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-specs=/usr/share/dpkg/no-pie-link.specs -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'

可知,我们使用的 Nginx 版本为 1.12.1,OpenSSL 版本为 1.1.0f,符合要求。

还有一点,虽然 HTTP/ 2 标准并没有要求加密,但是所有浏览器都要求 HTTP/ 2 必须加密,这样的话,只有 HTTPS 才能升级 HTTP/2。

如果你还没用过 HTTPS 的话,不妨看看我的博客:教你快速撸一个免费 HTTPS 证书,其实也很简单。

一切前提没问题的话(Nginx>=1.9.5,OpenSSL>=1.0.2,HTTPS),只需要修改 1 行配置,在 listen 指令后面添加 http2:

server
{
    listen 443 ssl http2;
    server_name www.fundebug.com;
}

重启 Nginx,升级 HTTP/ 2 就成功了,可以使用 curl 命令检查:

curl -sI https://www.fundebug.com
HTTP/2 200
server: nginx/1.12.1
date: Mon, 07 Oct 2019 00:12:53 GMT
content-type: text/html; charset=UTF-8
content-length: 4892
x-powered-by: Express
accept-ranges: bytes
cache-control: public, max-age=0
last-modified: Sun, 06 Oct 2019 23:07:25 GMT
etag: W/"131c-16da353dbc8"
vary: Accept-Encoding
strict-transport-security: max-age=15768001

3. HTTP/ 2 导致 Safari 浏览器 OPTIONS 请求失败

升级 HTTP/ 2 之后,使用 Safari 的用户发现无法登陆 Fundebug 了:

我们的前端异常监控插件捕获了这个报错:

可知,是 /api/members/login 接口出错了。

经过排查发现是 OPTIONS 请求失败了:

curl -X OPTIONS https://api.fundebug.com/api/members/login -v
*   Trying 120.77.45.162...
* TCP_NODELAY set
* Connected to api.fundebug.com (120.77.45.162) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=api.fundebug.com
*  start date: Sep 15 16:38:43 2019 GMT
*  expire date: Dec 14 16:38:43 2019 GMT
*  subjectAltName: host "api.fundebug.com" matched cert's"api.fundebug.com"*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fcfbb80ce00)
> OPTIONS /api/members/login HTTP/2
> Host: api.fundebug.com
> User-Agent: curl/7.54.0
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* http2 error: Invalid HTTP header field was received: frame type: 1, stream: 1, name: [content-length], value: [0]
* HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1)
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, Client hello (1):
curl: (92) HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1)

根据 curl 的报错信息,可知是 Header 中 content-length 有问题:

* http2 error: Invalid HTTP header field was received: frame type: 1, stream: 1, name: [content-length], value: [0]

将 Nginx 配置文件中 OPTIONS 请求的 Content-Length 配置注释掉,问题就解决了:

if ($request_method = "OPTIONS")
{
    add_header Access-Control-Allow-Origin *;
    add_header 'Access-Control-Max-Age' 86400;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, OPTIONS, DELETE';
    add_header 'Access-Control-Allow-Headers' 'token, reqid, nid, host, x-real-ip, x-forwarded-ip, event-type, event-id, accept, content-type';
    # add_header 'Content-Length' 0; // 必须注释,否则 HTTP/ 2 会报错
    add_header 'Content-Type' 'text/plain, charset=utf-8';
    return 200;
}

HTTP/ 2 中对于 Header 有特殊处理,这应该是导致出错的根本原因,关于这一个问题,我会在下一篇博客中详细介绍。

4. 升级 HTTP/ 2 真的提高性能了吗?

理论上来说,HTTP/ 2 应该可以提高网站性能,但是实际情况是怎样呢?HTTP/ 2 真的可以提高性能了吗?如果有的话,究竟提高了多少呢?

于是,我使用 Chrome 记录了升级 HTTP/ 2 前后 Fundebug 首页的加载时间,计算了 5 次加载的平均时间(单位为妙),如下表:

HTTP 版本 DOMContentLoaded Load Finish
HTTP/1.1 1.572 4.342 5.138
HTTP/2 1.0004 4.102 4.288

可知,HTTP/ 2 明显提高了首页加载时间,DOMContentLoaded、Load 与 Finish 时间均有明显提高。

一共也就改了 2 行 Nginx 配置,就可以提高页面访问性能,多好啊!

参考

  • Module ngx_http_v2_module
  • HTTP/2 Frequently Asked Questions
  • HTTP 的前世今生
  • The HTTP/2 Module in NGINX
  • HTTP/2: the difference between HTTP/1.1, benefits and how to use it
  • HPACK: the silent killer (feature) of HTTP/2
  • 浅谈 HTTP/2 Server Push

正文完
 0