优化 Tengine HTTPS 握手时间

背景

网络延迟是网络上的主要性能瓶颈之一。在最坏的情况下,客户端打开一个链接需要DNS查询(1个 RTT),TCP握手(1个 RTT),TLS 握手(2个RTT),以及最后的 HTTP 请求和响应,可以看出客户端收到第一个 H. . ) t NTTP 响应的首字节需要5个 RTT 的时间,而首字节时间对 web 体验非常重% P P 1 Z e a N v要,可以体现在网站的首屏时间,直接影响用户判断网站的快慢,所以首字节时间(TTFB)是网站F O 9和服务器响应速度的重要指标,下面我们来看影响 SSL 握手的几个方面:

TCP_NODELAY

我们知道# { i,小包的载荷率非常小,若网络上出现大量的小包,则网络利用率比较低,就像客运汽车,来一个人发一辆车,可想而知V E O ^ Z A = [效率将会很差,这就是典型的 TCP 小包问题,为了解决这个问题所以就有了 Nigle 算法,算法思想很简单,就是将多个即将发送的小包,缓存和合并成一个大包,然后一次性发送出去,就像客运汽车满员发车T p * y H v 4 i {一样,这样效率就提高了很多,所以内核协议栈会默认开启 Nigle 算法优化。Night 算法认为o K C f只要当发送方还没有收到前一次发送 TCP 报文段的的 ACK 时,发送方就应该一直缓存数据直到数据达到可以发送的大小(即 MSS 大小),然后再统一合并到一起发送出去,如果收到上一次发送的 TCP 报文段的 ACK 则立马将缓存的数据发送出去。虽然效率提高了,但对于急需交付的小包可能i a z h s就不适合了,比如 SSL 握手期间交互的小包应该立即发送而不应该等到发送的数据达到 MSS 大小才发送,所以m ) l : t Z 1 C,SSL 握手期间应该关闭 Nigle 算法,内核提供了关闭 Nigle 算法的选项: TCP_NODELAY,对应的 tengine/nginx 代码如下:

优化 Tengine HTTPS 握手时间
优化 Tengine HTTPS 握手时间

需要注意的是这块代码是2017年5月份才提交的代码,使用老版本的 tengine/nginx 需要自己打 patch。

Tp ] X ; ^ SCP Delay AN ^ zck

与 Nigle 算法对应的网络优化机制叫 TCP 延迟确认,也就是 TCP Delay Ack,这. * 8 ; ~ G C T个是针对接收方来讲的机制,由于 ACK 包是有效 payload 比较少的小包,如果频繁的发 ACK 包也会导致网络额外的开销,同样出现前面提到的小包问题,效率低下,因此延迟确认机制会让接收方将多个收到数据包的 ACK 打包成一个 ACK 包返回给发送方,从而提高网络传输效率,跟J t s o - v v @ Nigle 算法一样,内核也会默认开启 TCP Delay Ack 优化。进一r S N步讲,接收方在收到数据后,并不会立即回复h f x ACK,而是延迟一定时间,一般ACK 延迟发送的时间为 200ms(每/ N ` H 7 N ,个操作系统的这个时间可能略有不同),但这个 200ms 并非收到数据后需要延迟的时间,系统有一个固定的定时器每隔 200ms 会来检查是否需要发送 ACK 包,这样可以合并多个 ACK 从而提高效率,所以,如果我们去抓包时会看到有时会有 200msR / | { 左右的延迟。但是,对于 SSL 握手来说,200ms 的延迟对用户体验影响很大,如下图:

优化 Tengine HTTPS 握手时间

9号包是客户端的 ACK,对 7号服务器端发的证书包进行确认v | Q ~,这两个包相差了将近 200ms,这个就是客户端的 delay ack,这样这次 SSL 握手时间就超过 200ms 了。那怎样优化呢?其实只要我们尽量少发送小包就可以避免,比如上面的截图,只要将7号和10号一J 6 - | ( J起发送就可以避免 delay ack,这是3 ^ v z J k s因为内核协议栈在回复 ACK 时,如果收到的数据大于1个 MSSW q - 时会立即 ACK,内核源码如下:

知道了问题的原因所在以及如何避免,那就看应用层的发送数据逻辑了,由于是在 SSL 握手期间,- I q o所以应该跟 SSL 写内核有关系,查看S , 9 T 9 openssl 的源码:

优化 Tengine HTTPS 握手时间

默认写Q E p y buff@ ] i s 0 = & p Jer 大小是 4k,当证书比较大时,就容易分多次写内核,从而触发客户端的 delay ack。
接下来查看 tengine 有没有调整这个 buffer 的地方,还真有(下图第903行):

那不应该有 delay ack 啊……
无奈之下只能上 gdb 大法了,调试之后发现果然没有调用到 BIO_set_write_buffed 3 C Mr_size,原因是 rbio 和 wbio 相等了,那为啥以前没有这种情况现在才有呢?难道是升级 oZ r upenssl 的原因?继续查 openssl-1.0.2 代y @ 5 q [ q码:

openssl-1.1.1 的 SSL_get_wbio 有了变化:

优化 Tengine HTTPS 握手时间