这个异步指的是网络请求不会阻塞其后 js 代码继续执行和浏览器渲染页面其他的部分。
同步和异步
同步和异步关注的是消息通信机制
同步就是在发出一个调用后,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。
异步就是调用发出后,这个调用就直接返回了,所以没有返回结果。换句话说调用者不会立刻得到结果而是在调用返回的时候被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是 5 秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
阻塞和非阻塞
阻塞和非阻塞关注的是程序在等待调用结果时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟 check 一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。
对头阻塞
http 的对头阻塞
对于长链接来说:同一个 tcp 连接上下一个请求发出之前需要等到上一个请求的结果返回,所以如果前一个请求的处理时间较长或者是被挂起,那么后面的请求就会一直排队等待。(所以 http 长链接的对头阻塞发生在客户端)
对于 http1.1 的管道化来说:同一个 tcp 连接上可以同时发送多个请求,服务器也可以同时处理这些请求,但是响应必须要按照顺序返回。如果最先收到的请求的处理时间长的话,响应生成也慢,就会阻塞已经生成了的响应的发送。也会造成队首阻塞。 http1.1 管道化的对头阻塞发生在服务器端(浏览器默认是禁止使用管道化的,所以并发请求的处理方式只能是同时建立多个 tcp 连接,但是这就会收到浏览器对同一域名下并发连接数的限制。现代浏览器默认是不开启 HTTP Pipelining 的)
管道化的定义:一个支持持久连接的客户端可以在一个连接中发送多个请求(不需要等待任意请求的响应)。收到请求的服务器必须按照请求收到的顺序发送响应。
至于标准为什么这么设定,我们可以大概推测一个原因:由于 HTTP/1.1 是个文本协议,同时返回的内容也并不能区分对应于哪个发送的请求,所以顺序必须维持一致。比如你向服务器发送了两个请求 GET /query?q=A 和 GET /query?q=B,服务器返回了两个结果,浏览器是没有办法根据响应结果来判断响应对应于哪一个请求的。
http2 的多路复用解决了 http 层面的对头阻塞。HTTP2 不使用管道化的方式,而是引入了帧、消息和数据流等概念,每个请求/响应被称为消息,一个流代表一个完整的请求或者响应。每个消息都被拆分成若干个帧进行传输,每个帧都分配一个序号。不同流的帧可以乱序发送,同一个流的帧需要顺序发送。每个帧在传输是属于一个数据流,而一个连接上可以存在多个流,每个流都可以承载双向的消息。各个帧在流和连接上独立传输,到达之后在组装成消息,这样就避免了请求/响应阻塞。
但是即使是使用 http2,它底层也是使用 tcp,所以它依然存在 tcp 层面的对头阻塞
tcp 的对头阻塞
TCP 的阻塞问题是因为传输阶段可能会丢包,一旦丢包就会等待重新发包,其后面的数据包即使到达接收端,也不会被处理而是存放在缓冲区,等待这个丢失的包被发送端重新传到接收端,然后再将收到的数据包按顺序进行组装处理。
如何解决 tcp 的对头阻塞
TCP 中的队头阻塞的产生是由 TCP 自身的实现机制决定的,无法避免。想要在应用程序当中避免 TCP 队头阻塞带来的影响,只有舍弃 TCP 协议。
比如 google 推出的 quic 协议,在某种程度上可以说避免了 TCP 中的队头阻塞,因为它根本不使用 TCP 协议,而是在 UDP 协议的基础上实现了可靠传输。而 UDP 是面向数据报的协议,数据报之间不会有阻塞约束。
此外还有一个 SCTP(流控制传输协议),它是和 TCP、UDP 在同一层次的传输协议。SCTP 的多流特性也可以尽可能的避免队头阻塞的情况。
总结
从 TCP 队头阻塞和 HTTP 队头阻塞的原因我们可以看到,出现队头阻塞的原因有两个:
-
独立的消息数据都在一个链路上传输,也就是有一个“队列”。比如 TCP 只有一个流,多个 HTTP 请求共用一个 TCP 连接
-
队列上传输的数据有严格的顺序约束。比如 TCP 要求数据严格按照序号顺序,HTTP 管道化要求响应严格按照请求顺序返回
所以要避免队头阻塞,就需要从以上两个方面出发,比如 quic 协议不使用 TCP 协议而是使用 UDP 协议,SCTP 协议支持一个连接上存在多个数据流等等。
网友评论