同一个接口重复请求了n次?并发告警了?你可以试试这个

258 阅读3分钟

背景

一天,我正在做着自己的事情,后端跑过来跟我说,让我看个问题,有个页面一进去,同一个接口会一次性发 4~5 个请求出去,如果这个并发量再增加,会很容易触发并发告警,让我处理下。

排查

首先根据经验,出现这种情况,几乎能断定要么是一个地方循环发送了请求,要么是多个地方都发送了相同的请求。根据对业务的了解,很快排除出,是该页面的不同组件中都会用到同一份数据,这份数据是通过接口获取的,因此发送了多个相同请求。

解决办法

要解决这个问题有一个很简单的办法,就是将这个接口请求放在这些组件的公共父组件中,然后父组件将接口返回的值再逐一传给这些子组件。

这个方法是最简单的,但是如果这些组件与公共父组件之前还隔了 n 代呢,那不是得一层一层的传递数据了,再就是如果这种数据有很多,那这个公共父组件就得请求很多它本身不需要的接口和数据,因此不推荐。

因为我们项目是用的 angular 框架,先说一说我们项目本身的解决方案:

在说之前先说一下我们深度使用的一个库,RxJS

RxJS 是一个使用可观察序列编写异步和基于事件的程序的库。

angular 与 RxJS 是有深度绑定的,在基于 angular 框架的开发中,所有与异步、事件相关的东西都可以使用 RxJS。

先看代码:

class FetchData(id) {
    requestCache = {};
    requestData() {
        if (this.requestCache[id]) {
            return new Observable((observer) => {
                this.requestCache[id].push(observer);
            });
        } else {
            this.setRequestCacheList(id);
            return this.http.get('/url', {id}).pipe(
                map((res) => {
                    const list = this.requestCache[id];
                    list.forEach((item) => {
                        item.next(res);
                    });
                    return res;
                })
            );
        }
    }

    setRequestCacheList(id, observer?) {
        const list = this.requestCache[id] || (this.requestCache[id] = []);
        observer && list.push(observer);
    }
}

在 RxJS 中,Observable 是一个可观察对象,订阅者可以通过 subscribe 方法订阅这个可观察对象。在这个 Observable 已完成了操作,它可以通过 next 方法通知它的订阅者,next 也可以传一个参数,将值一并推送给订阅者。只要这个 Observable 中没有调用 next 方法,它的订阅者就会一直处于订阅状态,等待通知。

整体思路:

  1. 在这个请求类中,先定义一个请求缓存队列,请求的数据与传入的 id 有关系,需要保存这个依赖;
  2. 在有请求到来时,先判断是否已经有相同 id 的请求了,如果有,则将这次的请求先放入 Observable 中,如果没有,则是直接发送请求到后端;
  3. 在实际发送请求到后端的这次请求收到相应后,将缓存队列中所有相同 id 的 Observable 都将相应结果推送给各自的消费者。

在处理异步事务上,RxJS 和 Promise 在某种程度上是类似的,上面的方式用 Promise 来实现的话如下:

class FetchData(id) {
    requestCache = {};
    requestData() {
        if (this.requestCache[id]) {
            return new Promise((resolve) => {
                this.requestCache[id].push(resolve);
            });
        } else {
            this.setRequestCacheList(id);
            return this.http.get('/url', {id}).pipe(
            map((res) => {
                const list = this.requestCache[id];
                list.forEach((resolve) => {
                    resolve(res);
                });
                return res;
            })
            );
        }
    }

    setRequestCacheList(id, resolve?) {
        const list = this.requestCache[id] || (this.requestCache[id] = []);
        resolve && list.push(resolve);
    }
}

最后

其实这种类似于公共数据的应该存放在全局状态库中,先将数据获取到存储起来,后面需要用的直接来取就行。