当前位置: 首页 > news >正文

为了实现接口缓存,专门写了个缓存库 f-cache-memory

问题起因

起因是某次发版之后,服务器接口压力过大,当场宕机,排查之后发现有个接口在首页被调十来次(六七年的老项目了,都是泪呀),后端反馈这个接口的sql很复杂,很耗性能,临时把这个接口放到登录后只执行一次,数据缓存在 localStorage 内,后续这个接口都直接从 localStorage 中取。

虽然临时解决了宕机问题,但还是会有多个组件内同时发起多个相同请求的问题。现在也有很多请求库带有缓存功能,但是这是老项目,请求只封装了 axios ,换个请求库风险又很高,最终决定自己搞搞。

思路历程

当时最初的想法就是在 axios 拦截器内做判断,首先在 interceptors.request 中判断缓存中是否有对应的请求,在 interceptors.response 中赋予缓存设置。

但很快问题就来,在 interceptors.request 判断有缓存之后,再取消这个缓存吗?搞了搞又发现个问题,当某个请求已发起,但是还没返回,这个时候这个请求又发起了,这不没解决问题。

然后又想着在 interceptors.request 中先让请求占位,这样多个组件同时发起某个请求,只要第一个占位了,后续的都取消。问题又来了,后续请求取消之后,后续逻辑又不触发了。后面突然想到我可以在请求之前做检测,当对应接口已经在缓存中时,就直接返回当前缓存中的值,缓存不存在再发起,这个时候肯定会想那第一个已经发起的还未返回的,后续相同接口从缓存中拿到的值如何出发后续逻辑呢?

请求本身返回的是 promise,那我先把 promise 存入缓存,相同接口再请求时直接返回缓存中的 promise ,这样后续逻辑可以正常触发。

最终方案

最终实现如下(仅 get 请求做接口缓存):

export function get<T = any>(url: string, config: AxiosRequestConfig = {}): Promise<T> {const curHttpCacheKey: string = configToKey({url,...config})if (!httpCache.hasCache(curHttpCacheKey)) {const httpRequest = instance.get(url, config)httpCache.setCache(curHttpCacheKey, httpRequest)return httpRequest as Promise<T>} else {return Promise.resolve(httpCache.getCache(curHttpCacheKey))}
}

get 请求再做个封装,缓存中以请求链接和 query拼接的字符串作为 keyhttpCache 后续会讲, configToKey 实现如下:

export function configToKey(config: AxiosRequestConfig): string {let key = config.url as stringif (config.params) {key += JSON.stringify(config.params)}return key
}

然后在 interceptors.response 中赋予缓存值:

instance.interceptors.response.use((response) => {if (response.status === 200 && response.config.method === 'get') {const curHttpCacheKey = configToKey(response.config)// 缓存中设置的值要和下面 return 的结果一致httpCache.setCache(curHttpCacheKey, response)}return response},(error) => {return Promise.reject(error)}
)

下面说下 httpCache

import CacheMemory from 'f-cache-memory'const httpCache = new CacheMemory()
export default httpCache

缓存库 f-cache-memory

初始化一个库就行了,f-cache-memory 就是我专门开发的库,底层用的 map ,有些API也贴近 map,API如下:

初始化参数

参数默认值描述版本
size?: number100最多缓存多少个
expiration?: numberNumber.MAX_SAFE_INTEGER按时间毫秒设置缓存有效期,超出时间会被删除
change?: (data: [string, any][]) => void-当缓存变更的时候,可以在此方法内同步外部数据新增于 v0.0.7

api

名称参数返回值类型描述版本
initCachedata: [string, any][]-初始化缓存数据新增于 v0.0.7
hasCachekey: stringboolean验证是否在缓存中
setCachekey: string, data: any, expiration?: number-设置缓存,expiration 以毫秒为单位设置缓存有效期,优先级高于初始化的 expiration 参数,未设置时默认为 初始化的 expirationexpiration 新增于 v0.0.3
getCachekey: stringany获取缓存
deleteCachekey: string-删除缓存
deleteCacheByStartsurl: string-根据键值的前缀删除缓存
clearCache--清空缓存
cacheSize-number有多少个缓存
getNowCache-any获取当前缓存,默认为最后一个,getPreviousCache/getNextCache/goPostionCache/goAbsPostionCache都会影响当前缓存的值
getPreviousCache-any按设置顺序前一个缓存
getNextCache-any按设置顺序后一个缓存
goPostionCachenum: numberany相对当前缓存获取缓存,1为后一个,-1为前一个
goAbsPostionCachenum: numberany按照设置顺序获取第 num 个缓存
getCacheToArrayneedTime: boolean = false[string, any][]按设置顺序转换为数组,如果参数为 false,则直接返回设置的数据,如果为 true,则会返回 { dateTime: 过期时间, data: 设置数据 }dateTime 参数新增于 v0.0.7

同步缓存内外数据

当我们希望缓存内的数据和缓存外联动时,我们可以初始化时传入 第三个参数 change 函数, change 函数的参数就是缓存内的数据(内部数据的结构是 { dateTime: 过期时间, data: 设置的缓存 }),所以如果与 localStorage 联动如下:

const localCache = new CacheMemory(100, 100000, (data) => {localStorage.setItem('localCache', JSON.stringify(data))
})
localCache.setCache('aaa', 111)
localCache.setCache('bbb', 222)

那下次再打开浏览器,localStorage 内的值如何传递到缓存中,此时可以初始化之后使用 initCache :

const initCache = new CacheMemory()
const localStorageCache = localStorage.getItem('localCache')
if (localStorageCache) {initCache.initCache(JSON.parse(localStorageCache))
}
console.log(initCache.getCacheToArray())

Vue:

const cacheList = ref<[string, any][]>([])
const localCache = new CacheMemory(100, 100000, (data) => {cacheList.value = data
})

React:

const [cacheList, setCacheList] = useState<[string, any][]>([])
const localCache = new CacheMemory(100, 100000, (data) => {setCacheList(data)
})

实际上面还有个问题,就是添加缓存之后,什么时候使缓存失效,虽然有过期时间一说,但设的小了,缓存没效果,设得大了,就涉及需要清缓存。

这里我提供几个思路:

  1. 过期时间设的小一点,仅保证多个组件同时加载接口时做到缓存;
  2. 接口映射表,哪些接口改变之后需要清缓存,做好映射关系,在 interceptors.response 中清除对应缓存,这样项目中的代码不用动;
  3. 如果项目完全采用的 REST API 风格,可以在 post/put/delete 中清除对应缓存,此处有个 例子。

最后我们采用了第一种思路,解决服务器临时压力,因为接口规范不统一,接口映射表又太多,一时难以保证齐全。

完整例子可以查看 vue-components ,本地运行,接口 mock 。

相关文章:

为了实现接口缓存,专门写了个缓存库 f-cache-memory

问题起因 起因是某次发版之后&#xff0c;服务器接口压力过大&#xff0c;当场宕机&#xff0c;排查之后发现有个接口在首页被调十来次&#xff08;六七年的老项目了&#xff0c;都是泪呀&#xff09;&#xff0c;后端反馈这个接口的sql很复杂&#xff0c;很耗性能&#xff0c…...

actual combat 35 —— es

一、windows中es执行步骤 参考&#xff1a;https://blog.csdn.net/qq_21197507/article/details/115076913 下es安装包下es前端gitHub代码&#xff0c;然后npm -i安装&#xff0c;npm run start 启动安装kibana 二、遇到的问题 1. 第二步安装前端代码依赖报错 npm ERR! co…...

android R ext4 image打包脚本介绍

一、Android R打包指令使用介绍 &#xff08;1&#xff09;mkuserimg_mke2fs #./mkuserimg_mke2fs --help usage: mkuserimg_mke2fs [-h] [--android_sparse] [--journal_size JOURNAL_SIZE][--timestamp TIMESTAMP] [--fs_config FS_CONFIG][--product_out PRODUCT_OUT][--b…...

美式键盘 QWERTY 布局的来历

注&#xff1a;机翻&#xff0c;未校对。 The QWERTY Keyboard Is Tech’s Biggest Unsolved Mystery QWERTY 键盘是科技界最大的未解之谜 It’s on your computer keyboard and your smartphone screen: QWERTY, the first six letters of the top row of the standard keybo…...

ETL数据同步之DataX,附赠一套DataX通用模板

今天跟大家分享数据同步datax的模板&#xff0c;小伙伴们简单直接借鉴使用。 还记得上一篇关于大数据DS调度工具的分享嘛&#xff1f; 主流大数据调度工具DolphinScheduler之数据ETL流程-CSDN博客 里面的核心就是采用了DATAX的数据同步原理。 一&#xff0c;什么是DataX D…...

[论文笔记] CT数据配比方法论——1、Motivation

我正在写这方面的论文,感兴趣的可以和我一起讨论!!!!!! Motivation 1、探测原有模型的配比: 配比 与 ppl, loss, bpw, benchmark等指标 之间的关系。 2、效果稳定的配比:配比 与 模型效果 之间的规律。 Experiments 1、主语言(什么语言作为主语言,几种主语言?…...

某4G区域终端有时驻留弱信号小区分析

这些区域其实是长时间处于连接态的电信卡4G终端更容易出现。 出现问题时都是band1 100频点下发了针对弱信号的1650频点的连接态A4测量事件配置&#xff08;其阈值为-106&#xff09;。而这个条件很容易满足&#xff0c;一旦下发就会切到band3 1650频点。 而1650频点虽然下发ban…...

【体外诊断】ARM/X86+FPGA嵌入式计算机在免疫分析设备中的应用

体外诊断 信迈提供基于Intel平台、AMD平台、NXP平台的核心板、2.5寸主板、Mini-ITX主板、4寸主板、PICO-ITX主板&#xff0c;以及嵌入式准系统等计算机硬件。产品支持GAHDMI等独立双显&#xff0c;提供丰富串口、USB、GPIO、PCIe扩展接口等I/O接口&#xff0c;扩展性强&#xf…...

Linux上启动和停止jar

linux 后台运行jar 在Linux系统中&#xff0c;要想让jar包在后台运行&#xff0c;可以使用nohup命令和&符号。nohup命令可以使进程在后台不受挂起信号影响的执行&#xff0c;而&符号则是将任务放入后台执行。 以下是一个简单的命令示例&#xff0c;它将启动一个jar包…...

浏览器缓存:强缓存与协商缓存实现原理有哪些?

1、强缓存&#xff1a;设置缓存时间的&#xff0c;那么在这个时间内浏览器向服务器发送请求更新数据&#xff0c;但是服务器会让其从缓存中获取数据。 可参考&#xff1a;彻底弄懂强缓存与协商缓存 - 简书 2、协商缓存每次都会向浏览器询问&#xff0c;那么是怎么询问的呢&…...

持续集成04--Jenkins结合Gitee创建项目

前言 在持续集成/持续部署&#xff08;CI/CD&#xff09;的旅途中&#xff0c;Jenkins与版本控制系统的紧密集成是不可或缺的一环。本篇“持续集成03--Jenkins结合Gitee创建项目”将引导如何将Jenkins与Gitee&#xff08;一个流行的Git代码托管平台&#xff09;相结合&#xff…...

【Node.js基础02】fs、path模块

目录 一&#xff1a;fs模块-读写文件 1 加载fs模块对象 2 读制定文件内容文件 3 向文件中写入内容 二&#xff1a;path模块-路径处理 1 问题引入 2 __dirname内置变量 使用方法 一&#xff1a;fs模块-读写文件 fs模块封装了与本机文件系统交互方法和属性 1 加载fs模块…...

牛客TOP101:单链表的排序

文章目录 1. 题目描述2. 解题思路3. 代码实现 1. 题目描述 2. 解题思路 按我们以往的排序算法来看&#xff0c;针对链表来说都是太不合适&#xff0c;因为很多都会出现指针前移后移&#xff0c;后移还好说&#xff0c;前移对于链表来说就太难了&#xff0c;而且大部分都是某一个…...

数据可视化配色新工具,颜色盘多达2500+类

好看的配色,不仅能让图表突出主要信息,更能吸引读者,之前分享过很多配色工具,例如, 👉可视化配色工具:颜色盘多达3000+类,数万种颜色! 本次再分享一个配色工具pypalettes,颜色盘多达2500+类。 安装pypalettes pip install pypalettes pypalettes使用 第1步,挑选…...

SpringAI简单使用(本地模型+自定义知识库)

Ollama 简介 Ollama是一个开源的大型语言模型服务工具&#xff0c;它允许用户在本地机器上构建和运行语言模型&#xff0c;提供了一个简单易用的API来创建、运行和管理模型&#xff0c;同时还提供了丰富的预构建模型库&#xff0c;这些模型可以轻松地应用在多种应用场景中。O…...

为什么要从C语言开始编程

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;很多小伙伴在入门编程时。都…...

[数据集][目标检测]导盲犬拐杖检测数据集VOC+YOLO格式4635张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;4635 标注数量(xml文件个数)&#xff1a;4635 标注数量(txt文件个数)&#xff1a;4635 标注…...

数据结构(稀疏数组)

简介 稀疏数组是一种数据结构&#xff0c;用于有效地存储和处理那些大多数元素都是零或者重复值的数组。在稀疏数组中&#xff0c;只有非零或非重复的元素会被存储&#xff0c;从而节省内存空间。 案例引入 假如想把下面这张表存入文件&#xff0c;我们会怎么做&#xff1f;…...

python 爬虫技术 第02节 基础复习

Python基础复习 Python 是一种高级、通用、解释型的编程语言&#xff0c;以其简洁的语法和强大的功能在数据科学、Web 开发、自动化脚本编写、机器学习等领域广泛使用。下面是一些 Python 基础概念的复习&#xff1a; 1. 数据类型 Python 支持多种内置数据类型&#xff0c;包…...

数据结构-C语言-排序(3)

代码位置&#xff1a;test-c-2024: 对C语言习题代码的练习 (gitee.com) 一、前言&#xff1a; 1.1-排序定义&#xff1a; 排序就是将一组杂乱无章的数据按照一定的规律&#xff08;升序或降序&#xff09;组织起来。(注&#xff1a;我们这里的排序采用的都为升序) 1.2-排序分…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

PHP 8.5 即将发布:管道操作符、强力调试

前不久&#xff0c;PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5&#xff01;作为 PHP 语言的又一次重要迭代&#xff0c;PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是&#xff0c;借助强大的本地开发环境 ServBay&am…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...