服务是一种流行的设计模式,其中一个大型应用程序被分解为多个独立且松散耦合的服务,这些服务通过预定义的接口相互通信;Walmart 的ML平台使用相同的原理构建: 部署在 Kubernetes 集群中的独立服务通过 REST API 进行通信。
作为平台功能,为事件提供以用户为目标的通知是一项优先要求。为此,我们开发了一个模型框架,可供任何对通知服务感兴趣的基于微服务的应用程序使用。
在较高级别上,系统应该能够根据以下规则生成和处理通知。
除了所需的规则外,我们还向系统添加了一些所需的功能:
我们将整个系统设计为一个基于 Java 的库,可以将其导入任何有兴趣发送通知的微服务中。对于通知存储,Redis 是首选,服务器发送事件 (SSE) 用于将通知发送到 UI 客户端(用户的浏览器)。我们将在后续部分中分别介绍每个系统,然后将它们放在一起,看看它们是如何加起来完成此功能的。
后端实现
1. 通知模型
通知结构的一个非常简单的设计需要两个字段——目标用户和消息。这是我们最初采用的结构,随着功能开始成熟,添加了更多字段以增强界面并在数据结构中捆绑更多信息。最后,我们正式确定了这种通知结构。
|
2. 存储通知
在 Notification Store 中存储通知有两个要点需要考虑:
作为键值存储的 Redis 以毫秒级的延迟完成了这两项任务。此外,Redis 已被证明具有故障恢复能力和高度可扩展性。因此,它被选为通知的后备存储。
此外,Redis 具有对更高 ADT 的内置支持,例如列表和映射。我们利用映射(即 Redis 中的哈希)来存储通知。对于每个用户,存储通知 JSON 的相应通知 id 哈希值。每个唯一的用户 ID 都充当 Redis 中的一个键。这种结构确保支持系统中的 2³² 个用户,每个用户都有 2³² 个潜在的通知。
Redis 可以以线程安全的方式支持高并发工作负载。它还可以通过 Redis Sentinels 提供生产级支持以实现高可用性和 AOF 文件备份以实现持久性。有关于如何设置它们以运行生产级 Redis 集群的优秀文档(请参阅Redis Sentinel和Redis Persistence)。
3.推送通知到存储
通知库公开了NotifierClient具有notifyUsers和notifyGroup方法的接口。要触发通知,微服务将notifyUsers使用Notification对象和要向其发送通知的用户 ID 列表调用该方法。该库还允许创建可用于将相似用户(例如,特定项目的所有用户、使用 GPU 的所有用户等)聚集在一起的组,并且微服务可以选择使用notifyGroup方法向整个组发送通知。
Jedis是 Java 中最著名的与 Redis 通信的库,我们在库中使用它来读写通知。Jedis 支持 Sentinel 支持和连接池等高级功能,这也使其成为生产服务器的理想选择。
为了防止读取和写入偶尔中断,对 Redis 的调用通过Resilience4J 进行包装,以确保在出现临时故障时进行正确的重试和错误处理。
前端实现
为了启用前端,我们使用 Express 服务器作为用户浏览器和后端微服务之间的中间件来执行身份验证和会话管理。我们搭载在这个服务器上从 Redis 读取通知并将它们推送给用户。
1. 将通知推送到浏览器
服务器发送事件 (SSE) 是一种建立在 HTTP 之上的技术。对于登录到系统的每个用户,我们在用户会话期间建立一个持久的 HTTP 连接。SSE 的协议规范规定将 JSON 数据转换为字符串,并且每个事件以两个换行符结尾。
建立连接后,我们利用 Node.js 事件模型在 Redis 提供新通知时将数据推送到客户端。我们发出一个通知事件,该事件附加到 HTTP SSE 处理程序范围内的事件侦听器。我们使用登录用户的唯一 id 将来自 Redis 的消息与用户的连接进行匹配。
这个GitHub 代码片段描述了它是如何完成的。
2.在浏览器中接收通知
在客户端,SSE 提供了一个 EventSource API,允许我们连接到服务器并从它接收更新。SSE 有一个限制,它可以同时支持六个并发连接。由于我们在每个浏览器选项卡中打开一个新连接,因此它限制了我们的用户一次只能打开六个选项卡。为了规避这个限制,我们使用SharedWorkers。这使我们能够在 SharedWorker 中创建一个持久连接,并通过不同的浏览器选项卡和 iframe 访问它。
共享工作者SharedWorker的一个缺点是它在 Safari 和 IE 上不受支持,但由于我们的大多数用户群都在 Chrome 和 Firefox 上,因此这被视为可接受的解决方案。如果用户使用 IE 或 Safari,我们将退回到仅允许 6 个选项卡的 SSE 模型。
当用户第一次登录时,会实例化一个新的共享工作者实例,然后将其附加到窗口实例。然后可以在所有浏览器上下文中访问它。然后,网页可以使用 MessagePort 对象与共享 worker 通信,并附加一个事件处理程序,每次共享 worker 推送消息时都会调用该处理程序。
github中的以下代码段具有在浏览器中接收通知的代码。
3. 向用户显示通知
每次打开新选项卡或浏览器窗口时,共享工作者都会为每个新选项卡分配一个端口号。这些端口号为每个用户保存在一个数组中。每当生成新通知时,都会将其推送到所有端口,以使其在选项卡之间保持一致。关闭选项卡时beforeUnload会触发一个事件,在该事件中我们从阵列中删除相应的端口。
为了仅当用户在选项卡上处于活动状态时才显示通知,我们处理visibilitychange由Page Visibility API公开的事件。处理程序将页面标记为非隐藏,然后使用来自后端的通知刷新 redux 存储。这会触发 UI 的渲染,并且通知显示在小吃栏中。
桥接后端和前端
系统的两个部分协同工作以提供整个通知系统:
对于实时场景,新的通知一生成就需要通知 UI。我们使用 Redis PubSub 创建从后端服务到 UI 服务器的反馈通道,然后如所述通过 HTML5 SSE 与 UI 客户端(或用户的浏览器)进行通信。
当生成通知并将通知写入用户的密钥时,库还会在特定频道上生成一条 PubSub 消息,其中相应的用户 ID 已被修改。UI 服务器订阅给定的 PubSub 频道,并在接收到用户 ID 时在其内存中构建通知映射。如果用户在线,UI 服务器会在该用户的 SSE 套接字上发送整个通知 JSON 映射,以在他的浏览器中呈现它。
下面给出了一个粗略的序列图,用于演示通知实例从在后端服务中生成到向用户显示的流程。
一旦用户响应通知(阅读、点击或删除它),该信息必须流经后端存储。我们将其实现为 REST 端点。
该库本身公开了一个 Java API,它可以采用唯一的通知 id、用户 id 和要更新的状态,并且它将用更新的状态修补 Redis 中的通知。然后可以使用服务将这个 API 与 REST 或任何其他类似(例如 gRPC)端点包装在一起。
为了清除 Redis 中过期和删除的通知,库内部使用了Quartz调度程序。为了确保一次只运行一个清洁器实例,使用Redlock 算法来创建分布式锁定机制。
整个框架可在https://github.com/daichi-m/notification4J 上作为库使用。
天项目经理交给我一个开发任务。如果有人在前台下了订单就给后台仓库管理一个发货通知。也就是服务端触发一个事件,推送消息到客户端。如果我用websocket来做还要搞个websocket服务器,而且还 有不少配置。websocket是全双工通信,单向通信简直是杀鸡用牛刀。用轮询吧,浪费服务器资源不说,还不一定实时,订单处理慢了岂不是怠慢了客户。有没有别的选择呢?当然有!
1. SSE推送技术
SSE全称Server-sent Events,是HTML 5 规范的一个组成部分,具体去MDN网站查看相关文档。该规范十分简单,主要由两个部分组成:第一个部分是服务器端与浏览器端之间的通讯协议,第二部分是在浏览器端可供 JavaScript 使用的 EventSource 对象。通讯协议是基于纯文本的简单协议。服务器响应的内容类型是“text/event-stream”。响应文本的内容可以看成是一个事件流,由不同的事件所组成。每个事件由类型和数据两部分组成,同时每个事件可以有一个可选的标识符。不同事件的内容之间通过仅包含回车符和换行符的空行(“\r\n”)来分隔。每个事件的数据可能由多行组成。
如上图所示,每个事件之间通过空行来分隔。每一行都是由键值对组成。如果键为空则表示该行为注释,会在处理时被忽略。例如第10行。
第1行表示一个只包含数据的事件。会按照默认事件走(message事件)。第3-4行代表一个附带eventID的事件。第6-8行代表一个自定义事件。第10-14行代表一个多行数据事件,多行数据由换行符链接
key定义有以下几种:
SSE只适用于高级浏览器,但是注意IE不直接支持。IE上的XMLHttpRequest对象不支持获取部分的响应内容,所以不支持。每次总有IE,怪不得快被淘汰了。
2. SSE VS Websocket
2. SSE VS Websocket
3. Spring Mvc中的SSE
Spring Mvc对SSE进行了支持。如果你要声明一个SSE连接。只需要在你的控制器声明一个如下接口:
必须必须返回SseEmitter对象,SseEmitter对象是Session级别的,如果你要点对点针对每个session要独立存储。如果你是广播可以共用一个SseEmitter对象。按照SSE规范也必须声明produces为"text/event-stream"。当你调用该接口的时候将建立起SSE连接。
你可以在另一个线程中调用SseEmitter的send方法向客户端发送事件。你也可以在发送事件后调用complete方法来关闭SSE连接。
4. 浏览器端的EventSource
由于SSE 是HTML5规范。所以对于APP端必须有HTML才能支持。并且IE如果要支持需要使用一些兼容开发包,比如polyfill库。客户端因为只接受事件所以开发比较简单:
总结
今天介绍了SSE 服务端推送。和长轮训、comet、websocket相比而言比较轻量级。在一些需要服务器实时推送规模不大的业务场景实现更简单点。相信看了本文后你会很快入门。在实际开发中要根据业务对这几种推送进行技术选型。没有最好的只有最适合的。SSE对大多数开发者来说不够熟悉。
文章来源:https://dwz.cn/vQHsUK6H
作者:码农小胖哥
接上文HTTP请求的详细过程
http协议版本历史
http造就了万维网,http成就了互联网第三次信息技术革命并且影响着即将到来的第四次人工智能技术革命。
1989年第一个http协议,http0.9发布,发明了万维网,创建了世界第一个网页浏览器;
http1.0版本是在1996年正式发版的 ,这个时候才丰富了交互响应的文件类型,96年之前网页是不能播放音乐的;
97年发布http1.1版本,2015年才发布http2.0版本,源自谷歌的SPDY协议,目前现代浏览器完全支持http2.0,http2可以更好的表达设备的在线状态;
2022年6月,发布了http3 ,使用QUIC协议替代了TCP协议,作为位于应用层的通用传输协议,这是一种基于UDP构建的多路复用传输协议;
http3.0版本解决了TCP的一些问题,提高效率并保留了TCP的所有优点。
http2和http3这2个协议版本都是由谷歌主导的,并且第一个在chrome浏览器率先实现的;
http0.9到1.0版本见证了web1.0时代的开始,http1.1-2.0版本见证了web1.0并完成了它的使命和见证了web2.0时代发展到了今天。
这是开放式系统互联的OSI七层模型图,http协议位于应用程序层,需要经过传输层才能进行通信。
理论上传输层用哪个协议并不是那么重要,但一定要知道传输层的TCP和UDP两个协议到底是什么,着重介绍传输层的原因是因为它对http协议至关重要,后面的IP层和其他底层对TCP和UDP都很友好,而且对http各个版本的发展并没有影响。
TCP协议即传输控制协议,首先通过三次握手建立连接,然后传输数据,数据传输完毕,进入四次挥手关闭连接,这中间出现了数据丢失,则会重试保证数据传输可靠性。
可以看出TCP是一个面向连接的可靠的传输协议,而UDP协议是用户数据协议,是面向数据报文的,发出去就发出去了,发不出去就发不出去,发丢了消息也不会去重试,UDP不需要握手建立连接,发送消息的数据又小,所以速度更快。
为了保证传输的可靠性,http0.9到2.0版本都是采用tcp协议传输,到了3.0版本就抛弃了tcp改用udp传输,利用QUIC协议在应用层保证数据传输的可靠性;
http0.9是万维网第一个http协议,经过三次握手建立连接,客户端发起get请求,服务器仅能响应http文本类型的数据,但不能在客户端显示图片和播放音乐,客户端收到数据之后,四次挥手关闭连接,所以也叫单线协议,同时http0.9版本没有http标头,没有状态码、错误码,
如图所示仅仅使用一行请求信息,所以也叫它单行版本,就是因为0.9版本太简陋,服务器仅能响应html文件,比如制作个人网页,
于是就有了http1.0,请求加入了http协议标头,响应加了状态码和传输的内容类型,
也就是经常在服务器配置的MIME媒体类型,即多用途互联网邮件类型。
不为http服务器配置MIME类型,服务器就没办法解释这个内容类型是个啥,客户端也不认识就会出现问题,服务器配置好以后,就可以传输更多类型的文件内容数据了。
和0.9版本一样,1.0版本还是单线协议,一个连接只能处理一个请求一个响应,每次建立物理连接的成本很高,
并且服务器遭受不住太多客户端访问,否则服务器扛不住就会直接给你报503错误,为了减轻服务器压力,出现了升级的http1.1版本,该版本中引入了一个重要的概念,就是持久连接的概念,
即增加了keep-alive值的标头,就意味着建立连接后,指定时间内不会关闭这个连接,可以在保持的这个连接上发送多个请求,并且不会出现一个往返就关闭连接的情况,
节省了每次交互都会握手和挥手的时间,除此之外1.1版本还引入了流水线概念,在服务器响应前,客户端可以一次发送多个请求,服务器收到多个请求后,按顺序将准备好的数据再一个一个的回复给客户端,对比一下左边一次一个请求一个响应和右边一次多个请求逐个响应的过程,可以看到明显的节约了请求发送的时间,提高了传输的效率。
按顺序响应有没有问题?
首先就会造成一个重要的问题,head of line blocking(线头阻塞即流水线头阻塞),
假如客户端处理了三个请求,第一个请求处理的时间太长,第二个第三个请求是不是得必须老实的排队,等待第一个请求处理完成呢?
这就造成了流水线的塞车现象,在这个版本中解决塞车的办法就是浏览器通常会保持6个左右的连接以缓解一个连接塞车,客户端可以通过其他连接继续发送请求。
在连续发送请求的情况下,每次请求总是会带上相同基本不会变化的http标头,并且http1.1发送的数据还是纯文本的,传输数据不但大还啰嗦。
http2协议
以上2个问题就是http2要解决的,http2协议是http1.1版本的扩展而不是替换,因为它只是更好的解决了1.1版本的问题和功能补充,
b站网络请求就是使用的http2协议,
http2在应用层改变了传输方式,之前版本应用程序层传递的http标头和数据都是明文的,
http2在应用层增加了一个二进制帧数据处理层,
将http标头和数据明文消息拆分成二进制数据帧进行传输,
每个数据帧包含自己携带的数据并用stream id做标识,这也解释了什么叫数据流,就是将数据切开形成数据帧,然后将它们单独或者打包成独立流传输到目的地后,
再按照stream id将它们组装起来还原成原始数据。
好处就是OSI模型应用程序下的底层协议,更喜欢二进制数据帧,因为它们的大部分工作被应用程序层做了,它们的活少了,响应的网络传输速度也就提高了。
另一个好处就是再大的文件也给你剁的细碎,规避了大文件传输大小的限制,比如IIS、apache等http服务器都有对单个请求体又大小的限制。
客户端以流的方式请求,服务器以流的形式响应,就形成了双向数据流,数据帧都用stream id做标识,然后stream id将多个数据帧识别为一个消息,
保证了数据的完整性。
http2协议是客户端与服务器建立一个连接,再加上双向数据流,客户端和服务器的交互就可以重用它们建立的单个连接传递数据,就形成了http2的多路复用。
既然数据帧标明了stream id,那么传输数据流的顺序可以是乱序的,当然传输数据也可以设置优先权重的,
无论是客户端还是服器接收到的流数据先到的在缓冲区先组装,不用等待前一个请求数据处理完成再处理下一个,
就这解决了http1.1应用程序层的http线头阻塞的问题,http2是否真正解决了线头阻塞的问题?
之所以前面着重强调的是应用程序层的http线头阻塞,原因是http2仅在二进制帧处理层拿到完成的tcp数据后解决了线头阻塞问题,http2同样基于传输层的tcp协议传输的,
TCP标头格式
tcp协议为了保证消息可靠,顺序传递消息,就有可能造成塞车的情况,所以传输层的tcp线头阻塞问题并没有得到解决,为了说明这个问题,来模拟下传输层tcp传递数据包的视角。
当打开b站,我们和浏览器都明白我们向b站服务器请求获取js、css等文件资源,
http2通过前面讲的二进制帧数据处理层,将数据剁碎,加入stream id,用数据流的方式传递数据,其他的,http2并不关心,更不用说到达传输层的tcp协议了,甚至都不知道这是传输的是http请求、响应的信息,tcp只知道数据包的最大大小通常是1450字节。
http2跟踪传输的指定字节范围的数据部分,以便可以按正确顺序来还原原始数据,
假如tcp数据包2在传输过程中丢失了,
那么tcp数据包1和3会提前达到缓冲区,tcp就会发现数据包1和3之间缺少了数据包2的字节范围数据,哪怕http2知道tcp数据包1中有stream id为1的数据帧,加上tcp数据包3中stream为1的数据帧,可以组装成一个完整的消息,也要耐心等待tcp至少重返服务器一次,来重传丢包的数据2的数据副本到达才能继续解包其他tcp数据交给应用程序层,就是丢失的tcp数据包2阻塞了tcp数据包3导致了tcp的线头阻塞,所以结论就是http2并没有完全解决线头阻塞的问题。
接下来讲个http2重要的扩展功能:
http2服务器推送功能
首先正是因为http2具有双向流、多路复用的特点 ,才更有利于http2提供的服务器推送功能,比如直接打开b站 请求的是首页,
b站服务器可以将未经过请求的、首页所需的js、css等文件资源准备好之后直接推送给我们;
同时对于前端开发者,http2更加有益于html5套件的SSE功能(Server-Send Events即服务器发送事件),
服务器推送功能进一步减少了网络请求的数量,提高了交互的效率。
http2标头压缩功能
打开浏览器开发者工具,直接进入百度首页,
百度依然使用的是http1.1版本,
百度首页的请求标头是689字节,响应标头是598字节,点击标头标签可以看到请求和响应的所有标头信息,尝试刷新下页面,看到请求发送标头大小没有任何变化,说明http1.1来回总是在说车轱辘话。
b站使用的是http2协议,
b站首次请求的标头大小是387字节,响应的标头大小是417字节,进入标头标签可以详细看到http2请求和响应标头信息,
再次刷新页面,发现请求标头缩小至116kb,响应字节也小了,
再次刷新页面,发现b站请求和响应标头更小了,而达到这种节约网络请求的标头大小的效果就要归功于http2是使用HPACK来编解码http标头的。
HPACK的原理就是建立连接的客户端和服务器都维护着相同的61个条目的只读的静态表和可动态添加条目的空白的动态表,
当请求发送的时候先查找表中的名值,名值完全匹配直接提取该标头在表中对应的整数索引用于表示该名值对。
如果只匹配名称,则使用索引表示名称,值就用霍夫曼huffman算法进行编码后表示,
表中没有的,则按照顺序添加到动态表中,再使用霍夫曼算法编码名值对字符后传递给服务器;服务器端则反之, 使用表中的索引解码或者按顺序使用霍夫曼算法进行解码后添加到服务器动态表中,这样保证双方的表中保存的数据和整数索引是一致的,在之后的所有请求的标头会越来越小,减少了网络数据流量来提高交互的效率。
这是http2标准中已经定义的61个标头的静态表,
类似:path这样的标头名称,被称为伪标头,出现伪标头的原因是为了兼容之前的版本,
http2的标头都是名值对,之前版本的标头都存在标头行,http2将http1的标头行拆分成名值后的名称前加上了冒号,方便与http2的标头名称进行区分。
什么是霍夫曼算法
最优前缀码算法,利用二叉树将出现频率大的符号采用较短的有效编码,频率小的有效编码更长,解决等长编码解压时出现歧义的问题,
字节下的数字代表这个字母出现的频率,
霍夫曼算法会先从出现频率低的两个字母,按照左树代表0放出现频率小的字母
右树代表1放出现频率大的字母的原则合并组成一个新节点。
以此类推最终组成一个二叉树,
以上6个字母的最终编码如图所示,a=0直到f=1100,你是否认为http2每次都对未出现在表中的标头字符都这样算一遍?
http2标准按照标头字符的使用频率,专门准备了一张霍夫曼编码表,刚好256个条目,
对应ASCII编码表示的256个可能的字符,那么对应霍夫曼编码表,编码就不需要二叉树运算了,提高了编码的效率,例如在霍夫曼编码表中,位于索引47位置的符号/,由6位二进制编码011000组成,16进制编码值就是0x18。
http2留给我们的三个思考
因为客户端对同一个网站交互越多,
动态表中积累的http标头条目就越多,标头压缩的效果就越好,所以这里有一个http2的优化技巧,交互最好是同域名或者同域名下不同二级域名指向的服务器ip是一样的,
而且使用的是同一个加密证书,这样会确保使用相同的静动态标头表,这也解释了为啥泛域名的SSL加密证书每年费用那么贵的原因。
HPACK是按顺序编解码标头的是不是也会造成线头阻塞问题?
是的,因为http2数据流可以是无序的,
请求数据流达到服务器后,请求携带编码后的标头中,
发现有个请求标头索引比服务器解码动态标头表中的最大索引值还大,就会造成无法解码,而导致使用该请求标头相关的数据流都会被阻塞,直到携带该索引表示的正确霍夫曼编码的数据流达到后,
才能解码之前的请求数据流,如果这条数据流丢包了,就会造成塞车时间更长,目前这个塞车问题只能留给http3解决了。
第三个思考是使用多路复用,它真的就比之前版本的按顺序传输更快吗
真正解决了交互事务中的线头阻塞问题了吗? 答案是不一定
不是说好了速度更快且没有http线头阻塞问题?
首先表象的看http2和现代浏览器强制了必须使用https加密超文本协议传输,那https是如何工作的?
相比于http1.1之前不强制使用https加密,http2在加密交换密钥的过程中,最少增加了2个往返时间即2RRT(Round-Trip Time),反而减慢了交互速度。
另一个是多路复用中数据被切碎成数据帧,并用stream id标识打包成数据流传输,这样就可以乱序传输了。
假如我们一次请求一个js或css文件,根据文件大小假如被切分成了5份和3分,来对比下顺序传输和乱序传输
,顺序传输的js文件的接收速度比乱序的js传输速度更快,
总的到达时间基本一致的,所以在http2中的多路复用,就需要根据实际情况考虑使用优先级控制优化了。
再比如乱序中某个数据的丢包,在缓存中排序组装消息的时候发现少了一帧,
就需要至少往返一次服务器,拿丢失的数据帧副本,虽然没阻塞其他的消息,但确实阻塞了自己,所以这个问题 不单单存在于http2,http3的多路复用也存在这个问题,虽然这种数据流的线头阻塞是概率问题,但确实存在。
它是基于QUIC协议的升级版本,
已经知道了http2基于传输层的tcp协议传输,仅仅解决了在应用程序层的http线头阻塞的问题,
但又由于传输层的协议基本由系统实现,应用程序层无法干涉,所以http3就想,既然动不了你,干嘛不直接抛弃你,http3就直接使用了传输效率更高的UDP,虽然UDP不保证消息传输的可靠性,但在http2已经解决了应用程序层http的线头阻塞问题的基础之上,
http3在应用程序层适配UDP并保证数据传输的可靠性,就推出了基于UDP的QUIC协议,可以说它是tcp2.0,只不过是在应用程序层实现的,QUIC协议实现了tcp的可靠性、拥塞控制、流量控制、排序等功能和保留了http2基于流的多路复用及http2优化的其他功能。
http3将基于UDP的流作为一等公民和http2的流处理有2个重要区别
一个区别是http3使用动态标头编码QPACK和http2使用HPACK,QPACK和HPACK在编解码方式都是一样的,重要区别在与QPACK增加了静态表中的标头条目至98个。
另一个就是解决HPACK按顺序解码容易造成线头阻塞的问题,
QPACK的解决办法就是使用指令独立流,
提前讲好需要增加的标头项,使用添加指令发送给解码方,解码方收到后回复确认标头指令,从而双方利用标头动态表中标头项的状态,来确认标头是否完成同步,标头项状态未被确认的,请求的数据流就不会使用该项标头的索引编码标头以解决动态标头解码的塞车问题。
另一个区别就是因为QUIC协议实现了tcp协议的所有功能,那QUIC协议如何规避tcp线头阻塞的缺点?
http2存在tcp的线头阻塞,是因为tcp只知道跟踪携带的字节范围数据,并不知道我们前面讲的tcp线头阻塞例子中数据包1中的stream id为1的数据帧和数据包3中的stream id为1的数据帧可以组装成一个消息,因为tcp根本不知道传输的具体细节。
在http3中QUIC协议优化了帧的组成部分,脱掉了tcp外衣,直接在数据帧中加入了Offset字节范围帧头,
这样就能根据stream id和Offset帧头,判断哪些数据包中的帧数是同一个消息,因此例子中数据包1中的stream id为1的数据帧,
就可以和数据包3中的steram id为1的数据帧组成一个消息,因为它们的字节范围可以匹配的上,这样就解决了即使丢了数据包2,也不影响其他消息的情况,从而消除了tcp线头阻塞的缺点。
http3的三个重要优点
基于UDP有个明显的好处就是数据到达传输层是需要加入传输层协议的头部。
http2和之前的版本使用的是tcp协议,
需要20-60个字节的头部,
而http3采用传输层的UDP协议,头部仅仅8个字节,这样就进一步优化了交互数据的大小,提高了传输速度,QUIC协议基于UDP有个更大的好处就是0往返时间建立连接即0 RTT,
tcp建立连接是需要经历3次握手和四次挥手的,QUIC协议是基于UDP的,就不需要三次握手和四次挥手,名义上实现了0 RTT,但是http3也需要加密传输,直接将加密协商信息和请求数据一起发送给服务器,只需要1个RTT就可以完成建立连接,相比于http2的加密协商至少需要2个RTT来说,还少了一个RTT。
第三个优点:tcp是基于连接的,而QUIC协议基于UDP的数据传输,是基于报文的,那http3怎么识别当前连接并实现多路复用的?
使用tcp建立的连接会基于源IP、源端口、目的IP和目的端口四元组信息,来确认是否已经建立过该连接,假如说从wifi切换到5G至少会导致四元组信息中的源IP地址信息会发生改变,tcp就必须重新建立连接,
而QUIC协议使用的是随机数作为connection id即连接id,来识别客户端和同域名服务器建立连接,从而不受网络变化的影响,通过connection id确定了连接,当然就可以实现和http2一样的双向流多路复用的功能,基于这个优点,http3协议就非常适合移动互联网了。
*请认真填写需求信息,我们会在24小时内与您取得联系。