真实项目里,“页面并发请求太多”一般不是压测才发现的,而是用户直接告诉你:打开页面慢、偶尔转圈、还会莫名其妙报错。
常见的触发方式也很朴素:一个页面初始化要拉权限、菜单、用户信息、配置、列表、筛选项、推荐位、埋点……再加上组件各自 useEffect 一起跑,瞬间就把并发拉满。
处理这类问题别只想着“后端扛一下”。更健康的做法是前后端一起把请求治理起来:前端负责减少/合并/排队/缓存,后端负责限流/排队/缓存/抗压。
前端层面优化
1. 请求去重
在某些情况下,页面可能会多次触发相同的请求,如用户快速点击按钮、页面轮询等。可以通过 请求去重 来避免无意义的重复请求。
实现方式
使用 Map 或 Set 记录正在进行的请求,并在请求完成后移除。
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); } }); }
|
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, max: 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)
对于短时间内重复请求的数据,可以使用 缓存,减少数据库查询,提高响应速度。
常见缓存策略
示例(使用 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); return data; }); }
|
4. 数据库优化
对于高并发的 API 请求,数据库可能成为性能瓶颈,可以采用 数据库优化 来提升性能:
索引优化:为查询频繁的字段创建索引,提高查询速度。
分库分表:对大数据量的表进行拆分,减少单表查询压力。
读写分离:使用主从数据库架构,读操作走从库,写操作走主库。
示例(MySQL 读写分离):
1 2
| SELECT * FROM users WHERE id = 123; INSERT INTO users (name, age) VALUES ('Tom', 25);
|
总结
在页面请求高并发时,可以采取 前端优化、后端优化 和 数据库优化 的综合策略:
前端优化
请求去重、合并请求
限制请求频率(节流/防抖)
异步加载、懒加载
后端优化
数据库优化
通过合理运用这些策略,可以有效提高系统的抗压能力,避免服务器过载,保证用户在高并发环境下仍能流畅使用应用。
如果你正在处理“首屏接口太多/并发太高”的问题,建议先从前端治理入手(去重、合并、并发上限),再配合后端限流和缓存把稳定性兜住,通常很快就能看到效果。
Happy Coding!