亿及流量多级缓存 - 客户端缓存

时间:2022-07-22
本文章向大家介绍亿及流量多级缓存 - 客户端缓存,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

文档整理于 马士兵教育

服务并发化

其他缓存

客户端

浏览器缓存

首先,限定在get请求

由于浏览器缓存涉及到的应用比较多,所以针对不同的应用和版本效果也会有差异

浏览器:chrome

服务器:nginx

在系统中不常改变的资源上可以使用浏览器缓存,

其中在frame下

Cache-Control: max-age=2592000
ETag: "5d8c4a06-a0fc"  
Expires: Sat, 26 Oct 2019 15:16:24 GMT
ETag:

http1.1支持

在HTTP协议中If-Modified-Since和If-None-Match分别对应Last-Modified和ETag

Entity Tag 的缩写,中文译过来就是实体标签的意思.

HTTP中并没有指定如何生成ETag,哈希是比较理想的选择。

在计算Etag的时候,会产生CPU的耗费,所以也可以用时间戳,但这样直接使用Last-Modified即可。

ETag 用来校验用户请求的资源是否有变化,作用和lastmodified很像,区别是lastmodified精确到秒,ETag可以用hash算法来生成更精确的比对内容。

当用户首次请求资源的时候返回给用户数据和200状态码并生成ETag,再次请求的时候服务器比对ETag,没有发生变化的话返回304

java实现

package com.mashibing.httpcache.controller;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cache")
public class CacheController {
	
	@RequestMapping("/")
	public ResponseEntity<String> last(@RequestHeader(value="IF-Modified-Since",required = false) Date ifModifiedSince) {
		
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
		
		long now = System.currentTimeMillis() / 1000 *1000;
		
		HttpHeaders headers = new HttpHeaders();
		
		String body = "<a href =''>hi点我</a>";
	
		String ETag = getMd5(body);
		
		headers.add("Date", simpleDateFormat.format(new Date(now)));
		headers.add("ETag", ETag);
		
		return new ResponseEntity<>(body,headers,HttpStatus.OK);
	}
	
	/**
	 * 字符串转md5
	 * @param msg
	 * @return
	 */
	private String getMd5(String msg) {
		MessageDigest md5 = null;
		
		try {
			md5 = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		md5.update(msg.getBytes());
		byte[] digest = md5.digest();
		
		StringBuffer buf = null;
		buf = new StringBuffer(digest.length * 2);
		//遍历
		for (int i = 0; i < digest.length; i++) {
			if (((int) digest[i] & 0xff) < 0x10) { //(int) b[i] & 0xff 转换成无符号整型
				buf.append("0");
			}
			//Long.toHexString( 无符号长整数的十六进制字符串表示
			buf.append(Long.toHexString((int) digest[i] & 0xff)); 
		}
		return buf.toString();
	}

}
Cache-Control、 Last-Modified 、Expires

Last-Modified : 表示文档最后修改时间,浏览器在访问重复资源的时候会发送IF-Modified-Since 携带此时间去服务器验证,如果时间匹配则返回304,浏览器加载本地资源

Expires: 文档过期时间,在浏览器内可以通过这个时间来判断是否发送请求

Cache-Control :http1.1的规范,使用max-age表示文件可以在浏览器中缓存的时间以秒为单位

Cache-Control缓存头,分为响应头和请求头

标记

类型

功能

public

响应头

响应的数据可以被缓存,客户端和代理层都可以缓存

private

响应头

可私有缓存,客户端可以缓存,代理层不能缓存(CDN,proxy_pass)

no-cache

请求头

可以使用本地缓存,但是必须发送请求到服务器回源验证

no-store

请求和响应

应禁用缓存

max-age

请求和响应

文件可以在浏览器中缓存的时间以秒为单位

s-maxage

请求和响应

用户代理层缓存,CDN下发,当客户端数据过期时会重新校验

max-stale

请求和响应

缓存最大使用时间,如果缓存过期,但还在这个时间范围内则可以使用缓存数据

min-fresh

请求和响应

缓存最小使用时间,

must-revalidate

请求和响应

当缓存过期后,必须回源重新请求资源。比no-cache更严格。因为HTTP 规范是允许客户端在某些特殊情况下直接使用过期缓存的,比如校验请求发送失败的时候。那么带有must-revalidate的缓存必须校验,其他条件全部失效。

proxy-revalidate

请求和响应

和must-revalidate类似,只对CDN这种代理服务器有效,客户端遇到此头,需要回源验证

stale-while-revalidate

响应

表示在指定时间内可以先使用本地缓存,后台进行异步校验

stale-if-error

响应

在指定时间内,重新验证时返回状态码为5XX的时候,可以用本地缓存

only-if-cached

响应

那么只使用缓存内容,如果没有缓存 则504 getway timeout

在浏览器和服务器端验证文件是否过期的时候,浏览器在二次请求的时候会携带IF-Modified-Since属性

Cache-Control和ETag的区别 Cache-Control直接是通过不请求来实现,而ETag是会发请求的,只不过服务器根据请求的东西的内容有无变化来判断是否返回请求的资源

java实现Cache-Control Last-Modified:

Controller

package com.mashibing.httpcache.controller;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cache")
public class CacheController {
	
	private MyFile file = MyFile.getInstance();
	@RequestMapping("/")
	public ResponseEntity<String> last(@RequestHeader(value="IF-Modified-Since",required = false) Date ifModifiedSince) {
		
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
		
		long now = System.currentTimeMillis() / 1000 *1000;
		// 系统当前时间
		
		System.out.println(now);
		// 缓存时间
		long maxAge = 20;
		
		HttpHeaders headers = new HttpHeaders();
		
		if (null != ifModifiedSince && ifModifiedSince.getTime() == file.getLastModified() ) {
			
			System.out.println(304);
			
		}
		
		headers.add("Date", simpleDateFormat.format(new Date(now)));
		headers.add("Expires", simpleDateFormat.format(new Date(now + maxAge * 1000)));
		headers.add("Cache-Control", "max-age="+maxAge);
		headers.add("Last-Modified", simpleDateFormat.format(new Date(file.getLastModified())));
		
		String body = "<a href =''>hi点我</a>";
		return new ResponseEntity<>(body,headers,HttpStatus.OK);
	}

}

Bean

package com.mashibing.httpcache.controller;

public class MyFile {

	private String name;
	private long lastModified;
	private long expireTime;
	
	private static MyFile file ;
	
	private MyFile() {

		this.name = "file...";
		this.lastModified = System.currentTimeMillis() /1000 * 1000;
		this.expireTime = 10 *1000;
	}
	
	public long getExpireTime() {
		return expireTime;
	}
	public void setExpireTime(long expireTime) {
		this.expireTime = expireTime;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public long getLastModified() {
		return lastModified;
	}
	public void setLastModified(long lastModified) {
		this.lastModified = lastModified;
	}

	public static MyFile getInstance() {
		// TODO Auto-generated method stub
		if(file==null) {
			file = new MyFile();
		}
		return file;
	}
	
}
强制刷新

在强制刷新的时候浏览器就不在发送IF-Modified-Since了,而会带上

from disk cache & from memory cache

可以验证请求是否使用了浏览器缓存和是否发送请求给服务器端。

当点击链接、引入外部资源和浏览器的前进后退的时候。

from memory cache

字面理解是从内存中,其实也是字面的含义,这个资源是直接从内存中拿到的,不会请求服务器一般已经加载过该资源且缓存在了内存当中,当关闭该页面时,此资源就被内存释放掉了,再次重新打开相同页面时不会出现from memory cache的情况

from disk cache

是从磁盘当中取出的,也是在已经在之前的某个时间加载过该资源,不会请求服务器但是此资源不会随着该页面的关闭而释放掉,因为是存在硬盘当中的,下次打开仍会from disk cache

不做深入研究

js脚本,css,图片,音视频,字体

Age

是CDN添加的属性表示在CDN中缓存了多少秒

via

用来标识CDN缓存经历了哪些服务器,缓存是否命中,使用的协议

浏览器缓存原则

  • 首页可以看做是框架 应该禁用缓存,以保证加载的资源都是最新的
  • 还有一些场景下我们希望禁用浏览器缓存。比如轮训api上报数据数据
  • 浏览器缓存很难彻底禁用,大家的做法是加版本号,随机数等方法。
  • 只缓存200响应头的数据,像3XX这类跳转的页面不需要缓存。
  • 对于js,css这类可以缓存很久的数据,可以通过加版本号的方式更新内容
  • 不需要强一致性的数据,可以缓存几秒
  • 异步加载的接口数据,可以使用ETag来校验。
  • 在服务器添加Server头,有利于排查错误

应用缓存

分为手机APP和Client以及是否遵循http协议

在没有联网的状态下可以展示数据

流量消耗过多

  • 漂亮的加载过程
  • 提前下发 避免秒杀时同时下发数据造成流量短时间暴增
  • 兜底数据 在服务器崩溃和网络不可用的时候展示
  • 临时缓存 退出即清理
  • 固定缓存 展示框架这种,可能很长时间不会更新,可用随客户端下发
  • 父子连接 页面跳转时有一部分内容不需要重新加载,可用从父菜单带过来
  • 预加载 某些逻辑可用判定用户接下来的操作,那么可用异步加载那些资源
  • 异步加载 先展示框架,然后异步加载内容,避免主线程阻塞