查漏补缺-本地缓存与HTTP缓存

什么是缓存?为什么要缓存?

缓存是指将之前获取的资源或者文件暂存,以便下次访问时重复使用,由于不需要重新获取完整文件,可以节省带宽资源并提升访问速度

根据什么判断是否需要缓存

一、系统访问频率

1.当系统的访问频率较高时,使用缓存可以提高数据的读取速度。

例如,在一个电商网站中,商品列表是用户经常访问的内容之一。如果每次访问商品列表都要从数据库中读取数据,将会消耗大量的时间和系统资源。因此,可以将商品列表数据缓存在内存中,每次用户访问时直接从缓存中获取数据,避免频繁读取数据库,提升系统的响应速度。

2.当系统的访问频率较低时,可能不必使用缓存。

如果某个数据只会偶尔被访问,那么将其缓存起来可能会浪费资源。在这种情况下,可以根据具体需求来决定是否需要使用缓存。例如,某个网站的新闻页面中的评论数可能会变化,但用户访问评论数的频率较低,那么可以不必缓存评论数。

二、数据的更新频率

1.当数据的更新频率较高时,使用缓存需要考虑缓存的一致性和更新机制。

如果某个数据经常被更新,而缓存的数据没有及时更新,将会引发数据不一致的问题。在这种情况下,需要使用一些缓存更新策略例如定时更新缓存或通过触发更新事件来实时更新缓存数据。

2.当数据的更新频率较低时,使用缓存可以减轻数据库的负载,并提高系统的响应能力。

例如,某个系统中的配置文件数据一般较少改变可以将其缓存在内存中,避免频繁读取数据库,提高系统的性能。

三、数据的访问模式

1.如果某个数据具有明显的热点访问模式,即某些数据被频繁访问而其他数据很少被访问,可以考虑使用缓存。

例如,在一个社交网络中,用户的个人信息是频繁被访问的数据,可以将其缓存起来,减少数据库的访问次数。

2.如果数据的访问模式比较随机,或者每个数据的访问频率相差不大那么使用缓存可能无法带来明显的性能提升,甚至可能增加系统的复杂性。

在这种情况下,可以考虑其他的性能优化方案,如数据库索引优化、查询优化等。

客户端缓存可以分成HTTP缓存本地缓存HTTP缓存可以分为强缓存协商缓存,而常见的本地缓存包括CookieLocalStorageSessionStorage

HTTP 缓存

http缓存分文强制缓存和协商缓存,主要用来在客户端存储一些不经常变化的的静态文件,如图片、CSS、JS等。Http缓存流程如下:

img

强制缓存(强缓存)

浏览器在请求某一个资源时,会先获取资源的 header 信息,判断是否命中强缓存(请求头中的cache-control和expires信息),若命中,则直接从缓存中获取资源信息,包括header信息,本次请求就不会与服务器通信。

img

HTTP的缓存机制主要在响应头中进行设置,主要控制字段为 ExpriesCache-Control

Expires

Expires意为“到期”,是 HTTP/1.0 中定义的一个响应头字段,用于指定资源的到期时间,即资源在客户端缓存中的有效期。它的值是一个GMT格式的日期时间字符串。比如说将某一资源设置响应头为: Expires: new Date(“2024-4-20 23:59:59”);

因为Expires判断强缓存是否过期的机制是:获取本地时间戳,并对先前拿到的资源文件中的Expires字段的时间做比较。来判断是否需要对服务器发起请求。因此它就会受到客户端时间的影响,而这就有可能会导致出现错误。

也就是说,Expires过度依赖本地时间,如果本地与服务器时间不同步,就会出现资源无法被缓存或者资源永远被缓存的情况。因此现在我们都是用Cache-Control来代替Expres了。

Cache-Control

Cache-Control是HTTP/1.1中最重要的缓存控制字段之一,它可以指定缓存行为的各种指令。使用方法例:

1
2
3
4
5
res.writeHead(200, {
'Content-Type': mime.lookup(ext),
'Cache-Control': 'max-age=86400', // 一天
})

Cache-Control:max-age=N,N就是需要缓存的秒数。从第一次请求资源的时候开始,往后N秒内,资源若再次请求,则直接从缓存中读取,不会往服务器再发送一次请求。

Cache-control中因为max-age后面的值是一个滑动时间,从服务器第一次返回该资源时开始倒计时。所以也就不需要比对客户端和服务端的时间,解决了Expires所存在的巨大漏洞。

它常见的指令有:

  • public:表示响应可以被任何缓存(包括代理服务器)缓存。
  • private:表示响应只能被客户端缓存,而不允许被代理服务器缓存。
  • s-maxage=<seconds>:类似于max-age,但仅适用于代理服务器缓存。
  • no-cache:表示缓存内容需要重新验证,客户端每次都要向服务器发送请求进行确认。
  • no-store:表示不缓存任何内容,每次请求都要向服务器发送请求。

强缓存的缺点

  • 当用户第一次访问时,服务器会返回资源,并在响应头中设置缓存时间,当用户再次访问时,浏览器会根据缓存时间判断是否使用缓存,如果缓存时间未过期,则使用缓存,如果过期则重新请求资源,这样会导致用户第一次访问时,加载时间较长,用户体验不好。
  • 如果后端改了数据,但是名字没有改变,浏览器不会重新请求,导致数据不是最新的。
  • 后端资源修改了,前端无法实时获取最新资源。(通常都在文件名后面携带一串hash值,只要资源被修改,hash值就会更改,也就是文件名被修改了,因次浏览器一定会重新请求)
  • 通过浏览器url地址栏发送的get请求,无法被强缓存,因为浏览器会忽略缓存,直接请求服务器,但是通过页面上的链接、表单提交、ajax请求等方式发送的get请求,可以被强缓存。

协商缓存

协商缓存是 HTTP 缓存的一种机制,它通过在请求和响应中使用一些特定的头部信息来协商客户端和服务器之间的缓存行为。这种缓存机制允许客户端在重新获取资源时向服务器询问该资源的状态,并根据服务器返回的状态来决定是否使用缓存。

在协商缓存中,主要使用的头部字段包括:

  1. Last-Modified / If-Modified-Since:
    • 当服务器响应一个资源请求时,会在响应头中包含 Last-Modified 字段,表示资源的最后修改时间。
    • 当客户端再次请求相同资源时,会在请求头中包含 If-Modified-Since 字段,值为上一次获取资源时服务器返回的 Last-Modified 时间。
    • 服务器收到带有 If-Modified-Since 字段的请求后,会比较该字段的值与资源的最后修改时间,如果资源的最后修改时间晚于 If-Modified-Since 字段的值,则返回资源内容和状态码 200 OK;如果资源的最后修改时间早于或等于 If-Modified-Since 字段的值,则返回状态码 304 Not Modified,告知客户端可以使用缓存的资源。
  2. ETag / If-None-Match:
    • 当服务器响应一个资源请求时,可以在响应头中包含 ETag 字段,表示资源的标识符。
    • 当客户端再次请求相同资源时,可以在请求头中包含 If-None-Match 字段,值为上一次获取资源时服务器返回的 ETag
    • 服务器收到带有 If-None-Match 字段的请求后,会比较该字段的值与资源的当前 ETag,如果两者相等,则返回状态码 304 Not Modified,告知客户端可以使用缓存的资源;如果两者不相等,则返回新的资源内容和状态码 200 OK

就比如:

1
2
3
4
5
6
7
8
9
10
11
12
res.writeHead(200, {
'Content-Type': mime.lookup(ext),
'etag': `${content}`
// 'Last-Modified': stats.mtimeMs //时间戳
})
const content = fs.readFileSync(filePath) // 读取文件
const { ext } = path.parse(filePath);
const timeStamp = req.headers['if-modified-since'];
let status = 200;
if (timeStamp && Number(timeStamp) === stats.mtimeMs) {
status = 304;
}

通过以上操作,我们就可以实现一个简单的协商缓存了。需要注意的是,之所以会有ETag / If-None-Match,是因为:

  • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
  • 如果某些文件被修改了,但是内容并没有任何变化,而Last-Modified却改变了,导致文件没法使用缓存
  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

总结

HTTP缓存是通过在客户端和服务器之间存储已获取过的资源副本来提高网站性能和加载速度的一种技术。它分为强缓存和协商缓存两种类型。

强缓存通过设置特定的响应头字段(如Cache-ControlExpires),告知客户端在一定的有效期内直接从本地缓存中获取资源,而无需再次向服务器发送请求。强缓存可以减少网络流量和延迟,提高网站性能和加载速度,降低服务器负载,并支持离线浏览。常见的控制字段有ublic、private、s-maxage、no-cache和no-store

协商缓存则是通过在请求和响应中使用特定的头部信息(如Last-Modified / If-Modified-SinceETag / If-None-Match)来协商客户端和服务器之间的缓存行为。当客户端再次请求资源时,会根据服务器返回的状态码来决定是否使用缓存。如果资源未过期,则返回状态码200 OK;如果资源过期,则返回状态码304 Not Modified,告知客户端可以使用缓存的资源。


查漏补缺-本地缓存与HTTP缓存
https://username.github.io/2024/09/06/查漏补缺-Http缓存/
作者
ZhuoRan-Takuzen
发布于
2024年9月6日
许可协议