MVC5 POST接口每周偶发同一用户同时两次调用的异常排查问题
问题描述
现有一套基于C#开发的MVC5 Web应用,运行稳定,支持10用户并发开具发票,每周会出现一次异常:同一用户同一时间发起两次POST请求,生成重复发票。
- 已排除用户双击按钮:已实现按钮点击后立即禁用,服务端返回后才重新启用的逻辑
- 已排除HTTP2影响:两周前已关闭IIS的HTTP2功能,异常仍发生
- 日志特征:两次请求完全一致,第一次耗时9002ms,第二次耗时444ms,接口正常耗时不足1秒,异常发生时存在网络波动
2021-09-22 16:21:41 167.86.95.177 POST /Sales/Invoices/Save - 443 jnamicela 45.225.105.89 Mozilla/5.0+(Windows+NT+6.3;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36 https://xpertdynamics.com/Home/Index 200 0 1236 9002 2021-09-22 16:21:41 167.86.95.177 POST /Sales/Invoices/Save - 443 jnamicela 45.225.105.89 Mozilla/5.0+(Windows+NT+6.3;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36 https://xpertdynamics.com/Home/Index 200 0 0 444
根本原因分析
- TCP协议层自动重传:这是最高概率的原因。当客户端第一次发送的POST请求已经成功送达服务端,但服务端返回的ACK确认包在公网传输时丢失,客户端在超时窗口内未收到确认信息,会由操作系统内核的TCP协议栈自动重传完全相同的请求包,整个过程不经过上层JS逻辑,所以前端的按钮禁用逻辑完全拦截不到,普通的网络延迟模拟也很难复现「请求到端、回包丢失」的特定场景。日志中第一次请求耗时远高于正常值,就是因为双方在等待TCP重传的超时窗口期。
- 代理/网关层自动重试:如果用户侧有VPN、运营商代理,或者服务端有WAF、反向代理等中间层,部分中间件会默认对超时未返回的POST请求发起自动重试,也会产生完全相同的重复请求,和用户操作、前端逻辑无关。
彻底解决方案
核心方案:实现全链路幂等控制
这是唯一能覆盖所有重试场景的根因解决方案,不受上层操作、中间层、传输层行为影响:
- 页面加载阶段:用户进入发票开具页面时,服务端生成全局唯一的幂等Token,和当前用户身份绑定后返回给前端,存储在表单隐藏域或页面内存中。
- 请求提交阶段:前端将该Token作为请求的固定参数一起传给
/Sales/Invoices/Save接口。 - 服务端处理逻辑:
- 收到POST请求后,首先校验当前用户的该幂等Token是否已经存在处理成功的记录(可存储在Redis中,设置24小时过期即可,无需永久留存)
- 若已存在记录,直接返回上次开具成功的结果,不执行重复业务逻辑
- 若不存在记录,正常执行发票开具逻辑,处理完成后将Token和处理结果绑定存入缓存
兜底优化
业务字段校验(比如校验同一用户短时间内是否提交了完全相同的发票核心信息)可作为兜底逻辑,避免极端场景下的重复数据生成。
内容的提问来源于stack exchange,提问作者Steven Ochoa
相关产品推荐
相关产品推荐

