计算机网络——HTTP响应全过程及优化

一次http网络请求的历程

  • 发起请求

    • 客户端/浏览器创建线程发起请求

  • DNS解析

    • 查看host域名解析服务器

    • 从根、顶级、权威的各级域名服务器中查询

  • 创建链接

    • 三次握手

    • TSL/SSL

    • 慢启动

    • 重定向

  • 发送数据包

    • 延迟

    • 丢包

  • 接受数据包

    • I/O

    • 解析

  • 关闭链接

    • 四次挥手

存在的问题

  1. 发起请求:创建线程的过程本身就会消耗一定的时间,当在短时间内进行较多的请求导致频繁出现创建线程的情况时,创建线程所占的时间将会是不可忽略的;另一方面,当任务过多时,CPU频繁切换线程以及线程从内核态与用户态之间频繁转换也会造成大量的时间浪费,同时,当较多线程同时存在时,对空间占用也并非是可以忽略的。

  2. DNS:即使是较小的应用每个月也会遇到几次DNS劫持的情况,而出现这种情况将会极大的侵害其安全性;另一方面,由于国内没有根域名服务器,当进行访问时若直接跳转到境外进行请求域名解析服务,将会极大的延长时间;同时,运营商DNS转发查询请求或是出口NAT导致流量调度策略失效;除此之外,还要考虑IP选取策略、DNS缓存机制、灾容等问题,如有必要的话,可以对其进行优化。

  3. TCP:HTTP是一种基于TCP的应用层协议,而TCP在建立链接的时候需要进行三次握手,在断开链接的时候需要进行四次挥手,若只进行一次小数据的请求,建立链接的过程所用的时间开销将在整个请求开销中占用极高的比例;同时,对于较大规模的HTTP请求,由于TCP在发送数据包的过程中采用慢启动,初始时传输速度较慢,随着时间流逝而逐步增大窗口大小以加快传输速度;另一方面,对于HTTPS建立请求的过程中,需要进行SSL密钥协商,而协商过程所需的时间也是一个不小的开销。

  4. 对于服务端中为修改的数据,循环多次拉去并不会有更新,只会浪费一定的带宽去重复拉去,如果可以得知服务端中该数据并未修改则可以直接利用之前获取的信息即可,即免除拉去重复数据的时间开销。

  5. 数据体积:当请求体较大的时候,特别是大于链路中MTU时需要进行成帧和组帧操作较大的数据传输需要占用更高的带宽,同时也会花费更多的时间。

  6. I/O:客户端会进行等待以获取服务器的返回数据,客户端在收到数据后还要把数据从内核空间复制到用户空间里去,在此期间根据网络的阻塞模型阻塞I/O模型、非阻塞I/O模型、多路复用I/O模型、信号驱动I/O模型、异步I/O模型),会遇到不同程度的阻塞。

解决方案

线程池

对于一次网络请求而言,如果每一次网络请求都要创建一个新的线程去对它进行请求,当我们要进行100次请求的时候,就需要进行100次线程的创建与销毁,而这多次的创建和销毁过程将会产生不可忽略的时间开销,同时,一般情况而言,在一段时刻进行的网络请求的数量普遍不会太大,即,对应分配给线程的空间并不会太大。

此时可以发现,如果我们保持一定量的线程存在,一方面,当在长时间内进行多次分散的网络请求时,由于多个请求之间的分离的,同一时刻的请求数量较少,保持存在的线程完全可以覆盖这一批请求的时候,只需要花费维持这些线程存在的空间就可以免除对线程的创建和销毁过程,极大的节约了时间开销;另一方面,当在较短时间内进行较高频率的网络请求,即,请求数量超过保持存在的线程数量时,一来可以选择临时扩容一部分以容纳超出的请求,这种方式同最初的方式一样,即:发一次请求创建一个线程,两者的消耗并无太大区别,二来也可以选择不对线程池进行临时扩容,即只使用之前保持存在的线程进行多次请求,该方法虽然由于多次请求导致轮询等待的时间更长了,但由于CPU本身执行速率是有限的,在一段时间内可以发送的请求也是有限的,将创建线程的时间用来进行线程复用发送网络请求也可以平衡掉一部分时间损耗,同时也节省了一定量的空间。同时,也可以将前两种方法结合起来,即对线程池做临时的扩容,但并不扩张到实际需要的线程数目,仅进行小范围的增加,以在不使用太多空间的情况下提高运行速度。

由于一个客户端应用上在同一时刻所提交的网络请求数量普遍不会太大,所以遇到的情况大多数为第一种请求,及长时间进行多次分散的网络请求,通过引入池化技术通过去除线程的创建和销毁的过程,有效的提高的请求速度,即以空间换时间。

引入协程

对于一个线程而言,它其实可以分为两部分,一部分是“内核态”线程,另一部分是“用户态线程”,而一个“用户态线程”必须要绑定一个“内核态线程”,但CPU本身是不知道“用户态线程”的存在的,它只知道有个“内核态线程”在运行,因此,再进行一次细分,将“内核态线程”称为“线程”,“用户态线程”称为“协程”

对于一个协程绑定一个线程的情况,即1:1关系模型,虽然它十分容易实现,但协程的创建、切换和销毁都要由CPU来执行,会消耗大量时间;既然一个协程可以绑定一个线程,而CPU本身是不关注协程的,那其实使用多个协程去绑定一个线程形成N:1关系模型也是可行的,N个协程绑定在1个线程上,优点就是协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速,避免了线程在用户态和内核态的频繁切换导致的时间浪费,但对于这种情况,协程是无法使用硬件的多核加速能力,同时当一个协程阻塞时,其他协程业会无法执行从而导致阻塞,即丧失了并发能力;为解决上述问题,引入了M:N关系模型,即多个协程对应多个线程,这样既可以避免频繁在内核态和用户态之间的切换,也可以利用硬件的多核加速能力,同时对于协程出现阻塞的情况,可以通过调度将后续协程分配给其他线程以顺利执行,解决了并发失能的问题。

HTTPDNS

在传统的DNS解析过程中,会先连接到运营商本地的DNS服务器,由运营商服务器帮我们去整颗DNS树上进行解析,然后将解析结果返回给客户端,其中,传统DNS解析的请求普遍采用UDP报文,并不保证其安全性和可靠性,当域名劫持发生时,会导致用户体验异常恶劣甚至可能造成隐私数据泄露等问题。

HTTPNDS不同于传统的DNS解析,而是自己搭建基于HTTP协议的DNS服务器集群,分布在多个地点和多个运营商。当客户端需要 DNS 解析的时候,直接通过HTTP协议进行请求这个服务器集群,得到就近的地址。相当于每个HTTPDNS服务器自己实现自己的域名解析,使用独立的域名缓存,一方面,由于使用HTTP请求可以保证其可靠性和安全性,另一方面,由于使用独立的域名缓存机制可以普遍的提高域名解析速度,同时可以根据请求发出的地理位置自动寻找最优的查询路径。

DNS预处理

一次典型的DNS解析需要20ms~120ms的时间,而对于一次HTTP请求普遍需要200ms时间,DNS解析所花费的时间在整个请求过程中所占的比例也是不可忽略的,当域名请求过多的时候,会让连续的一串请求的速度变得过慢,对于此,可以考虑在相对空闲的时候,对域名进行预解析,即在网络请求数量较少的时候,客户端对当前界面中可能执行的DNS解析进行提前解析处理,使得后续在进行HTTP网络请求时就不需要再次进行DNS解析从而极大的提高请求速度。

TCP多路复用

对于一次HTTP请求,需要利用TCP的三次握手建立链接,当请求结束时会进行四次挥手结束链接,三次握手需要消耗大量的时间,对于较长的请求来说,三次握手所占的比例相对较低,影响并不会太大,但对于一些更为普遍的较短的请求,更为普遍的是较短的请求,对于这一部分请求来说,三次握手需要花费的时间占比就显得尤为突出,为此,如果可以减少甚至抵消三次握手的时间将会极大的提高效率。

由于TCP三次握手需要经过中间链路进行传递,所以对于缩短建立链接的时间来说并不是特别显著,于是,需要考虑能否通过保持TCP的长期链接以避免重复建立连接导致的时间开销,即TCP多路复用

TCP多路复用需要引入池化技术,建立一个连接池,对于一次HTTP请求,可以先判断是否存在和它相同的IP和端口的TCP连接,如果存在则直接进行复用即可,如果不存在再新建一个连接并放入池中。对于连接池,需要控制它的连接数量,毕竟太多的连接存在也会占用不少的资源,所以需要对一些连接进行销毁。对于销毁策略在连接数量并未超出连接上限时,可以给每一个连接设定一个衰老时间,比如10min或者5min甚至是递进式:5min->7min->9min->10min,当到达衰老时间时则将其销毁,依次保证服务端不至于太过拥挤,同时,当其中任何一个TCP连接复用时,重置其衰老时间;在连接数量超过连接上限时,此时需要销毁一个不常用的连接使得连接数量不超过连接上限,对于这类情况,销毁策略可以参考LRU(最近最少使用算法)进行连接淘汰即可。

缓存

对于一个HTTP请求中的GET请求而言,它做的仅仅只是拉去服务端的数据进行展示,并不会对服务端数据进行修改,所以,如果可以判断将要拉去的数据之前进行过一次获取同时也并未修改时,则可以直接复用之前获取的数据以避免重新进行数据拉去从而减少时间开销。

在HTTP响应报文中,有这样两个字段“Expires”和“Last-Modified”,其中,“Expires”表示:本次请求获取的内容在xx时间前后端并不会做修改,可以进行缓存直到到达xx时间;“Last-Modified”则表示:该文档修改的最后时间,同时,“Last-Modified”的使用需要客户通过在请求头中设置字段“If-Modified-Since”提供一个日期,该请求将被视为一个条件GET,当且仅当改动时间迟于指定时间的文档才会返回,而当改动时间不迟于(一般是等于)指定时间时,只需要返回一个304状态(Not Modified),“Last-Modified”也可用setDateHeader方法来设置。

对于以上两种情况,都可以对http的request内容进行缓存,通过将url设为key,将response设为value进行绑定,这样,在进行GET请求前,可先判断是否存在缓存未过期的情况(对应使用字段“Expires”),若判断超过该时间后在进行条件GET(通过使用“If-Modified-Since”)

HTTP压缩

对于一次HTTP请求来说,我们需要发送一些数据构成request去指定的获取服务端的response数据,而如果可以缩小request和response的数据体积,就可以在更短的时间内发送和获取更多的数据,从而减小HTTP请求的时间开销,对此,可以采用HTTP压缩的方式。

HTTP压缩:HTTP编码方式的一种,是Web服务端和浏览器(客户端)之间压缩传输的”文本内容“的方法。 HTTP采用通用的压缩算法,比如gzip来压缩html,javascript, CSS文件。 能大大减少网络传输的数据量,提高了用户显示网页的速度。当然,同时会增加一小部分服务器的开销

HTTP压缩可以使用“Content-Encoding”字段进行指定方式编码压缩。只有在解码之后才可以得到Content-Type头指定的内容类型。常用的编码方式主要有以下四种:

  • gzip:表明实体采用GNU zip编码

  • compress:表明实体采用Unix的文件压缩程序

  • deflate:表明实体是用zlib的格式压缩的

  • identity:表明没有对实体进行编码。当没有Content-Encoding header时, 就默认为这种情况

gzip, compress, 以及deflate编码都是无损压缩算法,用于减少传输报文的大小,不会导致信息损失。 其中gzip通常效率最高, 使用最为广泛。

一般而言,http压缩对纯文本可以压缩至原内容的40%, 从而节省了60%的数据传输。但同时,使用gzip压缩对于JPEG这一类文件的压缩并不够好。

IO优化

网络I/O优化通常有如下一些基本处理原则。

减少网络交互的次数

要减少网络交互的次数通常需要在网络交互的两端设置缓存,可以有效地减少对数据库的访问。除了设置缓存可以合并访问请求,如在查询数据库时,我们要查10个ID,可以每次查一个ID,也可以一次查10个ID。再比如,在访问一个页面时通常会有多个JS或CSS的文件,我们可以将多个JS文件合并在一个HTTP链接中,每个文件用逗号隔开,然后发送到后端Web服务器,根据这个URL链接再拆分为各个文件,最后打包再一并返回给前端浏览器。

网络传输数据量的大小

减少网络数据量的办法通常是将数据压缩后再传输,如在HTTP请求中,通常Web服务器将请求的Web页面gzip压缩后再传输给浏览器。还有就是通过设计简单地协议,尽量通过读取协议头来获取有用的价值信息,如在设计代理程序,4层代理和7层代理都是在尽量避免要读取整个通信数据来取得需要的信息。

量减少编码。

通常在网络I/O中数据传输都是以字节形式进行的,即序列化。但是我们所要发送的要传输的数据都是以字符形式的表示的,而从字符编码成字节需要一定的时间,同时这个时间开销也是比较大的,所以在要经过网络I/O传输时,尽量直接以字节形式发送,也就是尽量提高将字符转化为字节,或者减少从字符到字节的转化过程。

切换IO模型

现有的五种IO模型分别为:

  • 阻塞I/O模型:当进程在等待数据时,若该数据一直没有产生,则该进程将一直等待,直到等待的数据产生为止,这个过程中进程的状态是阻塞的。

  • 非阻塞I/O模型:当进程等待内核的数据,而当该数据未到达的时候,进程会不断询问内核,直到内核准备好数据。

  • 多路复用I/O模型:当有多个需要等待的消息同时进行时,使用一个线程同时监听多个消息,当其中任意一个获得反馈时则进行用户设定的调用即可。

  • 信号驱动I/O模型:用户态进程不再需要等待或轮询内核态的数据准备好,而是由内核态通知用户态数据已经就绪,随后用户态陷入内核态获取数据,在此期间,用户态可以执行其他事情。

  • 异步I/O模型:用户态进程不再需要等待或轮询内核态的数据准备好,而是由内核态通知用户态数据已经就绪,由内核态将数据复制到用户空间,用户直接处理即可,在此期间,用户态可以执行其他事情。

相比较而言,阻塞I/O模型效率过低,非阻塞I/O模型需要浪费大量的性能,信号驱动I/O模型还要花费时间陷入内核态进行处理,如果要进行优化,可以选择使用异步I/O模型,同时利用多路复用进一步提高效率。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值