为了实现接口缓存,专门写了个缓存库 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
拼接的字符串作为 key
,httpCache
后续会讲, 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?: number | 100 | 最多缓存多少个 | |
expiration?: number | Number.MAX_SAFE_INTEGER | 按时间毫秒设置缓存有效期,超出时间会被删除 | |
change?: (data: [string, any][]) => void | - | 当缓存变更的时候,可以在此方法内同步外部数据 | 新增于 v0.0.7 |
api
名称 | 参数 | 返回值类型 | 描述 | 版本 |
---|---|---|---|---|
initCache | data: [string, any][] | - | 初始化缓存数据 | 新增于 v0.0.7 |
hasCache | key: string | boolean | 验证是否在缓存中 | |
setCache | key: string, data: any, expiration?: number | - | 设置缓存,expiration 以毫秒为单位设置缓存有效期,优先级高于初始化的 expiration 参数,未设置时默认为 初始化的 expiration | expiration 新增于 v0.0.3 |
getCache | key: string | any | 获取缓存 | |
deleteCache | key: string | - | 删除缓存 | |
deleteCacheByStarts | url: string | - | 根据键值的前缀删除缓存 | |
clearCache | - | - | 清空缓存 | |
cacheSize | - | number | 有多少个缓存 | |
getNowCache | - | any | 获取当前缓存,默认为最后一个,getPreviousCache /getNextCache /goPostionCache /goAbsPostionCache 都会影响当前缓存的值 | |
getPreviousCache | - | any | 按设置顺序前一个缓存 | |
getNextCache | - | any | 按设置顺序后一个缓存 | |
goPostionCache | num: number | any | 相对当前缓存获取缓存,1为后一个,-1为前一个 | |
goAbsPostionCache | num: number | any | 按照设置顺序获取第 num 个缓存 | |
getCacheToArray | needTime: 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)
})
实际上面还有个问题,就是添加缓存之后,什么时候使缓存失效,虽然有过期时间一说,但设的小了,缓存没效果,设得大了,就涉及需要清缓存。
这里我提供几个思路:
- 过期时间设的小一点,仅保证多个组件同时加载接口时做到缓存;
- 接口映射表,哪些接口改变之后需要清缓存,做好映射关系,在
interceptors.response
中清除对应缓存,这样项目中的代码不用动; - 如果项目完全采用的 REST API 风格,可以在
post/put/delete
中清除对应缓存,此处有个 例子。
最后我们采用了第一种思路,解决服务器临时压力,因为接口规范不统一,接口映射表又太多,一时难以保证齐全。
完整例子可以查看 vue-components ,本地运行,接口 mock 。
相关文章:
为了实现接口缓存,专门写了个缓存库 f-cache-memory
问题起因 起因是某次发版之后,服务器接口压力过大,当场宕机,排查之后发现有个接口在首页被调十来次(六七年的老项目了,都是泪呀),后端反馈这个接口的sql很复杂,很耗性能,…...
actual combat 35 —— es
一、windows中es执行步骤 参考:https://blog.csdn.net/qq_21197507/article/details/115076913 下es安装包下es前端gitHub代码,然后npm -i安装,npm run start 启动安装kibana 二、遇到的问题 1. 第二步安装前端代码依赖报错 npm ERR! co…...
android R ext4 image打包脚本介绍
一、Android R打包指令使用介绍 (1)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 布局的来历
注:机翻,未校对。 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的模板,小伙伴们简单直接借鉴使用。 还记得上一篇关于大数据DS调度工具的分享嘛? 主流大数据调度工具DolphinScheduler之数据ETL流程-CSDN博客 里面的核心就是采用了DATAX的数据同步原理。 一,什么是DataX D…...
[论文笔记] CT数据配比方法论——1、Motivation
我正在写这方面的论文,感兴趣的可以和我一起讨论!!!!!! Motivation 1、探测原有模型的配比: 配比 与 ppl, loss, bpw, benchmark等指标 之间的关系。 2、效果稳定的配比:配比 与 模型效果 之间的规律。 Experiments 1、主语言(什么语言作为主语言,几种主语言?…...
某4G区域终端有时驻留弱信号小区分析
这些区域其实是长时间处于连接态的电信卡4G终端更容易出现。 出现问题时都是band1 100频点下发了针对弱信号的1650频点的连接态A4测量事件配置(其阈值为-106)。而这个条件很容易满足,一旦下发就会切到band3 1650频点。 而1650频点虽然下发ban…...
【体外诊断】ARM/X86+FPGA嵌入式计算机在免疫分析设备中的应用
体外诊断 信迈提供基于Intel平台、AMD平台、NXP平台的核心板、2.5寸主板、Mini-ITX主板、4寸主板、PICO-ITX主板,以及嵌入式准系统等计算机硬件。产品支持GAHDMI等独立双显,提供丰富串口、USB、GPIO、PCIe扩展接口等I/O接口,扩展性强…...
Linux上启动和停止jar
linux 后台运行jar 在Linux系统中,要想让jar包在后台运行,可以使用nohup命令和&符号。nohup命令可以使进程在后台不受挂起信号影响的执行,而&符号则是将任务放入后台执行。 以下是一个简单的命令示例,它将启动一个jar包…...
浏览器缓存:强缓存与协商缓存实现原理有哪些?
1、强缓存:设置缓存时间的,那么在这个时间内浏览器向服务器发送请求更新数据,但是服务器会让其从缓存中获取数据。 可参考:彻底弄懂强缓存与协商缓存 - 简书 2、协商缓存每次都会向浏览器询问,那么是怎么询问的呢&…...
持续集成04--Jenkins结合Gitee创建项目
前言 在持续集成/持续部署(CI/CD)的旅途中,Jenkins与版本控制系统的紧密集成是不可或缺的一环。本篇“持续集成03--Jenkins结合Gitee创建项目”将引导如何将Jenkins与Gitee(一个流行的Git代码托管平台)相结合ÿ…...
【Node.js基础02】fs、path模块
目录 一:fs模块-读写文件 1 加载fs模块对象 2 读制定文件内容文件 3 向文件中写入内容 二:path模块-路径处理 1 问题引入 2 __dirname内置变量 使用方法 一:fs模块-读写文件 fs模块封装了与本机文件系统交互方法和属性 1 加载fs模块…...
牛客TOP101:单链表的排序
文章目录 1. 题目描述2. 解题思路3. 代码实现 1. 题目描述 2. 解题思路 按我们以往的排序算法来看,针对链表来说都是太不合适,因为很多都会出现指针前移后移,后移还好说,前移对于链表来说就太难了,而且大部分都是某一个…...
数据可视化配色新工具,颜色盘多达2500+类
好看的配色,不仅能让图表突出主要信息,更能吸引读者,之前分享过很多配色工具,例如, 👉可视化配色工具:颜色盘多达3000+类,数万种颜色! 本次再分享一个配色工具pypalettes,颜色盘多达2500+类。 安装pypalettes pip install pypalettes pypalettes使用 第1步,挑选…...
SpringAI简单使用(本地模型+自定义知识库)
Ollama 简介 Ollama是一个开源的大型语言模型服务工具,它允许用户在本地机器上构建和运行语言模型,提供了一个简单易用的API来创建、运行和管理模型,同时还提供了丰富的预构建模型库,这些模型可以轻松地应用在多种应用场景中。O…...
为什么要从C语言开始编程
在开始前刚好我有一些资料,是我根据网友给的问题精心整理了一份「C语言的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!很多小伙伴在入门编程时。都…...
[数据集][目标检测]导盲犬拐杖检测数据集VOC+YOLO格式4635张2类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):4635 标注数量(xml文件个数):4635 标注数量(txt文件个数):4635 标注…...
数据结构(稀疏数组)
简介 稀疏数组是一种数据结构,用于有效地存储和处理那些大多数元素都是零或者重复值的数组。在稀疏数组中,只有非零或非重复的元素会被存储,从而节省内存空间。 案例引入 假如想把下面这张表存入文件,我们会怎么做?…...
python 爬虫技术 第02节 基础复习
Python基础复习 Python 是一种高级、通用、解释型的编程语言,以其简洁的语法和强大的功能在数据科学、Web 开发、自动化脚本编写、机器学习等领域广泛使用。下面是一些 Python 基础概念的复习: 1. 数据类型 Python 支持多种内置数据类型,包…...
数据结构-C语言-排序(3)
代码位置:test-c-2024: 对C语言习题代码的练习 (gitee.com) 一、前言: 1.1-排序定义: 排序就是将一组杂乱无章的数据按照一定的规律(升序或降序)组织起来。(注:我们这里的排序采用的都为升序) 1.2-排序分…...
【分布式事务】怎么解决分布式场景下数据一致性问题
分布式事务的由来 拿充值订单举个栗子吧,假设:原本订单模块和账户模块是放在一起的,现在需要做服务拆分,拆分成订单服务,账户余额服务。原本收到充值回调后,可以将修改订单状态和扣减余额放在一个mysql事务…...
C# 中的委托
委托的概念 在C#中,委托是一种引用类型,它表示对方法的引用,即委托就是一种用来指向一个方法的引用类型变量。委托的声明类似于方法签名,但是关键字是delegate。下面是一个委托的声明和使用的例子: // 声明一个委托 p…...
通过docker构建基于LNMP的WordPress项目
目录 1.准备nginx 2.准备mysql 3.准备php 4.构建各镜像 5.运行wordpress 1、项目环境: 1.1 (1)公司在实际的生产环境中,需要使用Docker 技术在一台主机上创建LNMP服务并运行Wordpress网站平台。然后对此服务进行相关的性能…...
2024新版IntelliJ IDEA修改包名 全网最简单最粗暴的方法
问题再现 我们在网上淘一些后端框架 又或者是开源的项目 如果要变成自己的 难免会去改包名 即把com.后面的内容改成自己自定义的 第一次我们直接用网络上的方法 shift F6 快捷键 可以修改包名 出现以下情况 进行修改 我们发现失败了 并没有像预计的一样直接把包名修…...
C#中处理Socket粘包
在C#中使用Socket进行网络通信时,粘包问题是常见的。粘包问题通常发生在TCP协议中,因为TCP是流式协议,数据可能会被分割成多个包发送,也可能多个小包会被合并成一个大包接收。 处理粘包问题的常见方法是使用消息分隔符或消息长度…...
7.19IO
思维导图 第一题:测试错误检查锁和递归锁是否会造成死锁状态 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #i…...
【Vue】深入了解 Axios 在 Vue 中的使用:从基本操作到高级用法的全面指南
文章目录 一、Axios 简介与安装1. 什么是 Axios?2. 安装 Axios 二、在 Vue 组件中使用 Axios1. 发送 GET 请求2. 发送 POST 请求 三、Axios 拦截器1. 请求拦截器2. 响应拦截器 四、错误处理五、与 Vuex 结合使用1. 在 Vuex 中定义 actions2. 在组件中调用 Vuex acti…...
【Qt】窗口
文章目录 QMainWindow菜单栏工具栏状态栏浮动窗口对话框自定义对话框Qt内置对话框QMessageBox QMainWindow Qt中的主窗口以QMainWindow表示,其总体结构如下: 菜单栏 菜单栏MenuBar,可包含多个菜单Menu,每个菜单也可以包含多个菜…...
代码随想录训练营【贪心算法篇】
贪心 注:本文代码来自于代码随想录 贪心算法一般分为如下四步: 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优解将局部最优解堆叠成全局最优解 这个四步其实过于理论化了,我们平时在做贪心类的题目 很难去按照这四步…...
Spark中的JOIN机制
Spark中的JOIN机制 1、Hash Join概述2、影响JOIN的因素3、Spark中的JOIN机制3.1、Shuffle Hash Join3.2、Broadcast Hash Join3.3、Sort Merge Join3.4、Cartesian Product Join3.5、Broadcast Nested Loop Join4、Spark中的JOIN策略5、Spark JOIN机制与策略总结5.1、Spark中的…...
做网站用中文路径/网站服务器速度对seo有什么影响
昨天开发的时候一直纠结一个问题,给a标签注册事件,却一直没有注册上! $(#myTab li a).click(function() {localStorage.setItem(tabId, $(this).attr("href"));});完全是一个普通的事件注册代码。今天排查的结论是,$(#m…...
php做的网站好不好/西安百度竞价推广
配置范例站点站点序号域名目录1www.heytool.com/www/html/www.heytool.com2bbs.heytool.com/www/html/bbs.heytool.com 修改nginx 配置文件:# vi nginx.confuser nobody nobody; # 运行 nginx 的所属组和所有者 Nginx 启动关闭# /usr/local/nginx-1.0.6/sbin/ngin…...
wordpress小工具文件/南京网络营销服务
原生js实现上拉加载其实超级简单,把原理整明白了你也会,再也不用去引一个mescroll啦~ 好了,废话不多说,开始进入正题:上拉加载是怎么去做的,原理就是监听滚动条滑到页面底部,然后就去做一次请求…...
wordpress 乱码/推广软文300字范文
硬件系统设计硬件平台基于ARM920T的处理器AT91RM9200,该处理器不仅有丰富的片上资源和标准接口,而且有低功耗、低成本、高性能、支持多种主要的嵌入式操作系统等特点,其采用5级整数流水线结构,性能高达200此方案的硬件系统结构设计…...
耐克1网站建设的总体目标/关键词优化外包服务
以下以添加Eclips为例 在桌面上添加Eclips.desktop 文件,向其写入如下代码 [Desktop Entry]NameEclipseComment用Eclipse开发Exec/usr/lib/eclispe/eclipseIcon/usr/lib/eclipse/eclipse32.pngTerminalfalseTypeApplicationCategoriesApplication;Development;将其…...
书怎么做pdf下载网站/微信推广文案
NULL 只能作为指针变量的值.如果1个指针变量的值是NULL值代表.代表这个指针不指向内存中的任何1块空间 NULL其实等价于0 NULL其实是1个宏.就是0 2. nil 只能作为指针变量的值.代表这个指针变量不指向内存中的任何空间. nil其实也等价于0也是1个宏.就是0. 所以, NULL和nil其实…...