洋仔的博客 洋仔的博客
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

洋仔

奋斗的小青年
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • iOS基础知识

    • iOS底层相关

    • Runloop系列

    • Runtime系列

    • 内存管理系列

    • Block系列

    • 线程系列

    • KVC跟KVO系列以及通知中心

    • UI系列

    • 离屏渲染系列

    • 组件化系列跟架构

    • OC跟webview交互系列

    • 持久化系列

    • APP编译系列

    • APP性能优化系列

    • cocoapods系列

    • swift系列

    • Git系列

    • 网络相关

      • HTTP七层模型
      • HTTPS相关知识
      • HTTP协议报文
      • HTTP中间人攻击
      • TCP相关知识
      • HTTP缓存机制
        • 如何理解HTTP协议的“无连接,无状态”特点?
          • 无连接
          • 无状态
        • HTTP缓存流程
        • 强制缓存
          • 基于日期过期的缓存机制
          • 基于日期过期的缓存机制存在的问题
          • 基于时效性的缓存机制
        • 协商缓存
          • 基于最后修改时间的缓存协商机制
          • 基于最后修改时间的协商方式存在的问题
        • 基于内容版本变化的协商机制
          • 各种缓存机制混合的处理优先级
          • 协商缓存的流程
          • memory cache 和 disk cache 的区别
        • 什么是 cookie?
        • 什么是 session?
        • session 与 cookie 的区别
        • localStorage 是什么?
      • HTTP发展史
      • HTTP粘包问题
    • 三方库系列

    • 系统原理

    • 总结系列

    • 算法系列

    • 数据结构系列

  • 前端

  • 技术
  • iOS基础知识
  • 网络相关
洋仔
2023-09-29
目录

HTTP缓存机制

# 缓存篇-HTTP缓存

# 如何理解HTTP协议的“无连接,无状态”特点?

# 无连接

无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

早期这么做的原因是 HTTP 协议产生于互联网,因此服务器需要处理同时面向全世界数十万、上百万客户端的网页访问,但每个客户端(即浏览器)与服务器之间交换数据的间歇性较大(即传输具有突发性、瞬时性),并且网页浏览的联想性、发散性导致两次传送的数据关联性很低,大部分通道实际上会很空闲、无端占用资源。因此 HTTP 的设计者有意利用这种特点将协议设计为请求时建连接、请求完释放连接,以尽快将资源释放出来服务其他客户端。

随着时间的推移,网页变得越来越复杂,里面可能嵌入了很多图片,这时候每次访问图片都需要建立一次 TCP 连接就显得很低效。后来,Keep-Alive 被提出用来解决这效率低的问题。

Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。市场上的大部分 Web 服务器,包括 iPlanet、IIS 和 Apache,都支持 HTTP Keep-Alive。对于提供静态内容的网站来说,这个功能通常很有用。但是,对于负担较重的网站来说,这里存在另外一个问题:虽然为客户保留打开的连接有一定的好处,但它同样影响了性能,因为在处理暂停期间,本来可以释放的资源仍旧被占用。当Web服务器和应用服务器在同一台机器上运行时,Keep-Alive 功能对资源利用的影响尤其突出。

这样一来,客户端和服务器之间的 HTTP 连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。

# 无状态

无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求,会给我们发送数据过来,但是,发送完,不会记录任何信息。

HTTP 是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive 没能改变这个结果。

缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

HTTP 协议这种特性有优点也有缺点,优点在于解放了服务器,每一次请求“点到为止”不会造成不必要连接占用,缺点在于每次请求会传输大量重复的内容信息。

客户端与服务器进行动态交互的 Web 应用程序出现之后,HTTP 无状态的特性严重阻碍了这些应用程序的实现,毕竟交互是需要承前启后的,简单的购物车程序也要知道用户到底在之前选择了什么商品。于是,两种用于保持 HTTP 连接状态的技术就应运而生了,一个是 Cookie,而另一个则是 Session。

Cookie可以保持登录信息到用户下次与服务器的会话,换句话说,下次访问同一网站时,用户会发现不必输入用户名和密码就已经登录了(当然,不排除用户手工删除Cookie)。而还有一些Cookie在用户退出会话的时候就被删除了,这样可以有效保护个人隐私。

Cookies 最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是 Cookies 的功用。另一个重要应用场合是“购物车”之类处理。用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入 Cookies,以便在最后付款时提取信息。

与 Cookie 相对的一个解决方案是 Session,它是通过服务器来保持状态的。

当客户端访问服务器时,服务器根据需求设置 Session,将会话信息保存在服务器上,同时将标示 Session 的 SessionId 传递给客户端浏览器,浏览器将这个 SessionId 保存在内存中,我们称之为无过期时间的 Cookie。浏览器关闭后,这个 Cookie 就会被清掉,它不会存在于用户的 Cookie 临时文件。

以后浏览器每次请求都会额外加上这个参数值,服务器会根据这个 SessionId,就能取得客户端的数据信息。

如果客户端浏览器意外关闭,服务器保存的 Session 数据不是立即释放,此时数据还会存在,只要我们知道那个 SessionId,就可以继续通过请求获得此 Session 的信息,因为此时后台的 Session 还存在,当然我们可以设置一个 Session 超时时间,一旦超过规定时间没有客户端请求时,服务器就会清除对应 SessionId 的 Session 信息。

# HTTP缓存流程

以浏览器作为客户端为例,HTTP大致使用缓存的流程如下

  1. 浏览器收到资源请求。

  2. 根据请求的资源路径,先从本地看是否有可以用的缓存。

  3. 如果有缓存则直接返回缓存的结果,如果没有可用的缓存则发送请求到服务器以获得资源。

  4. 服务端收到请求后,将对应的资源响应给客户端。

  5. 浏览器收到服务器额响应数据后,把数据保存一份到缓存,然后对数据进行渲染。

缓存使用起来其实都很简单,但是麻烦的是如何验证缓存的时效性,缓存可以在多长时间内使用,如果服务端的数据发生变化时,客户端又如何感知到从而向服务器获取最新的数据,所以还需要一种缓存有效性验证机制来解决这些问题。

缓存有效性验证机制是为了避免服务端修改了资源后,客户端无法获得最新的资源问题,HTTP历史里有两种类型的缓存验证机制,一种是通过日期、和有效时间的强制缓存, 强制缓存会指定一个有效的时间,在这个时间之内客户端会一直使用本地的缓存资源,而不会向服务器发起资源请求,只有当时间过期之后客户端才会重新向服务端请求最新的资源。

另一种是通过对比文件修改时间、对比文件版本号的协商缓存,通过修改时间或版本号的对比来识别资源的新鲜度,这种方式客户端每次都会向服务器发起一个请求,服务端通过验证后告诉客户端它的缓存可否继续使用。

# 强制缓存

强制缓存的思路很简单,就是服务端在返回资源的时候就告诉客户端,这个数据你可以缓存多久,在时间范围内客户端可以完全使用缓存而不用和服务端产生任何交互,但是在过了这个时间之后,客户端就必须重新发请求到服务端获取数据。

# 基于日期过期的缓存机制

服务端通过指定一个文件过期的日期,来告诉客户端在指定的时间前你可以使用你本地的缓存,在客户端日期小于文件过期日期之前,客户端无法再向服务端发送新的请求,直接读取缓存即可。

  1. 服务端在响应数据数据时,会在HTTP 头部添加一个Expires 的字段,来注明该数据在给定日期时间内客户端可以缓存。

  2. 客户端收到服务器响应,解析到服务端带有Expires日期的响应头,当前日期在指定过期日期内则认为该数据可以进行缓存,此时会将数据保存到本地的缓存中。

  3. 当客户端尝试再次访问该资源时,首先会查找自己缓存是否存在,存在的话则验证当前日期是否小于过期日期,如果小于过期日期则说明缓存有效,则直接使用缓存,否则就向服务器发送请求获取最新的数据

Screenshot-2023-09-02-at-15

# 基于日期过期的缓存机制存在的问题

客户端的日期不可控:首先是时区问题,客户端和服务端的日期很可能不一样。另外客户端可以修改本地日期,一旦日期错误,客户端时间比服务器早一些或晚一些,就可能造成缓存该失效的时候不失效,该使用缓存的不能使用缓存。

# 基于时效性的缓存机制

基于时效性的验证机制,可以解决了基于日期验证机制中的日期不可控问题,通过一个相对的有效时间(多少秒有效),客户端可以根据有效时间计算出一个属于自己的过期日期。

1、在时效性验证的验证机制里,服务端会返回一个 cache-contro: max-age=60 的头部, max-age=60代表缓存有效的时长为60秒。

2、客户端收到带有cache-contro: max-age 的资源,就会对数据进行缓存,过期时间会根据自己的时间和max-age得到一个实际的过期时间。

3、再次访问该资源时,验证资源的是否在有效期内,有效则直接使用缓存,否则就向服务器发送请求获取最新的数据。

# 协商缓存

强制缓存优点很明显,一旦缓存在时间范围内有效,那么客户端就无需与服务端发生任何交互,直接使用本地缓存即可;但同时缺点也很明显,因为强制缓存的失效时间是固定的,在缓存过期之前,如果服务端对于文件的修改了,这段时间内客户端是感知不到的,所以这个过程中就必定存在一定时间内的数据不一致。

为了保证客户端缓存与服务端资源的一致性,所以就需要有一种内容核对机制,客户端可以通过某种方式和服务端核对自己缓存数据的一致性,在与服务端不一致的情况下就重新获得最新的资源,与服务端一致的情况下则直接使用自己的本地缓存即可,通过这种内容核对机制来保证缓存数据新一致就称为协商缓存。

# 基于最后修改时间的缓存协商机制

首先服务端在修改文件的时候都会记录一个最后修改时间,一旦客户端与服务端文件的文件最后修改时间不一致,那么就说明客户端的缓存是已经过期了。

  1. 服务器在返回资源时会在Responese Headers 里带有 一个Last-Modified: Sun, 11 Jul 2021 16:49:14 GMT 表示响应文件的最后修改时间。

  2. 发送请求前首先查看本地缓存是否有当前资源,如果有该资源但是已过期的话,客户端会发起一个头部带有 If-Modified-Since: cached last-modified date 的再验证请求 ,此时客户端请求资源的时候会将资源的最后修改时间 (cached last-modified date) 附带到请求中。

  3. 当服务器收到客户端请求后,首先会验证客户端文件最后修改时间和自己的文件最后修改时间是否一致,如果一致的话说明客户端的缓存是最新的,那么此时服务端不会响应客户端对应的资源,而是响应一个304的状态码,告诉客户端它的缓存是最新的可以使用。如果客户端资源不是最新的,那此时服务端才会把最新的资源数据返回给客户端,响应一个200状态码的。

  4. 当客户端收到来自服务端的一个304响应状态码,则会直接读取本地的缓存资源使用,如果是200的状态码,则把服务端返回的资源替换掉自己本地的缓存。

v2-03065b0aeb094d84ceea9fdfd49ea197_r

# 基于最后修改时间的协商方式存在的问题

1、如果最后修改时间变了,是否就意味着文件内容产生了变化呢,这个是不一定的,就像你打开文件然后做了个保存,其实内容啥也没变,但是时间变了。

2、另外一个是基于最后修改时间的方式时间精确度只能精确到秒级,如果一个文件在1秒内被修改了多次,那么很显然通过最后修改时间来验证文件是否为最新就会不准确了。

# 基于内容版本变化的协商机制

判断一个文件是否真的有变化,最正确的方式应该是看文件内容有没有变化即可,当内容变化时修改这就对文件进行一个版本标记,每修改一次就把文件的版本进行修改,那么每次进行文件对比的时候只需要对这个版本号就行了,这样就可以保证只要文件内容修改了,就可以感知得到。

  1. 首先服务端每次返回数据的时候都会在响应头中带有一个 ETag: W/"86657-1626022154000" 的版本标签。

  2. 客户端在本地缓存已失效的情况下,会发送一个请求头带有If-None-Match: W/"86657-1626022154000" 标签的再验证请求。

  3. 服务端收到了客户端带有If-None-Match: 的请求时,会对文件版本号进行对比,如果一致的话说明客户端的缓存是最新的,那么此时服务端不会响应客户端对应的资源,而是响应一个304的状态码,告诉客户端它的缓存是最新的可以使用。如果客户端资源不是最新的,那此时服务端才会把最新的资源数据返回给客户端,响应一个200状态码的。

  4. 当客户端收到来自服务端的一个304响应状态码,则会直接读取本地的缓存资源使用,如果是200的状态码,则把服务端返回的资源替换掉自己本地的缓存。

Screenshot-2023-09-02-at-15

# 各种缓存机制混合的处理优先级

这些缓存机制的完善并不是一蹴而就的,而是经过了一段时期和不同的HTTP版本,所以多种缓存机制很可能会同时存在,那么在多种缓存机制并存的时候我们必须按一定的优先级进行处理。

  1. 强制缓存与协商缓存同时存在时,必须先验证强制缓存,只有强制缓存失效时,才会使用协商缓存的处理机制。

  2. 强制缓存中 Expire 和 cache contro : max-age 同时存在时, cache contro : max-age 规则会覆盖 Expire 规则,此时只要验证max-age即可。

  3. 协商缓存中 Last-Modified 和 ETag 同时存在时,客户端会在请求的时候也带上If-Modified-Since 和If-None-Match 标签,此时服务器收到客户端请求时会对最后修改时间和版本号都会验证,只有当两个条件都符合时才会向客户端响应304的响应码

# 协商缓存的流程

  1. 什么是”Last-Modified”?

     在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样:
    
     Last-Modified: Fri, 12 May 2006 18:53:33 GMT
    
     客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:
    
     If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
    
     如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而 保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
    
  2. 什么是”Etag”?

     HTTP 协议规格说明定义ETag为“被请求变量的实体值” (参见 —— 章节 14.19)。 另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单 独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:
    
     ETag: "50b1c1d4f775c61:df3"
    
     客户端的查询更新格式是这样的:
    
     If-None-Match: W/"50b1c1d4f775c61:df3"
    
     如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。本人测试Etag主要在断点下载时比较有用。      
    

    Last-Modified和Etags如何帮助提高性能?

     聪明的开发者会把Last-Modified 和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。
    

因为服务器首先产生 Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客 户端)缓存。 过程如下:

            1. 客户端请求一个页面(A)。

            2. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。

            3. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。

            4. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。

            5. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。

(注意可以学习这篇文章)[https://www.51cto.com/article/746168.html]

# memory cache 和 disk cache 的区别

Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢?

这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。

当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存。

内存缓存中有一块重要的缓存资源是 preloader 相关指令(例如)下载的资源。总所周知 preloader 的相关指令已经是页面优化的常见手段之一,它可以一边解析 js/css 文件,一边网络请求下一个资源。

需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的 HTTP 缓存头 Cache-Control 是什么值,同时资源的匹配也并非仅仅是对 URL 做匹配,还可能会对 Content-Type,CORS 等其他特征做校验。

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。

浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?

关于这点,网上说法不一,不过以下观点比较靠得住:

对于大文件来说,大概率是不存储在内存中的,反之优先;

当前系统内存使用率高的话,文件优先存储进硬盘

# 什么是 cookie?

HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

cookie,指网站为了辨别用户身份而储存在用户本地终端上的数据。cookie 本质上是 HTTP 的一个内容(请求头)。 在前端工作中,可以这么理解 cookie:

cookie 是浏览器访问服务器后,服务器传给客户端的一段数据。 浏览器将 cookie 保存下来,一般情况下不会删除。 浏览器每次访问返回 cookie 的服务器时,都会在请求头(请求的第二部分)中带入这段 cookie

# 什么是 session?

个人理解: session 是一种在服务器端保存数据的机制。服务器通过读取浏览器发送的 cookie 和 服务器端的 session 来交换数据。 不同于 cookie,session保存在服务器端,不同的语言保存方式不一样:

java,保存于服务器内存中,重启服务器,session 消失 php,保存于服务器文件中,重启服务器,session 依然存在 nodejs,保存于服务器内存中,重启服务器,sessino 消失

# session 与 cookie 的区别

session 在服务器端,cookie 在客户端。 session 用户无法查看和修改,cookie 用户可以查看修改。 session 和 cookie 的存储容量不同。 session 的实现依赖于 sessionID,而 sessionID 又存储在 cookie 上,所以,可以这么说:session 是基于 cookie 实现的一种数据存储方式。

# localStorage 是什么?

localStorage 是一个保存于客户端的哈希表,可以用来本地保存一些数据。

变量持久化存储 js 中的变量都是存在内存中的,一旦刷新页面,内存释放之后,所有变量的值全部会被重新初始化。 而 localStorage 保存在本地,不会因为刷新而释放,所以,可以使用 localStorage 来实现变量的持久化存储

localStorage 与 HTTP 没有任何关系。 HTTP 不会带上 localStorage 的值,因为两者没有一毛钱关系。 只有相同域名的页面才能互相读取 localStorage,同源策略与 cookie 一致 不同的浏览器,对每个域名 localStorage 的最大存储量的规定不一样,超出存储量会被拒绝。Chrome 10MB 左右 常用场景:记录一些不敏感的信息(不涉及安全的信息) localStorage 理论上永久有效,除非用户清理缓存。

  1. sessionStorage(会话存储)

sessionStorage 的所有性质基本上与 localStorage 一致,唯一的不同区别在于: sessionStorage 的有效期是页面会话持续,如果页面会话(session)结束(关闭页面),sessionStorage 就会消失。而 localStorage 则会一直存在。

编辑 (opens new window)
上次更新: 2024/10/23, 23:26:17
TCP相关知识
HTTP发展史

← TCP相关知识 HTTP发展史→

最近更新
01
数组
10-25
02
数组双指针系列之对撞指针
10-25
03
数组双指针系列之快慢指针
10-25
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式