WEB加速,协议先行 ( 下)

《 WEB加速,协议先行 ( 上)》,下面我们看一下TLS协议的优化。

TLS协议最大的性能问题也是它的握手。所以优化的目标也非常明确,就是减少完全握手,提升简化握手的比例。协议层面提供了两种机制,session id和session ticket,我相信接触过的同学都非常清楚,网上的资料也非常多,关于原理和过程我就不多做介绍了。

这里我主要是分享两点:

1.通过提升简化握手比例,iOS Qzone的SSL握手时间提升了50%,从200ms节省到了100ms。

2.虽然session ticket是一种更加优秀的机制,因为它不需要服务端做缓存,但是iOS目前还不支持session ticket,要想实现简化握手,必须要支持session id,并且最好是实现分布式session cache来提升简化握手比例。

然后我们再来看一下完全握手,因为有很多场景下必须要进行完全握手,比如用户第一次打开浏览器或者App,用户关闭tab页面再打开,用户手机或者系统重启等,都需要进行完全握手,因为前面提到的sesison ticket还是session id都是基于内存的,客户端重启之后再发起握手默认就无法携带上这些信息,必须进行完全握手。

针对完全握手该如何优化?

优化思路类似TFO,即在完全握手的第二个阶段,即密钥交换阶段,提前发送应用层数据,节省这一个RTT对性能的影响。

上图左边是普通握手,可以看出必须要进行四次握手,两个RTT之后才能发送绿色的HTTP加密数据。上图右边就是false start,抢跑的意思。在第二阶段,clientKeyExchange消息发出的时候,将HTTP GET的应用层数据加密发出来了。相当于节省了一个RTT。

如何支持false start?

很简单。因为现在最新的客户端都已经支持了这个特性,所以对于服务端来廛,我们只需要将支持PFS(完美前向密码)算法的密码套件配置在前面就行了,比如ECDHE,DHE算法配置在最前面。协商好密码套件后,客户端就能提前发送数据,实现false start。False start对完全握手的优化效果也很明显,大概提升了30%。

接下来我们再看一下OCSP的问题。OCSP是在线证书状态检查协议,这个检查和证书本身的签名校验不是一回事。因为有一些情况,单纯检验签名是发现不了的。比如我们申请了一张有效期一年的证书,但不幸的是,申请下来的第一个月,私钥被内部人员泄露了,或者CA本身的数据库被黑客攻击了,我们需要主动撤销这张证书的信任关系。那就只能主动告诉CA这张证书不安全,然后客户端自己再去CA那边查询一下。因为这个时候证书本身的签名是没有问题的,如果不去额外查一下,在证书本身过期之前,永远也发现不了证书被撤销的事实。

OCSP的过程发生在客户端接收到server hello和certificate消息后,这个时候它会根据证书里的OCSP域名,发起OCSP请求,如上图左边所示。OCSP stapling的意思,简单来说就是服务端代理CA实现的OCSP内容的签发。服务端会提前向CA站点请求好OCSP内容,并保存在本地,在握手的时候,将OCSP内容和证书一起发送给客户端,这样客户端就不需要自己主动去请求CA查询OCSP内容了。这样看来OCSP Stapling至少节省了三个RTT,效果应该非常不错。但事实上,OCSP Stapling的效果并不会特别突出,因为客户端有缓存。一般来讲会有7天,也就是说客户端7天中才会查询一次OCSP。对于一个用户经常访问的页面来讲,这个概率可能只有千分之一甚至万分之一。所以对用户的访问体验来讲,提升的效果也比较有限。

然后我们再看一下dynamic record size。为什么需要做这个动态调整呢?是因为TLS协议本身的HOL(队头阻塞)。

Record是TLS协议处理的最小单位,最大不能超过16K,一些服务器比如Nginx默认的大小就是16K。由于一个record必须经过数据一致性校验才能进行加解密,所以一个16K的record,就算丢了一个字节,也会导致已经接收到的15.99K数据无法处理,因为它不完整。

比如上图右边所示,假设一个record需要6个TCP segment传输完成,如果最后一个segment丢了,那么上层应用程序必须HANG在那里等,无法继续处理。

上述就是TLS协议层面的队头阻塞,那如何解决呢?也有两个方案:

1.Nginx高版本支持一个配置指令ssl_buffer_size,可以将它设置成4K,这样就算有HOL,影响的也只是4K数据,而不是之前的16K。

  1. 更好的一个方案是动态调整大小,思路类似tcp 的slow start。在TLS连接刚刚建立的时候,由于不知道网络速率,可以将record设置得小一点,比如1K,当发送速度逐渐提上来之后,再将这个record size设置成16K。这个方案也已经有开源的patch,是cloud flare提供的,大家有兴趣可以关注一下。刚才提到的一些TLS优化特性都是针对TLS1.2及其之前的协议版本。接下去我们看一下TLS1.3协议。这是一个具有革命性的创造性的极具里程碑意义的TLS协议。
    它现在迟迟没有发布的一个讨论焦点就是它到底该叫TLS1.3还是TLS2.0。在性能方面最大的提升就是能够实现1RTT的完全握手,能够实现0RTT的简化握手。 上图左边就是1RTT的完全握手,右边就是0RTT的简化握手,也就是说应用层数据可以握手消息一起发出来,而且都是经过加密的。关于TLS1.3协议的原理和0RTT的详细过程我就不做详细描述了,因为还没有正式发布,那这里为什么又给大家介绍呢?因为如果大家想尝鲜的话,现在就可以体验了。Openssl1.1.1以及Nginx 1.13.0目前已经支持TLS1.3的最新草稿draft20。也有一些客户端支持了TLS1.3,比如firefox。大家如果有自己的客户端,现在就可以参考这些实现进行集成。刚才我们介绍的是TLS协议,接下去我们再往上看,HTTP协议。
    之前提到过需要302跳转强制用户使用HTTPS。那能不能减少这个跳转呢?HSTS就是这个作用。这是一个HTTP的Header,客户端接收到这个头部后,就会在接下去指定的时间内,默认只发起HTTPS请求。不管用户输入qq.com, www.qq.com 还是 http://www.qq.com ,浏览器都会在本地进行跳转,直接发起HTTPS请求。就算返回的HTML里包含有HTTP资源,浏览器也会将它们全部替换成HTTPS资源。不过HSTS还是有一个安全风险,因为通常来讲它都是通过HTTP1.1协议返回的,所以很容易被中间者劫持,直接被干掉,这样客户端就可能永远也接收不到HSTS的头部,也就不会发起HTTPS请求了。为了解决这个问题,chrome提供了一个preload list的机制,大家可以给上图所示的网站申请,将自己的网站域名加入到preload list里,这样不需要返回HSTS,chrome也会默认使用HTTPS来访问你的网站。
    然后我们再来看一下SPDY和HTTP2,这里为什么要提一下SPDY呢,主要是两个原因:1.HTTP2的大部分特性,除了HPACK头部压缩算法,都是沿用自SPDY,可以说SPDY是HTTP2的鼻祖。现在越来越多的人只知道HTTP2,不知道SPDY,我这里介绍一下主要是为了向它致敬。2.现在还有很多的客户端只支持SPDY,比如Android4.4.4以前,以及iOS现在都支持SPDY,为了兼容一些老的客户端,提升它们的性能,我们腾讯云的服务端也是同时支持SPDY和HTTP2。上图列的几个HTTP2特性是大家都比较清楚的特性,其中多路复用是HTTP2最强大的特性,它能将多个请求在一个连接上并发地发出来,同时请求和请求之间在协议层面可以没 任何依赖,当然也可以有依赖。也就是说谁先处理完成谁就可以先返回,不会影响其他请求的处理。HTTP1.1的pipelining就不行,比如上图右边,如果在同一个连接上发起四个请求,那四个响应必须按照顺序返回,其中一个处理慢了或者丢失了,都会导致四个请求全都无法处理,也就是http1.1 pipelining的队头阻塞。
    前面几个HTTP2特性大家比较清楚,这里我再重点介绍一下两个大家可能不太清楚的特性,一个是头部压缩,从字面很好理解,就是压缩了头部大小,提升了传输效率。但压缩比真的如官方页面宣传的有90%吗?我们通过实验发现,在一个连接上发起第一次请求时,压缩只有30%,发起第二次请求时,压缩比能达到60%,一直到第三次请求以及之后的请求时,压缩比才能达到将近90%。为什么会这样呢?因为SPDY头部压缩是基于zlib的,HTTP2是基于Hpack的压缩算法,它们都是利用状态空间的重复信息进行压缩,也就是说信息越冗余越重复,压缩比才会越高。这带给我们的启发就是,在用户发起真正的请求前,我们利用JS提前发送两个空请求,积累重复的头部数据,当用户发起真正的请求时,已经是这个连接上的第三个请求了,这样用户请求的头部压缩比一下就能达到90% ,有利于提升用户访问速度。然后再来看一下server push。由于Nginx不支持server push,这个功能目前在国内用得还比较少,但确实很有用。比如客户端请求一个html,正常来讲它需要解析完DOM后再请求css和png,这里至少会有2个RTT。但是如果支持server push,我们在服务端配置一个link头部,这样服务器在接收到html请求后就知道将另外两个资源css和png 一起返回给客户端,不需要客户端发起额外的请求。这就是server push未发先至的作用,和inlining有点类似,但是相比inlining有两个好处:1.有利于缓存。因为inlining在html里,不仅增加了html的体积,而且html一般来讲是不会缓存的。2.减少开发成本,css 雪碧图也好还是图片内联,服务端都有一定的开发成本,有些开发甚至是反模式的,需要精确地位置调整和屏幕适配。
    分享一下HTTP2的实践建议。1.使用一个连接,或者使用尽量少的连接。为什么呢?有三个好处:a) 连接少意味着更少的连接建立成本,之前也提到了,HTTPS的连接成本很高。b) 能够实现更高的压缩比,因为数据都在一个连接上,提供的冗余信息更丰富,有利于压缩。c) 能够更好地利用TCP的特性。因为TCP的很多特性,包括滑动窗口,拥塞控制都是基于一个连接的,如果连接数量多,特别是网络拥塞的时候,很容易放大拥塞系数,加剧拥塞。2.使用更少的域名。一方面能够减少域名解析的时间,另外一方面也能建立更少的连接,作用和第一点类似。3.如果一定要使用多个域名,那么尽量保证多个域名解析到相同IP,并且使用了相同的证书。这样也能方便浏览器复用相同的连接,比如chrome就会作这样的智能判断。4.灵活运用server push,代替inlining。5.HTTP2只支持TLS1.2及之后的版本(TLS1.3)。而且只有TLS1.2的部分cipher suite才能使用HTTP2。所以如果大家想使用HTTP2,一定要注意配置好TLS1.2协议,参考RFC7540的规范,配置好密码套件。6.HTTP2不是万能的,如果你的页面很简单,比如只有几个元素,如果还是有性能问题,那也不能寄希望于HTTP2。HTTP2最强大的特性是多路复用,还是适合于解决多元素多请求的场景。
    最后我们再来看一下预建连接。所谓的预建连接就是在用户发起正直的请求前将连接提前建立好。这样当用户发起请求时,由连接建立所导致的开销成本,用户都是感觉不到的。所以预建连接可以说是一种最简单,最有效的方案。因为之前提到的一系列方案,就算是0RTT握手,也会有一些数据校验和计算的工作。预建连接的效果也非常明显,对性能的提升至少是400ms以上。那如何预建连接呢?主要有两个方法:1.通过link标签和头部告诉浏览器提前建立另外一个资源的连接。不过还有很多浏览器或者一些历史版本不支持这个特性。
  2. 通过页面的JS给另外一个资源发送请求,提前建立连接。

预建连接也可以根据具体的用户场景和用户行为来建立。比如:

1.首页提前预建子页面的连接。当用户打开百度首页的时候,它通常会发起搜索,那我们可以提前给搜索结果页面建立连接。

2.根据用户行为预测。比如用户进入QQ空间首页时,我们可以根据用户的浏览习惯,他是经常访问QQ相册,还是经常访问QQ商城来预建不同的连接。

预建好的连接也有可能会超时断开,比如HTTPS的超时时间是1分钟,HTTP2有可能是3分钟,那过了几分钟后连接就断开了,用户再次发起请求时又需要建立新连接,如何避免这种情况?

可以使用长连接保持。即我们使用JS周期性比如每分钟发起一次长连接保持的请求,stgw就提供一个空页面,JS访问这个页面我们返回1个字节,作用就是为了维持住这个连接,不让它中断。

总的来说,HTTPS的访问速度是可以超越HTTP1.1的,这里面最核心的一点是,HTTPS可以使用HTTP2,可以多路复用,而HTTP1.1不行。

上图的数据是两年前H5 QQ空间优化的效果,数据虽然有点老,不过思路和优化方法是一致的,没有过时。

前面提到了很多HTTP2的特性,性能也很强大,那HTTP2是未来吗?或者更准确地说,HTTP2是下一个十年,最有性能优势,最具有统治力的WEB协议吗?

可以说是。因为它的许多特性,包括多路复用,头部压缩,server push,优先级等,设计得十分先进,性能也十分优良,解决了许多WEB性能问题。也可以说不是,为什么?因为当前的HTTP2协议是构建在TCP和TLS之上的,由此导致了一系列问题:

1.TCP连接耗时。比如需要TCP三次握手才能建立连接,就算是有了TFO,也需要操作系统才能支持,有许多系统目前也不支持TFO。而且TFO本身,在第一次获取Cookie时,也需要一次额外的RTT才能实现接下去的快速握手。

2.TLS连接耗时,当前的TLS1.2至少需要1个RTT才能建立TLS连接。

3.TLS安全问题,TLS目前并没有针对TCP头部进行一致性校验,从而存在TCP头部被篡改的风险,比如修改滑动窗口数,修改TCP序列号等。

4.加剧TCP队头阻塞。TCP为了实现可靠性和数据的有序性,发生segment丢包后需要重传,就算丢包序列号之后的包提前到达了,也需要等待丢失的包重传才能通知应用层来读取数据。这就是TCP的队头阻塞问题,而HTTP2的多路复用,加剧了TCP的队头阻塞,因为一条连接上同时发送的数据变多了。队头阻塞的影响也就更严重了。

5.重传的模糊性问题。由于TCP重传segment的序列号和原始segment的序列号相同。在判断该segment及后续segment是否需要重传的时候,很容易迷糊。

6.拥塞控制,需要操作系统的支持,升级成本高。

以上种种,影响了HTTP2的性能,所以从这个角度来看,也可以说HTTP2并不是未来最有性能优势的协议,那什么才是呢?我觉得最有竞争力的一个协议就是QUIC。

让我们拥抱QUIC。什么是QUIC协议?简单来说就是使用UDP实现的HTTP2。它具体以下特性:

1.继承了HTTP2的大部分特性,包括多路复用,头部压缩,server push,优先级等。

2.当前支持0RTT握手,等TLS1.3发布后,也会使用TLS1.3的0RTT握手协议。

3.使用UDP传输,没有TCP连接建立的耗时。

4.针对IP Packet进行加密,减少了队头阻塞的程度。

5.针对UDP头部进行一致性校验,就算是修改了UDP头部也能及时发现。

由于篇幅关系,关于QUIC的原理和详细功能就不再做介绍了。感兴趣的同学可以关注我们后续的报道,如果有机会,下一次架构师峰会,也可以专门给大家重点分享。

上述提到的许多协议优化特性,我们都已经集成到了腾讯云CLB负载均衡。大家如果感兴趣的话,可以使用腾讯云的产品和CLB来体验一下。

注:本篇内容来自“腾讯技术工程官方号”,公众号ID:tegwzx

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏铭毅天下

Elasticsearch学习,请先看这一篇!

题记 Elasticsearch研究有一段时间了,现特将Elasticsearch相关核心知识、原理从初学者认知、学习的角度,从以下9个方面进行详细梳理。欢迎讨...

94314
来自专栏about云

Spark1.0.0 学习路线指导

问题导读 1.什么是spark? 2.spark编程模型是什么? 3.spark运维需要具有什么知识? 4.spark如何监控? 5.如何搭建开发spark? ...

2937
来自专栏腾讯技术工程官方号的专栏

WEB加速,协议先行 ( 上)

本文分享了 STGW 及腾讯云 CLB 在 WEB 协议优化过程中的实践经验,并对WEB协议的未来进行了探讨分析。

4212
来自专栏服务端技术杂谈

.Net做大型互联网项目性能差?看看StackOverflow的架构是怎么样的?

小编: 在整个web开发世界里,java,.net,PHP是三足鼎立的状况,但是相对于java和php都有优秀的大型互联网架构解决方案,.net的响应架...

2755
来自专栏互联网高可用架构

如何设计一款多场景分布式发号器(Vesta)

2603
来自专栏IT大咖说

移动端SDK优化的特点与经验分享

摘要 结合极光的业务和自身开发经验,极光高级Android工程师为我们简单介绍移动SDK与APP的区别,以及在做架构设计、性能优化上的一些经验。 ? SDK和A...

3466
来自专栏owent

libatbus基本功能及单元测试终于写完啦

经过茫茫长时间的编写+过年在家无聊补充和修正单元测试,再加上这两天的整理,终于把以前的这个关于服务器通信中间件的基本功能和相应的单元测试完成啦。还是可以热烈庆祝...

592
来自专栏Web 开发

大内存的机子的新玩具-FancyCache

自从这个学期开始,DDR3内存进入了白菜价时期,4G 1333笔记本的都只需要130RMB左右,果断入手两条

750
来自专栏解Bug之路

解Bug之路-记一次JVM堆外内存泄露Bug的查找 顶

JVM的堆外内存泄露的定位一直是个比较棘手的问题。此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤了Bug的源头。笔...

784
来自专栏云计算与大数据

阅读:配置中心,让微服务更『智能』

随着微服务的流行,应用和机器数量急剧增长,程序配置也愈加繁杂:各种功能的开关、参数的配置、服务器的地址等等。 同时,我们对程序配置的期望值也越来越高:配置修改后...

675

扫码关注云+社区