利用LRU策略实现Axios请求缓存
业务场景
前一段时间刚做完一个项目,先说一下业务场景,有别于其他的前端项目,这次的项目是直接调用第三方服务的接口,而我们的服务端只做鉴权和透传,第三方为了灵活,把接口拆的很零散,所以这个项目就像扔给你一堆乐高颗粒让你组装成一个机器人。所以可以大概分析一下这个项目在请求接口时的一些特点,然后针对性的做一些优化:
- 请求接口多,可能你的一个n个条目的列表本来一个接口搞定现在需要n*10个接口才能拿到完整的数据,有些功能模块可能需要请求成千上万次接口;
- 基本都是get请求,只读不写;
- 接口调用重复率高,因为接口很细碎,所以可能有些常用的接口需要重复调用;
- 接口返回的数据实时性要求不高,第三方的数据不是实时更新的,可能一天或者一周才更新一次,但是第三方要求不能以任何的方式落库。
所以综上分析,前端缓存成了一个可行性较高的优化方案。
解决方案
前端的HTTP请求使用的是Axios,因此可以利用Axios的拦截器进行缓存的管理。梳理一下逻辑:
- 创建缓存对象;
- 请求发起之前判断该请求是否命中缓存:
- 是,直接返回缓存内容;
- 否,发起请求,请求成功后将请求结果存入缓存中。
如标题所说,这里的缓存策略我们用的是LRU(Least Recently Used)策略,因为缓存不能无限大,过大的缓存可能会导致浏览器页面性能下降,甚至内存泄漏。LRU会在缓存达到最大承载量后删除最近最少使用的缓存内容,因此不用担心缓存无限增大。那么如何实现LRU缓存策略呢?Github上有现成的轮子,但是为了更深入的学习嘛,我们自己来手动实现一个。
实现LRU
LRU主要有两个功能,存、取。梳理一下逻辑:
- 存入:
- 如果缓存已满,删除最近最少使用的缓存内容,把当前的缓存存进去,放到最常用的位置;
- 否则直接将缓存存入最常用的位置。
- 读取:
- 如果存在这个缓存,返回缓存内容,同时把该缓存放到最常用的位置;
- 如果没有,返回-1。
这里我们可以看到,缓存是有优先级的,我们用什么来标明优先级呢?如果用数组存储可以将不常用的放到数组的头部,将常用的放到尾部。但是鉴于数据的插入效率不高,这里我们使用Map对象来作为容器存储缓存。
代码如下:
class LRUCache {
constructor(capacity) {
if (typeof capacity !== 'number' || capacity < 0) {
throw new TypeError('capacity必须是一个非负数');
}
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) {
return -1;
}
let tmp = this.cache.get(key);
// 将当前的缓存移动到最常用的位置
this.cache.delete(key);
this.cache.set(key, tmp);
return tmp;
}
set(key, value) {
if (this.cache.has(key)) {
// 如果缓存存在更新缓存位置
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// 如果缓存容量已满,删除最近最少使用的缓存
this.cache.delete(this.cache.keys().next.val);
}
this.cache.set(key, value);
}
}
结合Axios实现请求缓存
理一下大概的逻辑:每次请求根据请求的方法、url、参数生成一串hash,缓存内容为hash->response,后续请求如果请求方法、url、参数一致,即认为命中缓存。
代码如下:
import axios from 'axios';
import md5 from 'md5';
import LRUCache from './LRU.js';
const cache = new LRUCache(100);
const _axios = axios.create();
// 将请求参数排序,防止相同参数生成的hash不同
function sortObject(obj = {}) {
let result = {};
Object.keys(obj)
.sort()
.forEach((key) => {
result[key] = obj[key];
});
}
// 根据request method,url,data/params生成cache的标识
function genHashByConfig(config) {
const target = {
method: config.method,
url: config.url,
params: config.method === 'get' ? sortObject(config.params) : null,
data: config.method === 'post' ? sortObject(config.data) : null,
};
return md5(JSON.stringify(target));
}
_axios.interceptors.response.use(
function(response) {
// 设置缓存
const hashKey = genHashByConfig(response.config);
cache.set(hashKey, response.data);
return response.data;
},
function(error) {
return Promise.reject(error);
}
);
// 将axios请求封装,如果命中缓存就不需要发起http请求,直接返回缓存内容
export default function request({
method,
url,
params = null,
data = null,
...res
}) {
const hashKey = genHashByConfig({ method, url, params, data });
const result = cache.get(hashKey);
if (~result) {
console.log('cache hit');
return Promise.resolve(result);
}
return _axios({ method, url, params, data, ...res });
}
请求的封装:
import request from './axios.js';
export function getApi(params) {
return request({
method: 'get',
url: '/list',
params,
});
}
export function postApi(data) {
return request({
method: 'post',
url: '/list',
data,
});
}
这里需要注意的一点是,我将请求方法,url,参数进行了hash操作,为了防止参数的顺序改变而导致hash结果不一致,我在hash操作之前,给参数做了排序处理,实际开发中,参数的类型也不一定就是object,可以根据自己的需求进行改造。
如上改造后,第一次请求后,相同的请求再次触发就不会发送http请求了,而是直接从缓存中获取,真是多快好省~
原文地址:https://www.cnblogs.com/rain-watcher/p/15087066.html
- 04 整合IDEA+Maven+SSM框架的高并发的商品秒杀项目之高并发优化
- 过滤器第一篇【介绍、入门、简单应用】
- 通过pl/sql来格式化sql(r4笔记第63天)
- 程序员如何写出杀手级的简历
- 过滤器第二篇【编码、敏感词、压缩、转义过滤器】
- JSP第二篇【内置对象的介绍、4种属性范围、应用场景】
- Struts2的配置和一个简单的例子
- 监听器第一篇【基本概念、Servlet各个监听器】
- 监听器第二篇【统计网站人数、自定义session扫描器、踢人小案例】
- 通俗易懂的分析如何用Python实现一只小爬虫,爬取拉勾网的职位信息
- JSP第一篇【JSP介绍、工作原理、生命周期、语法、指令、行为】
- 一条执行时间两天半的sql语句简化(r4笔记第62天)
- 05-01总结方法,数组(一维)
- 02 Java类的加载机制
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- NumPy进阶80题完整版|附Notebook版本下载
- 【LeetCode每日一题】21. Merge Two Sorted Lists
- 计算广告笔记06-程序化交易广告
- TF入门05-实验过程管理
- [LeetCode]709. To Lower Case
- ISO C forbids comparison between pointer and integer [-fpermissive]
- 【疑难杂症】解决-TensorFlow “FutureWarning: Conversion of the”
- [Deep-Learning-with-Python] 文本序列中的深度学习
- TN-SCUI2020挑战赛详细讲解
- QSignalMapper使用以及替换方法
- 对于组件的可重用性,大佬给出来6个级别的见解,一起过目一下!
- 多个线程为了同个资源打起架来了,该如何让他们安分?
- 实战|记录一次渗透测试项目
- SVN工具分析
- 客服MM被投诉说下单耗时很长,老板下令必须控制在1秒以内