基于LRU算法实现接口缓存

250 阅读3分钟

1 解决什么问题

项目中存在大量重复接口,请求地址,方法,参数一模一样,该怎么样优化呢?同一个接口被调了七八次!

梳理一下整体优化逻辑:

  1. 创建缓存对象;

  2. 请求发起之前判断该请求是否命中缓存:

    1. 是,直接返回缓存内容;
    2. 否,发起请求,请求成功后将请求结果存入缓存中。

image.png

2 LRU算法介绍

什么是LRU算法

  • Least Recently Used 淘汰算法以时间作为参考,淘汰最长时间未被使用的数据

  • 如果数据最近被访问过,那么将来被访问的几率也更高;会淘汰最长时间没有被使用的元素(都没人要你了,不淘汰你淘汰谁)

    • 基本原理是:在缓存满时,将最近最久未使用的数据淘汰出缓存,以便给新的数据留出空间。

    • 实现方式可以用:数组、链表等方式

  • 新插入的数据放在头部,最近访问过的也移到头部,空间满时将尾部元素删除

image.png

3 实现流程

先创建LRUCache类,构造函数中capacity表示该缓存数组最多可以缓存多少元素, 包括两个核心方法,get获取缓存列表中的key对应的value,如果没有返回-1,set方法用于向缓存列表中添加元素。

如果缓存容量已满,删除最近最少使用的缓存

export default 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)
  }
}

4结合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,
    });
}

5 项目效果

原项目加载首页请求了59个接口,优化后请求了30个接口

,将近30个重复请求的接口 image.png

image.png

image.png