这个分享主要对提升WEB性能,在协议层面讨论优化的方案,主要分三个层面优化,TCP层面,TLS层,HTTP层。文中截图来自分享ppt http://ppt.geekbang.org/slide/show/946
TCP层面的优化
使用TFO(tcp fast open),使用TFO需要linux 内核版本在3.7以上。
内核选项设置
# 打开客户端TFO
sysctl -w net.ipv4.tcp_fastopen=1
# 打开服务端TFO
sysctl -w net.ipv4.tcp_fastopen=2
# 同时打开客户端和服务端TFO选项
sysctl -w net.ipv4.tcp_fastopen=3
TFO的基本步骤如下:
- 客户端发送一个SYN包到服务器,这个包中携带了Fast Open Cookie请求的TCP选项;
- 服务器生成一个cookie,这个cookie是通过使用密钥加密客户端的IP地址生成的。服务器给客户端发送SYN|ACK响应,在响应包的选项中包含了这个cookie;
- 客户端存储这个cookie以便将来再次与这个服务器的IP建立TFO连接时使用;
- 再次发起tcp连接请求,客户端发送一个携带应用数据和以TCP选项方式存储的Fast Open cookie的SYN包;
-
服务器验证这个cookie,如果合法,服务器发送一个SYN|ACK确认SYN和数据,然后数据被传递到应用进程;如果不合法,服务器丢弃数据,发送一个SYN|ACK只确认SYN,接下来走三次握手的普通流程;
客户端测试代码
int sockfd, n;
char recvbuffer[1024], sendbuffer[1024];
struct sockaddr_in servaddr;
char buf[20] = {"hello server"};
int ret = 0;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf ("create socket error: %s(errno: %d)\n", strerror (errno),
errno);
exit (0);
}
memset (&servaddr, 0, sizeof (servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons (5999);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = sendto(sockfd, buf, strlen(buf), MSG_FASTOPEN,
(struct sockaddr *)&servaddr, sizeof(servaddr));
if (ret < 0) {
printf ("send msg error: %s(errno: %d)\n", strerror (errno), errno);
}
close (sockfd);
在代码不需要使用connect,直接sendto发送数据。第一次交互时只是向服务器申请一个TFO cookie,数据并不在连接建立过程中送达;客户端拿到TFO cookie后,客户端每次用同样方式发送数据时都会在SYN包中携带数据。
在VM中双核2g内存的CentOS中对比普通建连和TFO建连1024次耗时
普通建连 | TFO建连 |
---|---|
457ms | 285ms |
服务端关键代码
// 将listenfd设置TCP_FASTOPEN选项
ret = setsockopt(listenfd, 6, TCP_FASTOPEN, &qlen, sizeof(qlen));
TFO在收到SYN的时候就创建socket并将数据提交给应用进程,在握手中传递数据。比普通模式节省了SYN|ACK与ACK的交互时间,减小了通信延迟。
TLS层面的优化
fasle start
False Start 有抢跑的意思。TLS False Start 是指客户端在发送 Change Cipher Spec Finished 同时发送应用数据(如 HTTP 请求),服务端在 TLS 握手完成时直接返回应用数据(如 HTTP 响应)。这样,应用数据的发送实际上并未等到握手全部完成,故谓之抢跑。
启用 False Start 之后,TLS 阶段只需要一次 RTT 就可以开始传输应用数据。使用false start有个前提是,必须使用支持前向安全性(Forward Secrecy)的加密算法,例如ECDHE。False Start 在尚未完成握手时就发送了应用数据,Forward Secrecy 可以提高安全性
** Session Resumption**
通过会话复用,提高tls连接速度,是 TLS 握手中生成的 Session ID。服务端可以将 Session ID 协商后的信息存起来,浏览器也可以保存 Session ID,并在后续的 ClientHello 握手中带上它,如果服务端能找到与之匹配的信息,就可以完成一次快速握手。
Session Ticket
Session Identifier 机制有一些弊端
- 负载均衡中,多机之间往往没有同步 Session 信息,如果客户端两次请求没有落在同一台机器上就无法找到匹配的信息
- 服务端存储 Session ID 对应的信息不好控制失效时间,太短起不到作用,太长又占用服务端大量资源。
Session Ticket可以解决这些问题,Session Ticket 是用只有服务端知道的安全密钥加密过的会话信息,最终保存在浏览器端。浏览器如果在 ClientHello 时带上了 Session Ticket,只要服务器能成功解密就可以完成握手。
** OCSP Stapling**
证书颁发者有时候需要作废某些证书,证书使用者可以通过 OCSP(Online Certificate Status Protocol,在线证书状态协议)查询证书是否已经作废。客户端会在 TLS 握手阶段进一步协商时,实时查询 OCSP 接口,并在获得结果前阻塞后续流程,这对性能影响很大。使用OCSP Stapling,可以使服务端在证书链中包含颁发机构对证书的 OCSP 查询结果,从而让浏览器跳过自己去验证的过程。服务端有更快的网络,获取 OCSP 响应更容易,也可以将 OCSP 响应缓存起来。从而提高TLS握手的速度。
0-RTT Handshake
在即将发布的TLS1.3中,服务器可以把自己的 ECDH 公钥长期缓存在客户端,那么客户端就可以用缓存里的ECDHE公钥,构造一个电子信封,在第一个RTT里,直接就发送应用层数据了。
HTTP层面
使用http2
HTTP/2 并没有改动 HTTP/1 的语义部分,例如请求方法、响应状态码、URI 以及头部字段等核心概念依旧存在。HTTP/2 最大的变化是重新定义了格式化和传输数据的方式,这是通过在高层 HTTP API 和低层 TCP 连接之间引入二进制分帧层来实现的。这样带来的好处是原来的 WEB 应用完全不用修改,就能享受到协议升级带来的收益。
HTTP/2相对于HTTP1的优点:
- 使用一个连接,握手少,头部做压缩,更好地利用TCP特性;
- 连接复用,减少域名解析的时间;
- HTTP/2 的多路复用特性,使得可以在一个连接上同时打开多个流,双向传输数据;
- Server Push,意味着服务端可以在发送页面 HTML 时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。
写在最后
针对这个分享之后,自己的一点思考。发现这些优化策略中有不少共同点,如复用,提前传输数据。在我司的产品中,在tls协议的使用中,可以尝试上述的一些优化策略,对TCP重连场景,也可以尝试使用TFO机制,但TFO需要较高的内核版本,在端上未必能支持。