当前位置: 首页 > 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-排序分…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor

1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...

HTTPS证书一年多少钱?

HTTPS证书作为保障网站数据传输安全的重要工具&#xff0c;成为众多网站运营者的必备选择。然而&#xff0c;面对市场上种类繁多的HTTPS证书&#xff0c;其一年费用究竟是多少&#xff0c;又受哪些因素影响呢&#xff1f; 首先&#xff0c;HTTPS证书通常在PinTrust这样的专业平…...

手动给中文分词和 直接用神经网络RNN做有什么区别

手动分词和基于神经网络&#xff08;如 RNN&#xff09;的自动分词在原理、实现方式和效果上有显著差异&#xff0c;以下是核心对比&#xff1a; 1. 实现原理对比 对比维度手动分词&#xff08;规则 / 词典驱动&#xff09;神经网络 RNN 分词&#xff08;数据驱动&#xff09…...