前端大并发除了频控和QPS,还可以极致缓存

背景

前面写了网络请求的「频控」和「QPS」的文章:

可以想象,有这两种需求,肯定是有大量并发请求 的场景。除了控制请求的频率,还有一个思路也很有效果:去除冗余的重复请求。毫无疑问,要延续我们优雅的低侵入风格,Decorator 继续走起。

正文

思路

去除冗余的重复请求,最简单直接的思路就是把相同请求的结果缓存起来。下次发现请求相同,直接从内存读取结果返回,直接就不用发请求了。当然,还有一些细节需要处理,比如:

  1. 什么时候清除缓存?
  2. 如果前面的请求还没有返回,又发起了相同的请求怎么办?
  3. 如果返回报异常怎么处理?
  4. ......

这些点用语言解释都太苍白了,直接看代码,一眼就懂的事儿。但是,第 2 点是比较关键也有一定的难度,所以我们稍微展开说说。

现在主流的异步操作,都是使用的 Promise,我们这次也不例外。所以,如果前序请求 A1 还没有完成,重复的 A2 请求就发出了。我们只需要给 A2 返回 A1 的 Promise 就可以了。同理,A3、A4、A5......不管多少个请求来了,都是返回 A1 的 Promise。是不是听起来就很爽?!

什么?!还是不太明白?!

没关系,直接看代码。Talk is cheap, show me the code!

代码

ts 复制代码
// 定义 requestCache 装饰器工厂函数
export const requestCache = (
  originalFunction: (...args: any[]) => Promise<any>,
  cacheDuration: number,
) => {
  const cache: Map<string, any> = new Map();
  const cachePromise: Map<string, Promise<any>> = new Map();

  // 返回一个装饰器函数
  return (...args: any[]) => {
    const [url, payload] = args;

    // 生成唯一的缓存键
    const cacheKey = `${url}-${JSON.stringify(payload)}`;

    // 检查是否有缓存
    if (cache.has(cacheKey)) {
      return Promise.resolve(cache.get(cacheKey));
    }

    // 检查是否有进行中的 Promise
    if (!cachePromise.has(cacheKey)) {
      cachePromise.set(
        cacheKey,
        originalFunction(...args)
          .then((result) => {
            // 请求完成后缓存结果
            cache.set(cacheKey, result);
            // 缓存过期后清除缓存,防止内存泄露
            setTimeout(() => {
              cache.delete(cacheKey);
            }, cacheDuration);
            
            return result;
          })
          .finally(() => {
            // 请求结束后清除 Promise 缓存
            cachePromise.delete(cacheKey);
          }),
      );
    }
    return cachePromise.get(cacheKey)!;
  };
};

Useage:

ts 复制代码
// 同时最多并发 5 个,每秒最多请求 9 个,5 秒内走缓存的请求
export const limitPost = 
requestCache(
  limitQps(
    limitConcurrency(
      request.post, 5), 
    9),
  5000,
);

总结

以上就是利用缓存,解决重复请求的思路和代码。还是有一些不完善的地方,比如:

  • 只针对 url 和 payload 进行了去重判定(因为实际项目中,通常只要 POST 请求需要控制);
  • 如果有些需要重试的逻辑,接口返回的是正常的 200,没有走 catch,而是走了 then。那么错误的结果也是会被缓存的,重试的逻辑会受影响;

这些问题都需要根据项目的实际情况来改良。大家领会这其中的思路即可,相信这里绝大多数的代码是可以复用的。希望大家在追求极致的路上稳步前进。

PS: 本文代码用了 ChatGPT 生成基础代码,然后笔者进行了优化,确实可以提效。

相关推荐
大耳朵土土垚26 分钟前
【Linux】日志设计模式与实现
linux·运维·设计模式
小王子10243 小时前
设计模式Python版 组合模式
python·设计模式·组合模式
linwq85 小时前
设计模式学习(二)
java·学习·设计模式
小王子10241 天前
设计模式Python版 桥接模式
python·设计模式·桥接模式
Cikiss2 天前
「全网最细 + 实战源码案例」设计模式——桥接模式
java·后端·设计模式·桥接模式
纪元A梦2 天前
Java设计模式:行为型模式→观察者模式
java·观察者模式·设计模式
shenyaofeng2 天前
React 封装高阶组件 做路由权限控制
前端·javascript·react.js·职场和发展·typescript
荣--2 天前
DAB实现中用到的主要设计模式
设计模式·dab
草明2 天前
TypeScript 学习
javascript·学习·typescript
mofei121382 天前
Python设计模式 - 组合模式
python·设计模式·组合模式