真实项目里,“页面并发请求太多”一般不是压测才发现的,而是用户直接告诉你:打开页面慢、偶尔转圈、还会莫名其妙报错。

常见的触发方式也很朴素:一个页面初始化要拉权限、菜单、用户信息、配置、列表、筛选项、推荐位、埋点……再加上组件各自 useEffect 一起跑,瞬间就把并发拉满。

处理这类问题别只想着“后端扛一下”。更健康的做法是前后端一起把请求治理起来:前端负责减少/合并/排队/缓存,后端负责限流/排队/缓存/抗压

前端层面优化

1. 请求去重

在某些情况下,页面可能会多次触发相同的请求,如用户快速点击按钮、页面轮询等。可以通过 请求去重 来避免无意义的重复请求。

实现方式

使用 MapSet 记录正在进行的请求,并在请求完成后移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
const pendingRequests = new Map<string, Promise<Response>>();

async function fetchData(url: string, options: RequestInit = {}) {
if (pendingRequests.has(url)) {
return pendingRequests.get(url);
}

const promise = fetch(url, options)
.finally(() => pendingRequests.delete(url));

pendingRequests.set(url, promise);
return promise;
}

2. 请求合并

如果多个请求目标相同,可以合并多个请求为一个。例如,多个用户查询不同 ID 的数据,可以合并成一个批量查询请求。

实现方式

  • 例如,某个 API 需要查询多个 ID 的数据,而前端可以短时间内发起多个请求,这时可以合并多个请求,减少服务器压力。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let requestQueue: string[] = [];
let requestTimeout: ReturnType<typeof setTimeout> | null = null;

function batchFetch(ids: string[]) {
return fetch(`/api/data?ids=${ids.join(",")}`).then(res => res.json());
}

function fetchWithBatching(id: string) {
return new Promise(resolve => {
requestQueue.push(id);

if (!requestTimeout) {
requestTimeout = setTimeout(() => {
batchFetch([...new Set(requestQueue)]).then(resolve);
requestQueue = [];
requestTimeout = null;
}, 50); // 50ms 内的请求会被合并
}
});
}

3. 请求节流与防抖

对于频繁触发的请求(如搜索、滚动事件),可以使用 节流(throttle)防抖(debounce) 来控制请求频率。

节流(throttle)

限制请求触发的频率,在一定时间内只允许一次请求。

1
2
3
4
5
6
7
8
9
10
function throttle(fn: Function, delay: number) {
let lastTime = 0;
return function (...args: any[]) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
fn(...args);
}
};
}

防抖(debounce)

当用户输入停止一段时间后才触发请求,适用于搜索输入框等场景。

1
2
3
4
5
6
7
function debounce(fn: Function, delay: number) {
let timer: ReturnType<typeof setTimeout>;
return function (...args: any[]) {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}

4. 给并发加上限(排队)

去重/合并解决的是“别打重复的、别打散的”,但有些页面就是需要请求很多接口。这时更稳的做法是:给并发设一个上限,让请求排队执行,避免弱网/低端机/浏览器连接数限制下直接把自己打爆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function createConcurrencyLimiter(maxConcurrent = 6) {
let active = 0;
const queue: Array<() => void> = [];

const runNext = () => {
if (active >= maxConcurrent) return;
const next = queue.shift();
if (!next) return;
next();
};

return function limit<T>(task: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
const execute = () => {
active++;
task()
.then(resolve, reject)
.finally(() => {
active--;
runNext();
});
};

queue.push(execute);
runNext();
});
};
}

const limit = createConcurrencyLimiter(6);
const fetchLimited = (url: string) => limit(() => fetch(url));

后端层面优化

1. 限流

对于短时间内大量请求,后端可以通过 限流 机制进行控制,防止单个用户或 IP 过度消耗资源。

常见限流算法

  • 令牌桶算法(Token Bucket)

  • 漏桶算法(Leaky Bucket)

  • 固定窗口限流

  • 滑动窗口限流

示例(使用 express-rate-limit 实现 API 限流):

1
2
3
4
5
6
7
8
9
const rateLimit = require("express-rate-limit");

const limiter = rateLimit({
windowMs: 60 * 1000, // 1 分钟
max: 100, // 每分钟最多 100 个请求
message: "请求过多,请稍后再试",
});

app.use("/api", limiter);

2.队列化请求(使用消息队列)

当请求超过后端处理能力时,可以将请求放入 消息队列(如 RabbitMQ, Kafka, Redis 队列),后端按照能力处理,避免瞬时请求量过大。

示例(使用 Redis 队列):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const redis = require("redis");
const client = redis.createClient();

// 添加请求到队列
function addToQueue(data: any) {
client.lpush("requestQueue", JSON.stringify(data));
}

// 后端消费队列
async function processQueue() {
while (true) {
const data = await client.rpop("requestQueue");
if (data) {
handleRequest(JSON.parse(data));
}
}
}

3. 缓存(Cache)

对于短时间内重复请求的数据,可以使用 缓存,减少数据库查询,提高响应速度。

常见缓存策略

  • 浏览器缓存(HTTP 缓存)

  • CDN 缓存

  • Redis/Memcached 缓存

  • 数据库查询缓存(如 MySQL Query Cache)

示例(使用 Redis 缓存 API 请求):

1
2
3
4
5
6
7
8
9
10
11
12
13
const cache = new Map();

function getDataWithCache(key: string, fetchData: () => Promise<any>) {
if (cache.has(key)) {
return Promise.resolve(cache.get(key));
}

return fetchData().then(data => {
cache.set(key, data);
setTimeout(() => cache.delete(key), 60 * 1000); // 1 分钟后清除缓存
return data;
});
}

4. 数据库优化

对于高并发的 API 请求,数据库可能成为性能瓶颈,可以采用 数据库优化 来提升性能:

  • 索引优化:为查询频繁的字段创建索引,提高查询速度。

  • 分库分表:对大数据量的表进行拆分,减少单表查询压力。

  • 读写分离:使用主从数据库架构,读操作走从库,写操作走主库。

示例(MySQL 读写分离):

1
2
SELECT * FROM users WHERE id = 123; -- 走从库
INSERT INTO users (name, age) VALUES ('Tom', 25); -- 走主库

总结

在页面请求高并发时,可以采取 前端优化后端优化数据库优化 的综合策略:

  1. 前端优化

    • 请求去重、合并请求

    • 限制请求频率(节流/防抖)

    • 异步加载、懒加载

  2. 后端优化

    • API 限流

    • 使用消息队列处理请求

    • 缓存热点数据

  3. 数据库优化

    • 读写分离、分库分表

    • 索引优化

通过合理运用这些策略,可以有效提高系统的抗压能力,避免服务器过载,保证用户在高并发环境下仍能流畅使用应用。

如果你正在处理“首屏接口太多/并发太高”的问题,建议先从前端治理入手(去重、合并、并发上限),再配合后端限流和缓存把稳定性兜住,通常很快就能看到效果。

Happy Coding!