优化 Tengine HTTPS 握手时间

  • 日期:09-12
  • 点击:(1618)


云栖社区2011.1.12我想分享

作者金九,阿里云技术专家

背景

网络延迟是网络上的主要性能瓶颈之一。在最坏的情况下,客户端需要DNS查询(1个RTT),TCP握手(1个RTT),TLS握手(2个RTT),以及最后一个HTTP请求和响应以打开链接。接收第一个HTTP响应的第一个字节需要5个RTT时间,第一个字节时间对Web体验非常重要。它可以反映在网站的第一个屏幕时间,这直接影响用户对网站速度的判断,因此第一个字节时间(TTFB)是对网站和服务器响应速度的重要指标。让我们看看影响SSL握手的几个方面:

TCP_NODELAY

我们知道数据包的负载率非常小。如果网络上存在大量小数据包,则网络利用率相对较低。就像乘用车一样,很容易知道这种效率会很差。这是典型的TCP数据包问题。为了解决这个问题,有一个Nigle算法。算法的想法非常简单。它将缓存和合并多个数据包发送到一个大数据包,然后立即发送出去,就像乘用车一样。与完全启动一样,效率大大提高,因此默认情况下内核协议栈将打开Nigle算法优化。 Night算法认为,只要发送方没有收到前一个TCP段的ACK,发送方应始终缓存数据,直到数据达到可发送的大小(即MSS大小),然后统一并一起发送。走出去,如果收到最后发送的TCP段的ACK,将立即发送缓存的数据。虽然提高了效率,但它可能不适合需要紧急交付的小包。例如,在SSL握手期间交换的小数据包应立即发送,并且在发送的数据达到MSS大小之前不应发送。因此,应在SSL握手期间关闭Nigle算法。内核提供了关闭Nigle算法的选项:TCP_NODELAY,对应的tengine/nginx代码如下:

应该注意的是,此代码是2017年5月提交的代码。要使用旧版本的tengine/nginx,您需要自己修补它。

TCP延迟确认

与Nigle算法对应的网络优化机制称为TCP延迟确认,即TCP Delay Ack。这是接收器的机制。由于ACK数据包是有效负载较小的小数据包,如果频繁发送ACK数据包,也会引起网络。额外开销以及上述分组问题效率低下。因此,延迟确认机制将允许接收器将接收到的分组的多个ACK打包成一个ACK分组并将其返回给发送器,从而提高网络传输效率。与Nigle算法一样,内核默认也会启用TCP Delay Ack优化。此外,在接收数据之后,接收器不立即回复ACK,而是延迟一段时间。一般ACK延迟发送200ms(这个时间对于每个操作系统可能略有不同),但是这200ms不是数据在接收数据后需要延迟的时间,系统每隔200ms就有一个固定的定时器来检查是否需要发送ACK数据包,以便可以组合多个ACK来提高效率,所以如果我们去捕获数据包,我们有时会看到会有大约200ms的延迟。但是,对于SSL握手,200ms延迟对用户体验有很大影响,如下所示:

第9个数据包是客户端的ACK。服务器在7日发送的证书包已确认。两个数据包之间的差异接近200毫秒。这是客户端的延迟确认,因此SSL握手时间超过200ms。如何优化?事实上,只要我们尽可能少地发送数据包,我们就可以避免它们。例如,如果您同时发送第7个和第10个,则可以避免延迟确认。这是因为内核协议栈在回复ACK时接收到多于1个数据。 MSS将立即确认,内核源代码如下:

默认写缓冲区大小为4k。当证书很大时,很容易多次写入内核,这会触发客户端的延迟确认。

接下来,检查tengine是否已调整此缓冲区。它确实有(以下第903行):

那不应该有延迟的确认.

无奈之下,只能使用gdb Dafa。调试后,发现未调用BIO_set_write_buffer_size。原因是rbio和wbio是平等的。以前有这种情况吗?这是升级openssl的原因吗?继续检查openssl-1.0.2代码:

openssl-1.1.1的SSL_get_wbio已更改:

原因终于被发现,旧版本没有这样的问题。别看执行bbio,修复比较简单,可以使用旧版本的实现,所以我打了一个补丁:

重新编译打包的测试后,问题得到解决。遇到与新版本openssl相同问题的学生可以修补这个地方。

会话重用

完整的SSL握手需要两个RTT,SSL会话多路复用只需要一个RTT,这大大缩短了握手时间。另外,Session重用避免了密钥交换的CPU操作,大大降低了CPU的消耗,因此服务器必须启动Session。多路复用以提高服务器性能并减少握手时间。有两种方法可以在SSL中重用Sessions:

服务器会话缓存

原理类似于网页SESSION。服务器在服务器上缓存上次完成握手的会话信息,然后通知客户端会话ID。客户端会话的下一个会话使用会话ID加密,并且可以恢复SSL握手。会话信息是必需的,然后客户端和服务器使用相同的算法生成会话密钥并完成握手。此方法是优化SSL握手的最早方法。在早期,独立模式没有问题,但现在它是分布式集群模式。暴露了这种方法的缺点。在CDN的情况下,在一个节点内有几十台机器,前端使用LVS进行负载均衡,客户端的SSL握手请求到达哪个机器没有固定,这导致会话的重用率较低。因此,会话票据有一个优化计划,然后我将在稍后讨论它。如何优化分布式集群中服务器会话缓存的复用方法,有两种方式:一是LVS根据Session ID一致哈希,二是Session Cache分布式缓存;第一种方法比较简单。修改LVS可以实现,但是这可能会导致Real Server负载不均匀,我们使用第二种方式,在节点中部署redis,然后通过Tengine握手查找是否有来自redis的会话,有重用,如果它不存在,会话将缓存到redis并完成握手。当然,每次与redis交互时,都会有时间消耗。它需要进行多级缓存,因此不会在此处进行扩展。核心实现主要使用ssl_session_fetch_by_lua_file和ssl_session_store_by_lua_file,并在lua和redis以及cache中执行一些操作。

会议票据

上面提到了分布式集群中服务器Session Cache的缺点,Session Ticket用于解决优化方法的弊端,其原理类似于网页的cookie,客户端缓存会话信息(当然加密,称为会话票证,下次发送握手时,会话票据通过客户端的扩展字段发送到服务器。服务器使用配置的解密密钥对票证进行解密。在解密成功之后,获得会话信息,该会话信息可以直接重用,而不必执行完整的握手和密钥交换。大大提高了效率和性能,(客户端如何获得此会话票证,当然,服务器在完成握手后生成它并为其提供加密密钥)。可以看出,该方法不需要服务器缓存会话信息,并且自然支持分布式集群的会话复用。此方法也有缺点,并不是所有客户端或SDK都支持(但主要浏览器支持)。因此,当前服务器会话Cache和Session Ticket将存在,并且将来将基于Session Ticket。 Tengine打开Session Ticket也很简单:

更令人兴奋

识别QR码并立即进入免费试用

如果您认为这篇文章不错,请点击查看!点击这里了解更多!收集报告投诉

作者金九,阿里云技术专家

背景

网络延迟是网络上的主要性能瓶颈之一。在最坏的情况下,客户端需要DNS查询(1个RTT),TCP握手(1个RTT),TLS握手(2个RTT),以及最后一个HTTP请求和响应以打开链接。接收第一个HTTP响应的第一个字节需要5个RTT时间,第一个字节时间对Web体验非常重要。它可以反映在网站的第一个屏幕时间,这直接影响用户对网站速度的判断,因此第一个字节时间(TTFB)是对网站和服务器响应速度的重要指标。让我们看看影响SSL握手的几个方面:

TCP_NODELAY

我们知道数据包的负载率非常小。如果网络上存在大量小数据包,则网络利用率相对较低。就像乘用车一样,很容易知道这种效率会很差。这是典型的TCP数据包问题。为了解决这个问题,有一个Nigle算法。算法的想法非常简单。它将缓存和合并多个数据包发送到一个大数据包,然后立即发送出去,就像乘用车一样。与完全启动一样,效率大大提高,因此默认情况下内核协议栈将打开Nigle算法优化。 Night算法认为,只要发送方没有收到前一个TCP段的ACK,发送方应始终缓存数据,直到数据达到可发送的大小(即MSS大小),然后统一并一起发送。走出去,如果收到最后发送的TCP段的ACK,将立即发送缓存的数据。虽然提高了效率,但它可能不适合需要紧急交付的小包。例如,在SSL握手期间交换的小数据包应立即发送,并且在发送的数据达到MSS大小之前不应发送。因此,应在SSL握手期间关闭Nigle算法。内核提供了关闭Nigle算法的选项:TCP_NODELAY,对应的tengine/nginx代码如下:

应该注意的是,此代码是2017年5月提交的代码。要使用旧版本的tengine/nginx,您需要自己修补它。

TCP延迟确认

与Nigle算法对应的网络优化机制称为TCP延迟确认,即TCP Delay Ack。这是接收器的机制。由于ACK数据包是有效负载较小的小数据包,如果频繁发送ACK数据包,也会引起网络。额外开销以及上述分组问题效率低下。因此,延迟确认机制将允许接收器将接收到的分组的多个ACK打包成一个ACK分组并将其返回给发送器,从而提高网络传输效率。与Nigle算法一样,内核默认也会启用TCP Delay Ack优化。此外,在接收数据之后,接收器不立即回复ACK,而是延迟一段时间。一般ACK延迟发送200ms(这个时间对于每个操作系统可能略有不同),但是这200ms不是数据在接收数据后需要延迟的时间,系统每隔200ms就有一个固定的定时器来检查是否需要发送ACK数据包,以便可以组合多个ACK来提高效率,所以如果我们去捕获数据包,我们有时会看到会有大约200ms的延迟。但是,对于SSL握手,200ms延迟对用户体验有很大影响,如下所示:

第9个数据包是客户端的ACK。服务器在7日发送的证书包已确认。两个数据包之间的差异接近200毫秒。这是客户端的延迟确认,因此SSL握手时间超过200ms。如何优化?事实上,只要我们尽可能少地发送数据包,我们就可以避免它们。例如,如果您同时发送第7个和第10个,则可以避免延迟确认。这是因为内核协议栈在回复ACK时接收到多于1个数据。 MSS将立即确认,内核源代码如下:

默认写缓冲区大小为4k。当证书很大时,很容易多次写入内核,这会触发客户端的延迟确认。

接下来,检查tengine是否已调整此缓冲区。它确实有(以下第903行):

那不应该有延迟的确认.

无奈之下,只能使用gdb Dafa。调试后,发现未调用BIO_set_write_buffer_size。原因是rbio和wbio是平等的。以前有这种情况吗?这是升级openssl的原因吗?继续检查openssl-1.0.2代码:

openssl-1.1.1的SSL_get_wbio已更改:

原因终于被发现,旧版本没有这样的问题。别看执行bbio,修复比较简单,可以使用旧版本的实现,所以我打了一个补丁:

重新编译打包的测试后,问题得到解决。遇到与新版本openssl相同问题的学生可以修补这个地方。

会话重用

完整的SSL握手需要两个RTT,SSL会话多路复用只需要一个RTT,这大大缩短了握手时间。另外,Session重用避免了密钥交换的CPU操作,大大降低了CPU的消耗,因此服务器必须启动Session。多路复用以提高服务器性能并减少握手时间。有两种方法可以在SSL中重用Sessions:

服务器会话缓存

原理类似于网页SESSION。服务器在服务器上缓存上次完成握手的会话信息,然后通知客户端会话ID。客户端会话的下一个会话使用会话ID加密,并且可以恢复SSL握手。会话信息是必需的,然后客户端和服务器使用相同的算法生成会话密钥并完成握手。此方法是优化SSL握手的最早方法。在早期,独立模式没有问题,但现在它是分布式集群模式。暴露了这种方法的缺点。在CDN的情况下,在一个节点内有几十台机器,前端使用LVS进行负载均衡,客户端的SSL握手请求到达哪个机器没有固定,这导致会话的重用率较低。因此,会话票据有一个优化计划,然后我将在稍后讨论它。如何优化分布式集群中服务器会话缓存的复用方法,有两种方式:一是LVS根据Session ID一致哈希,二是Session Cache分布式缓存;第一种方法比较简单。修改LVS可以实现,但是这可能会导致Real Server负载不均匀,我们使用第二种方式,在节点中部署redis,然后通过Tengine握手查找是否有来自redis的会话,有重用,如果它不存在,会话将缓存到redis并完成握手。当然,每次与redis交互时,都会有时间消耗。它需要进行多级缓存,因此不会在此处进行扩展。核心实现主要使用ssl_session_fetch_by_lua_file和ssl_session_store_by_lua_file,并在lua和redis以及cache中执行一些操作。

会议票据

上面提到了分布式集群中服务器Session Cache的缺点,Session Ticket用于解决优化方法的弊端,其原理类似于网页的cookie,客户端缓存会话信息(当然加密,称为会话票证,下次发送握手时,会话票据通过客户端的扩展字段发送到服务器。服务器使用配置的解密密钥对票证进行解密。在解密成功之后,获得会话信息,该会话信息可以直接重用,而不必执行完整的握手和密钥交换。大大提高了效率和性能,(客户端如何获得此会话票证,当然,服务器在完成握手后生成它并为其提供加密密钥)。可以看出,该方法不需要服务器缓存会话信息,并且自然支持分布式集群的会话复用。此方法也有缺点,并不是所有客户端或SDK都支持(但主要浏览器支持)。因此,当前服务器会话Cache和Session Ticket将存在,并且将来将基于Session Ticket。 Tengine打开Session Ticket也很简单:

更令人兴奋

识别QR码并立即进入免费试用

如果您认为这篇文章不错,请点击查看!点击这里了解更多!