Axios非幂等POST请求连接超时重试导致服务端重复执行问题求助
问题根因与解决方案
核心根因
你遇到的是HTTP请求的固有特性:
- Axios的
cancelToken本质只是在前端侧中止对响应的监听,并不会真的终止已经发送到网络中的请求。只要请求报文已经离开浏览器到达服务端,就算前端断开连接、取消请求,服务端只要已经开始处理逻辑,就会完整执行完操作,你在前端拿到的取消/超时错误,和服务端是否执行完请求没有任何关系。 - 你当前的超时逻辑没有区分请求阶段,只要到达
connTimeout就直接取消重试,当超时刚好卡在「请求已发出、响应还未回到前端」的时间窗口时,就会出现前端判断超时重试、但上一个请求已经被服务端处理的情况,自然会产生重复的非幂等请求执行。
改造方案
1. 非幂等请求仅允许请求发送前重试
非幂等POST请求绝对不能在请求已经发出后触发重试,connTimeout只能作用于DNS解析、TCP握手、TLS握手这类请求还未发出的阶段,一旦请求开始发送,就立刻终止连接超时的判断逻辑。可以通过Axios自定义适配器监听XMLHttpRequest的状态来实现阶段区分:
const makeRequest = async (args, connTimeout, responseTimeout) => { const source = axios.CancelToken.source(); // 标记请求是否已经发送到服务端 let requestSent = false; const argsWithToken = { ...args, cancelToken: source.token, // 自定义适配器监听请求阶段 adapter: (config) => { const nativeAdapter = axios.defaults.adapter; const xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { // readyState >= 2 代表请求已发出,已收到服务端响应头 if (xhr.readyState >= 2) { requestSent = true; // 一旦请求发出,立刻清除连接超时定时器 clearTimeout(timeout); } }; return nativeAdapter(config); } }; const api = buildAxios(responseTimeout); const timeout = setTimeout(() => { // 仅请求未发出时,才允许取消并重试 if (!requestSent) { source.cancel('Connection timeout before request sent'); } }, connTimeout); return api(argsWithToken) .then(result => { clearTimeout(timeout); return result; }) .catch(err => { // 给错误添加可重试标记 if (axios.isCancel(err) && !requestSent) { err.retryAllowed = true; } throw err; }); }; // 重试逻辑改造 const handleRetries = (args, maxRetries) => ( new Promise(async (resolve, reject) => { let retries = 0; let success = false; while (!success && retries < maxRetries) { try { const result = await makeRequest(args, 300, 30000); success = true; resolve(result); } catch (err) { // 不可重试的错误直接抛出,不继续重试 if (!err.retryAllowed) { return reject(err); } retries += 1; } } if (!success) reject(new Error(`Retried ${retries} times and still failed: ${args.url}`)); }) );
2. 服务端配合做幂等控制(推荐)
如果业务场景下必须支持POST请求超时后的重试,最稳妥的方案是前后端配合做幂等校验:
- 前端每次请求生成全局唯一的
requestId携带在请求头/请求参数中 - 服务端接收到请求后先校验
requestId是否已经处理过,若已处理直接返回上次的结果,不重复执行业务逻辑
该方案可以完全规避网络层的竞态问题,不受前端超时逻辑的影响。
3. 参数设置优化
连接超时connTimeout的作用是筛选无法建立连接的节点,不要设置得和服务端平均响应时长接近,一般设置为1000~2000ms即可,响应超时才需要根据业务接口的实际耗时来调整。
内容的提问来源于stack exchange,提问作者Tallboy
相关产品推荐
相关产品推荐

