李龙彦,网络技术专家,多年网络协议栈开发经验。
分享概要
一、HTTP/1.1简单介绍
二、HTTP/2介绍
三、HTTP/3介绍
四、结语
五、Q&A
一、HTTP/1.1简单介绍
与HTTP/1.0相比,HTTP/1.1主要有两个新的特性:keepalive和pipelining。
Keepalive实现了连接复用的功能,从而达到了“长连接”的目的。长连接即是长期存活不关闭的连接,短连接是连接用完之后就关掉的连接。
那什么情况下需要长连接呢? 如果一个业务的连接需要不停地传输数据,需要长期不间断地传输数据,那么它就需要“长连接”,需要用心跳包来保证连接的活性。
如果业务有多个请求需要发送,那么连接复用无疑是给业务带来了非常大的便利。在资源消耗上,不需要频繁地建立和销毁连接,节省了不少资源;在速度上,每次建立连接都至少需要TCP三次握手,https的话还额外需要TLS四次握手,这会增加很多网络耗时。
HTTP/1.1另一个重要的特性就是pipeling,流水线帮助HTTP/1.1实现了并发连接的功能。
如果你的业务有多个请求,而且请求之间没有先后依赖关系,那么使用并发功能无疑可以成倍地提升访问速度,以前在一个连接中,请求需要串行地进行,第一个请求必须成功了之后才能发起第二个请求;多个请求就要排着队逐一等待进行,有了pipelining功能,就可以并发进行(最多并发6个)。节省时间的同时,也造成了资源消耗的增加。
二、HTTP/2介绍
二进制帧。帧:HTTP/2 数据通信的最小单位消息:指 HTTP/2 中逻辑上的 HTTP 消息。例如请求和响应等,消息由一个或多个帧组成。HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,二进制协议解析起来更高效。HTTP / 1 的请求和响应报文,都是由起始行,首部和实体正文(可选)组成,各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。
流:存在于连接中的一个虚拟通道。流可以承载双向消息,每个流都有一个唯一的整数ID。
如果把连接比作一条马路的话,我们可以粗略地把“流”比作这条马路上的一个个车道。
为什么需要流呢?为了并发!但是前面HTTP/1.1的pipelining不是已经实现了并发了吗?这里有两点区别:
首先“流”的个数原则上是不受限制的,不像连接,一般最多只能创建6个,也就是最大并发量是6个,而流可以更多;
其次,“流”的创建和销毁带来的资源的消耗远远低于“连接”。
实际上数据传输中,TCP连接是不认识“流”的,对于TCP连接来说,所有的数据包都是这条连接上的,它无法区分当前数据包是哪个“流”。所以在TCP传输过程中,所有流的数据包都是混合在一起进行传输的。那么只有在HTTP应用层面才可以看到“流”ID,才能识别出当前的数据是属于哪条流的。一般情况下,一条流就代表了一个请求。
多流并发带来了请求优先级的问题,因为有的请求客户端(比如浏览器)希望它能尽快返回,有的请求可以晚点返回;又或者有的请求需要依赖别的请求的资源来展示。这也是为什么引入流的优先级和流依赖。
流的优先级表示了这个请求被处理的优先级,比如客户端请求的关键的CSS和JS资源是必须高优先级返回的,图片、视频等资源可以晚一点响应。
流的优先级的设置是一个难以平衡或者难以做到公平合理的事情,如果设置稍微不恰当,就会导致有些请求很慢,这在用户看来,就是用了HTTP/2之后,怎么有的请求变慢了。
HTTP/2解决了HTTP协议层面的队头阻塞,但是TCP的队头阻塞仍然没有解决,所有的流都在一条TCP连接上,如果万一序号小的某个包丢了,那么TCP为了保证到达的有序性,必须等这个包到达后才能滑动窗口,即使后面的序号大的包已经到达了也不能被应用程序读取。
这就导致了在多条流并发的时候,某条流的某个包丢了,序号在该包后面的其他流的数据都不能被应用程序读取。
这种情况下如果换作HTTP/1.1,由于HTTP/1.1是多条连接,某个连接上的请求丢包了,并不影响其他连接。所以在丢包比较严重的情况下,HTTP/2整体效果大概率不如HTTP/1.1。
并发量很大,很多请求需要同时发送。如果还是坚持用HTTP1.1,可能浪费资源很严重,而且用了HTTP/2,首部压缩功能也能快速提升。
请求/响应的body比较小,HTTP/2并不适合大文件的下载。如果你用了,请把它的优先级降低,不然它会影响其他请求。因为连接都被这条耗时很长的流占用着,其他的请求干等着。
RTT比较大。HTTP/2是可以更充分利用带宽,但是HTTP业务更看重的是耗时。在RTT较小的情况下,使用HTTP/2体现不出它的价值。4. 带宽高,丢包率低。在丢包率高的情况下,HTTP/2由于所有请求都在一条连接上,很容易造成TCP的队头阻塞,导致效果还不如HTTP1.1。
三、HTTP/3介绍
从一个小请求说起。我们知道,一个HTTPS请求会经历四个阶段:DNS解析,TCP三次握手建连,TLS四次握手协商会话密钥,应用请求的发送和应答。对于一个小请求而言,TCP建连需要消耗1个RTT(数据包的往返时间),TLS建连需要消耗2个RTT,用户数据的传输需要消耗1个RTT。那么总共需要消耗DNS+4RTT的时间。
在这么长时间中,只有1个RTT是真正用户传输应用数据的,所以我们可以认为:性价比很低,为了传输1个RTT的数据,前面需要消耗那么长时间进行建连。那么对于这种情况,能不能更快速地进行建连呢?这就是HTTP/3要解决的头等大事!HTTP/3底层不再使用TCP,改用QUIC协议达到了更高效的传输速度。
QUIC的全称是Quickly UDP Internet Connection,所以QUIC的特点就是一个字,快!QUIC基于UDP,又将TCP的安全可靠传输特性移植过来,再把TLS1.3标准协议拿过来,融合了一个协议叫QUIC,基于QUIC协议的HTTP协议,更新了一个版本号,这就是HTTP/3。
事实上,我们并不是真的需要更新HTTP版本,而是需要对底层传输协议进行升级!
接下来我们将围绕QUIC协议的5个特性进行详细介绍。
对比TCP+TLS协议,QUIC协议的握手非常简洁。
传统基于TCP的HTTPS的建连过程为什么如此慢?它需要TCP和TLS两个建连过程。
为什么需要两个过程?可恶就可恶在这个地方,TCP和TLS没办法合并,因为TCP是在内核里完成的,TLS是在用户态。也许有人会说把干掉内核里的TCP,把TCP挪出来放到用户态,然后就可以和TLS一起处理了。
首先,你干不掉内核里的TCP,TCP太古老了,全世界的服务器的TCP都固化在内核里了。所以,既然干不掉TCP,那我不用它了,我再自创一个传输层协议,放到用户态,然后再结合TLS,这样不就可以把两个建连过程合二为一了吗?是的,这就是QUIC。
QUIC的1-RTT建连:客户端与服务端初次建连(之前从未进行通信过),或者长时间没有通信过(0-RTT过期了),只能进行1-RTT建连。只有先进行一次完整的1-RTT建连,后续一段时间内的通信才可以进行0-RTT建连。
1-RTT的握手主要包含两个过程:
客户端发送Client Hello给服务端;
服务端回复Server Hello给客户端。
注意:在1-RTT握手完成之后,服务端会发送一个New Session Ticket报文给客户端,这个包非常重要,这是0-RTT实现的基础。
client和server在建连时,仍然需要两次握手,仍然需要1个rtt,但是为什么我们说这是0-rtt呢,是因为client在发送第一个包client hello时,就带上了数据(HTTP 请求),从什么时候开始发送数据这个角度上来看,的确是0-RTT。
另外需要注意的是,0-RTT存在前向安全问题,请谨慎使用!
QUIC通过连接ID实现了连接迁移。
我们经常需要在WiFi和4G之间进行切换,比如我们在家里时使用WiFi,出门在路上,切换到4G或5G,到了商场,又连上了商场的WiFi,到了餐厅,又切换到了餐厅的WiFi,所以我们的日常生活中需要经常性的切换网络,那每一次的切换网络,都将导致我们的IP地址发生变化。
传统的TCP协议是以四元组(源IP地址、源端口号、目的ID地址、目的端口号)来标识一条连接,那么一旦四元组的任何一个元素发生了改变,这条连接就会断掉,那么这条连接中正在传输的数据就会断掉,切换到新的网络后可能需要重新去建立连接,然后重新发送数据。这将会导致用户的网络会“卡”一下。
但是,QUIC不再以四元组作为唯一标识,QUIC使用连接ID来标识一条连接,无论你的网络如何切换,只要连接ID不变,那么这条连接就不会断,这就叫连接迁移!
QUIC限制连接迁移为仅客户端可以发起,客户端负责发起所有迁移。如果客户端接收到了一个未知的服务器发来的数据包,那么客户端必须丢弃这些数据包。
如图所示,连接迁移过程总共需要四个步骤。
连接迁移之前,客户端使用IP1和服务端进行通信;
客户端IP变成IP2,并且使用IP2发送非探测帧给服务端;
启动路径验证(双方都需要互相验证),通过PATH_CHANLLENGE帧和PATH_RESPONSE帧进行验证。
验证通过后,使用IP2进行通信。
连接迁移的实现,不可避开的两个问题:一个是四层负载均衡器对连接迁移的影响,一个是七层负载均衡器对连接迁移的影响。
四层负载均衡器的影响:LVS、DPVS等四层负载均衡工具基于四元组进行转发,当连接迁移发生时,四元组会发生变化,该组件就会把同一个请求的数据包发送到不同的后端服务器上,导致连接迁移失败;
七层负载均衡器的影响(QUIC服务器多核的影响):由于多核的影响,一般服务器会有多个QUIC服务端进程,每个进程负载处理不同的连接。内核收到数据包后,会根据二元组(源IP、源port)选择已经存在的连接,并把数据包交给对应的socket。在连接迁移发生时,源地址发生改变,可能会让接下来的数据包去到不同的进程,影响socket数据的接收。
如何解决以上两个问题?DPVS要想支持QUIC的连接迁移,就不能再以四元组进行转发,需要以连接ID进行转发,需要建立 连接ID与对应的后端服务器的对应关系;
QUIC服务器也是一样的,内核就不能用四元组来进行查找socket,四元组查找不到时,就必须使用连接ID进行查找socket。但是内核代码又不能去修改(不可能去更新所有服务器的内核版本),那么我们可以使用eBPF的方法进行解决。
由上图来看,假设有两个请求同时发送,红色的是请求1,蓝色的是请求2,这两个请求在两条不同的流中进行传输。假设在传输过程中,请求1的某个数据包丢了,如果是TCP,即使请求2的所有数据包都收到了,但是也只能阻塞在内核缓冲区中,无法交给应用层。但是QUIC就不一样了,请求1的数据包丢了只会阻塞请求1,请求2不会受到阻塞。
有些人不禁发问,不是说HTTP2也有流的概念吗,为什么只有QUIC才能解决呢,这个根本原因就在于,HTTP2的传输层用的TCP,TCP的实现是在内核态的,而流是实现在用户态度,TCP是看不到“流”的,所以在TCP中,它不知道这个数据包是请求1还是请求2的,只会根据seq number来判断包的先后顺序。
拥塞控制算法中最重要的一个参数是 RTT,RTT的准确性决定了拥塞控制算法的准确性;然而,TCP的RTT测量往往不准确,QUIC的RTT测量是准确的。
由于网络中经常出现丢包,需要重传,在TCP协议中,初始包和重传包的序号是一样的,拥塞控制算法进行计算RTT的时候,无法区别是初始包还是重传包,这将导致RTT的计算值要么偏大,要么偏小。
QUIC通过Packet Number来标识包的序号,而且规定Packet Number只能单调递增,这也就解决了初始包和重传包的二义性。从而保证RTT的值是准确的。
另外,不同于TCP,QUIC的拥塞控制算法是可插拔的,由于其实现在用户态,服务可以根据不同的业务,甚至不同的连接灵活选择使用不同的拥塞控制算法(Reno、New Reno、Cubic、BBR等算法都有自己适合的场景)。
QUIC是双级流控,不仅有连接这一个级别的流控,还有流这个级别的流控。如下图所示,每个流都有自己的可用窗口,可用窗口的大小取决于最大窗口数减去发送出去的最大偏移数,跟中间已经发送出去的数据包,是否按顺序收到了对端的ACK 无关。
业内统计数据全球有7%地区的运营商对UDP有限速或者禁闭,除了运营商还有很多企业、公共场合也会限制UDP流量甚至禁用UDP。这对使用UDP来承载QUIC协议的场景会带来致命的伤害。
对此,我们可以采用多路竞速的方式使用TCP和QUIC同时建连。除了在建连进行竞速以外,还可以对网络QUIC和TCP的传输延时进行实时监控和对比,如果有链路对UDP进行了限速,可以动态从QUIC切换到TCP。
理论上,HTTP/3在任何场景都适用,但是在有些场景下优化效果并不明显,比如:连接复用率非常高、需要下载或上传的文件特别大、网络特别好等。
然而,现实生活中,适用HTTP/3的场景还是占大多数的,尤其是在流媒体、语音助手、游戏等场景。
四、结语
QUIC协议的出现,为HTTP/3奠定了基础。这是近些年在web协议上最大的变革,也是最优秀的一次实践。面对新的协议,我们总是有着各种各样的担忧,诚然,QUIC协议在稳定性上在成熟度上,的确还不如TCP协议,但是经过近几年的发展,成熟度已经相当不错了,Nginx近期也发布了1.25.0版本,支持了QUIC协议。面对这样优秀的协议,我们希望更多的公司、更多的业务参与进来使用QUIC,推动QUIC更好发展,推动用户上网速度更快!
Q&A
Q1:怎么保证QUIC的安全使用?
A1:在协议层面,已经尽可能地用各种手段保证安全了,比如:retry机制、地址校验机制、抗放大上限机制等等。但是仍然存在一定的安全问题,比如0-RTT攻击,主机安全等。另外,在安全检测能力上也有一定的缺乏。如果想要保证QUIC安全的使用,我们还需要做好以下准备:
加密配置:QUIC协议内置了加密功能,可以通过TLS来保护数据的机密性和完整性。在配置和部署QUIC时,确保合适的加密算法和参数被使用,并定期更新SSL/TLS版本和证书。
防止中间人攻击:使用公开可信任的证书和证书颁发机构,以防止中间人攻击。对于客户端,要验证服务器的证书;对于服务器,要验证客户端的证书。
丢包恢复与拥塞控制:QUIC协议内置了丢包恢复和拥塞控制功能,这有助于应对网络中的丢包和拥塞现象,并能降低恶意行为对网络性能的影响。
协议解析和检测:实施深度包检测(DPI)技术,以检测和监控QUIC流量中的恶意行为,确保网络安全。
安全更新和补丁:及时更新和部署最新的QUIC实现和协议版本,以修复已知的安全漏洞和问题。同时,关注相关安全团队和社区所发布的安全补丁,并及时应用。
监控和日志分析:建立日志监控和分析机制,以实时监测和检测异常行为,并记录和分析系统日志,以追踪任何潜在的安全威胁。
加固网络基础设施:通过防火墙、入侵检测系统(IDS/IPS)、Web应用程序防火墙(WAF)等,加固网络基础设施。
在部署QUIC之前,建议进行安全评估和测试,以确保安全性和合规性的要求得到满足。
Q2:网络防火墙无法解密QUIC流量,潜在恶意流量怎么检测呢?
A2:网络防火墙无法解密QUIC流量,潜在恶意流量可能会绕过防护墙进行攻击,因此确实需要一种机制来检测这些流量。下面是一些可能的解决方案:
DPI(深度包检测)检测:DPI是指通过对报文进行深度解析,获取其应用协议信息,然后进行协议特征识别的技术。由于网卡将数据包中的数据交由aop,所以网络防火墙在无法对QUIC进行解密之后,可以使用DPI技术识别流量的协议类型,并根据协议特征对流量进行检测、过滤、阻断。
DNS SNI(服务器名称指示)识别:QUIC协议的握手阶段DNS查询会暴露目标服务器的名称,防火墙可以从DNS查询中识别出目标服务器,然后根据服务器区域和历史行为对流量进行检测和分析。
分析网络流量威胁:进行网络流量分析,监测隐蔽的威胁,检测流量的行为模式,进行行为异常检测。
自适应安全策略:如果QUIC流量对网络防火墙造成了太大负担,您也可以使用自适应安全防护策略,根据威胁情况,进行更加精细化的防护策略制定与调整,以提升安全防范水平。
需要注意的是,以上解决方案可能会带来一定的额外开销和性能压力,因此需要合理评估使用这些方案的可行性。同时,推荐综合使用多种安全技术,以提高安全防范能力。
Q3: 直接应用QUIC协议栈,代码体积过大,会否裁剪代码?具体如何选择被裁剪的代码?
A3:QUIC协议栈,代码体积过大,可以考虑仅保留与QUIC能力相关的功能,把没有必要的比如 log系统、无用的算法可以不编译进去。同时,选择一个轻量级库也是一个明智的做法;还有通过优化编译参数也是一个不错的思路。另外BoringSSL也是一个不小的库,也可以进行按需裁剪,但需要谨慎谨慎再谨慎。
在优化和裁剪代码时,可以进行代码分析和评估,找出影响代码体积和性能的主要因素,选择性地进行优化和裁剪,以在保证功能完整性的前提下,减小代码体积,提高应用性能,同时需要注意避免影响应用体验和安全性。
Q4:除了HTTP/3,QUIC还能用于其他协议吗?
A4:可以的,理论上来说,quic是一个传输层协议,除了HTTP协议,也可以应用在其他应用层协议上。在应用于其他协议上时,需要注意一些原则,如在选择QUIC协议时需根据实际场景和需求,进行适当的配置调整和优化;在使用过程中需要加强网络安全防护,保障数据传输的安全性;在网络设备及应用程序的开发和调试过程中需做相关的技术支持和调试工作。
Q5:网站整体迁移到HTTP/3新框架的成本怎样预估?客户端和服务端的网络协议栈如何选型?
A5:成本,我们可以分为服务器成本以及研发成本。
研发成本:客户端和服务端都需要支持HTTP/3,甚至中间负载均衡器也需要支持HTTP/3. 即使选择一个成熟的协议库,想要达到很好的效果也需要不小的研发成本。SDK接入一个开源协议库后,会遇到各种问题,性能问题是最头痛的,导致性能上不去的原因,处理协议库本身问题,往往还跟你所使用的事件机制是否合理,处理程序是否有冗余或死锁等问题。
服务器成本:QUIC更耗CPU和内存,至少需要增加20%的服务器成本。
开源协议栈的选择有很多,比较知名的有:Google的quiche,cloudflare的quiche,lsquic,亚马逊的s2n-quic。最终的选型应该根据实际需求、系统环境和预算等因素综合考虑,权衡不同协议栈的优缺点,选择适合自己业务和技术栈的网络协议栈。
Q6:Rtt跟丢包会有效的原因?
A6:同学是想问:为什么在RTT比较长、有一定丢包率的弱网场景,QUIC优化效果更明显?
RTT越长,建连所需要的时间就越长,用户的请求耗时中,网络耗时占比就会越大。那么QUIC就是解决网络耗时的,QUIC所发挥的价值就会越大。
丢包就需要重传,传统的TCP对于重传包有“二义性”,无法精确地计算RTT。而QUIC采用单调递增的Packet Number方式,解决了重传包的“二义性”问题,RTT计算准确。
Q7:QUIC衍生子版本众多,怎么选择适合的版本?现有版本使用时可能会遇到什么困难?
A7:QUIC 一直保持着高速发展,分为 gQUIC(Google QUIC)、iQUIC(IETF-QUIC)两大类,衍生的 QUIC 子版本有几十个。
选择适合的QUIC衍生子版本可以考虑以下因素:
功能需求:因为每个版本都可能有自己的独特功能,需要根据实际需求来选择。
兼容性:因为QUIC衍生子版本众多,有些版本可能不兼容其他版本,需考虑具体应用场景和系统环境。
可靠性:选择稳定、可靠的版本,避免出现意外情况。
性能:选择高效、性能好的版本,可以提高应用的响应速度和质量。
在使用现有版本时,可能会遇到以下困难:
兼容性问题:不同版本之间可能存在兼容性问题,需要在选择版本和应用时进行调试和测试。
安全性问题:一些版本可能存在安全漏洞,需要选择稳定、安全的版本。
性能问题:性能可能会受版本差异和网络环境影响,需要进行调试和优化。
支持问题:不同版本可能受到不同程度的支持,需要考虑长期维护和更新问题。
Q8:如何理解拥塞控制和流量控制的关系?
A8:流量控制要解决的问题是:接收方控制发送方的数据发送的速度,就是我的接收能力就那么大点,你别发太快了,你发太快了我承受不住,会给你丢掉,你还得重新发。
拥塞控制要解决的问题是:数据在网络的传输过程中,是否网络有拥塞,是否有丢包,是否有乱序等问题。如果中间传输的时候网络特别卡,数据包丢在中间了,发送方就需要重传,那么怎么判断是否拥塞了,重传要怎么重传法,按照什么算法进行发送数据才能尽可能避免数据包在中间路径丢掉,这是拥塞控制的核心。
所以,流量控制解决的是接收方的接收能力问题,一般采用滑动窗口算法;拥塞控制要解决的是中间传输的时候网络是否拥堵的问题,一般采用慢启动、拥塞避免、拥塞恢复、快速重传等算法。
Q9:请问从端到端考虑,一般网络请求耗时多少算是正常?
A9:不同的网络环境,不同的请求大小,耗时差异非常大,要看具体情况。不能一概而论。
但是有一个标准:站在用户的角度上看问题,是否影响用户使用体验,如果明显体验不好,那么就是耗时过长了。
Q10:可以用HTTP3加载网页静态资源,然后用HTTP1.1调用接口吗?
A10:可以,HTTP/3可以用来加载网页的静态资源,而HTTP/1.1可以用来调用接口。HTTP/3是基于QUIC协议的,它能够提供更快的网络连接和更好的性能。可以通过HTTP/3来加载网页的静态资源,包括HTML、CSS、JavaScript、图片等。使用HTTP/3能够减少连接建立的延迟以及减少对服务器资源的消耗,从而加快网页的加载速度。
而对于接口调用,HTTP/1.1在许多场景下仍然可以使用。HTTP/1.1是目前最广泛支持的HTTP协议,它具有广泛的兼容性和广泛的支持,许多服务器和客户端都可以对其进行支持。对于简单的接口调用,HTTP/1.1仍然能够提供足够的性能。
通过使用HTTP/3加载网页的静态资源,同时使用HTTP/1.1进行接口调用,可以充分发挥两个协议的优势,提供更好的用户体验和性能。
Q11:文件上传场景适合QUIC协议么?
A11: 可以用在文件上传场景,具体能带来多大的提升,还需要具体业务具体分析。做项目要考虑投入产出比。
Q12:QUIC有哪些缺点?
A12:虽然QUIC协议有许多优势,但也存在一些缺点:
设计复杂:相比较于TCP协议,QUIC协议的设计较为复杂,需要支持更多的协议特性和功能。
难以调试:由于QUIC协议采用了加密技术,对数据进行加密,难以对其进行协议分析和调试。
对网络资源的需求较高:由于QUIC协议需要建立加密连接、实现多路复用,相对TCP协议而言,占用的网络资源多,如带宽、内存和CPU等。
更易受到恶意攻击:由于QUIC协议使用SSH进行加密,如果用户的密码或SSH证书丢失,可能会使整个网络处于风险状态。
针对这些缺点,建议在选择QUIC协议作为传输层协议前,评估好自己对于网络传输的需求和系统环境,选择合适的协议;同时,在使用QUIC协议时,注意安全防护,加强密码管理和认证授权,避免可能的网络攻击和数据泄漏风险。
获取本期PPT,请添加群秘微信号:dbayuqing
点这里可回看本期直播
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721