背景
最近看到nginx已经支持http3.0/QUIC了,可惜node.js还没支持issue,今天抽空来安装nginx-quic体验下,在体验之前先回顾下http更新的主要版本和更新内容(不需要看的童鞋可以直接拉到后面看http3.0体验)。
在本文开始之前,大家可以先回想下,http到底在更新什么(主要特性)?
1、从http1到http1.1;
1、keepalive;
2、缓存优化,增加缓存控制字段,lastmodified, Etag等;
2、http1.1到http2.0;
1、多路复用;
2、首部压缩(压缩请求头,且相同的请求头只发送一次);
3、http2.0到http3.0;
1、使用udp替代tcp,不需要复杂的创建连接和关闭链接的成本;
2、基于传输层的多路复用,避免队头堵塞;
3、向前纠错;
keepalive和多路复用的实际体验
先简单解释下 keepalive 和 多路复用 概念:
// keepalive
创建一个tcp通道,允许多个(chrome限制6个请求)http请求使用该通道,节省了每个请求都创建一个tcp通道的时间;
// 多路复用
在单个tcp通道上,传输多个请求数据,不考虑传输数据的顺序,请求之间不阻塞;
在了解多路复用之前,有一个比较容易出现的误区,我一度以为多路复用是增加一次传输的数据量(每次传输多个请求数据)。实际上多路复用仍是每次只传输一帧(http1.1叫文本片段),实际速度根据网络速度和可用带宽决定;
关于 keepalive 和 多路复用 的区别其实我可以感觉到说的不够清晰,建议可以再看一下 彻底理解 IO 多路复用实现机制,知乎上也有一个很恰切的例子
第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。
下面我们来试下 keepalive 和 多路复用 在前端的实际应用区别,我构建一个简单的项目,用 http1.1 和 http2.0 分别请求 6/13 个静态资源文件;
一、请求6个静态资源;
1、http1.1;
2、http2.0;
二、请求13个静态资源文件;
1、http1.1;
2、http2.0;
由上面的 waterfall 可以得出一些结论(请求时间跟网络有关,主要看 waterfall):
1、在6个或以下的静态资源请求时,其实多路复用跟keepalive区别不大;
2、请求6个以上的静态资源,keepalive只能同时请求6个,只有等一个最先完成的请求后,才能插入新的请求,所以http1.1会比较慢;
队头堵塞(Head-of-line-blocking)
我理解的队头堵塞是一个有序的队列,前面的任务执行会阻塞后面的任务;
在http整个迭代过程中 队头阻塞 一直是一个非常重要的优化点;
下面我简单介绍下,在http1.0,http1.1,http2.0, tcp中的 队头堵塞 问题;
// http1.0
在http1.0中http请求是串联发送的,例如html中有两个js静态资源请求,需要在第一个下载完成后才会进行第二个请求;
// http1.1
http1.1使用keepalive优化了http1.0队头堵塞问题,但对大量静态资源请求仍会存在堵塞问题(并发6个请求限制);
// http2.0
http2.0使用多路复用解决了1.0和1.1版本的队头堵塞问题;
// http3.0
http2.0虽然解决了应用层队头堵塞的问题,但传输层tcp仍存在队头堵塞问题(e.g. tcp丢包会导致队列堵塞).http3.0通过udp/quic替换tcp来解决传输层队头堵塞问题.
tcp丢包(逻辑丢包)
关于丢包率,我看到有一篇文章解释的比较好:
主要原因是虽然数据包丢失确实发生在网络上,但还是比较少见的。特别是在有线网络中,包丢失率只有 0.01%。即使是在最差的蜂窝网络上,在现实中,您也很少看到丢包率高于2%。这与数据包丢失和抖动(网络中的延迟变化)通常是突发性的这一事实结合在一起的。包丢失率为2%并不意味着每100个包中总是有2个包丢失(例如数据包 42 和 96)。实际上,可能更像是在总共500个包中丢失10个连续的包(例如数据包255到265)。这是因为数据包丢失通常是由网络路径中的路由器内存缓冲区暂时溢出引起的,这些缓冲区开始丢弃无法存储的数据包。
我测试了下我自己的一台在新加坡的机器,简单测试了几个大文件请求,丢包率在1.3%左右,跟运维讨论了下,可以简单的通过两种方式查询,通过tcpdump/netstat命令记录对比,我用的netstat -s分别记录前一次的包数量和重传数量进行计算,但条件和参数太多会有一些遗漏导致计算并不会准确,后面有时间再深入研究分享(不会。
既然丢包是客观存在的,那tcp是怎么处理丢包的呢?
tcp是按顺序传递数据的,e.g. 收到第一个包,没收到第二个包,却收到了第三个包,就会将第三个包缓存到缓冲区(缓冲区也可能会内存溢出导致另外一种包丢失的情况),然后再像服务器请求获取数据包2,而这里的重传就会造成队头堵塞;
这也是为什么可能有时候我们觉得http2.0甚至比http1.1更慢的原因:
因为tcp重传机制会阻塞所有的请求,而http1.1对大量的请求会开启多个tcp通道(6个限制);
tcp那么多设备和平台估计是改不动了,所以谷歌基于udp实现了一个新的quic协议。
http3.0 初体验
http3.0目前仍处于草案阶段(2022-02),可能很多东西都还会修改,所以只是体验下,对比http2.0看下性能是否有很大的提升。
主要是通过别人开源的docker镜像运行的容器进行测试的:
// 注意问题
1、这个镜像里面有一些文件映射缺失的,反正根据nginx错误提示一步步补充下就好了。
2、主要的坑是客户端支持,我用谷歌或者谷歌canary版本开启http3都没办法测试,但测试某些http3网站是好的,后面是用火狐开启http3测试的。可能是版本不一致,毕竟还是草案阶段,做测试过程中需要考虑下客户端支持性。
先说测试条件和测试结果:
// 测试条件
1、测试下载12个84kb的js文件;
2、浏览器限制网速(chrome: fast 3G, firefox: good 3G);
3、自己国外的渣服务器;
// 测试结果,取中间结果
1、http1.1大概花了7.3s;
2、http2.0大概花了6.9s;
3、http3.0大概花了5.9s;
这个结果不会很准确,只是作为一点简单的参考。
但在测试http3.0的时候,water fall的显示非常奇怪:
不像http1.1先六个并发,等某个请求完成后继续发送其他请求,也不像http2.0全部并发,而是貌似没什么规律的(其实也有,仔细看图可以发现是三个三个并发)。暂时不太确定原因,可能是我搭建的这个版本的原因,等后面正式版本再深入了解下。
结论,http3.0应该还是会比http2.0有一些提升,但目前看不到非常明显的效率。
问题记录
1、为什么要用"不靠谱"的udp传输协议,怎么处理丢包问题?
目前只看到一个关于丢包的处理方式(前向纠错, Forward Error Correction),每个包除了本身的信息外,还会带有其他数据包的信息,当丢失一个包的时候,可以通过纠错机制进行恢复,两个或以上的话只能通过重传。
其实也增加了每个包的大小,也是一种妥协,不知道是否可以满足绝大部分网络环境,等后面上线后再进一步了解下。
参考文档
1、彻底理解 IO 多路复用实现机制: juejin.cn/post/688298…
2、高级 TCP 指标: satori-monitoring.readthedocs.io/zh/latest/b…
3、QUIC – Will it Replace TCP/IP? www.snia.org/educational…
4、nestealin.com/ce9634bf/