1. 前言
在使用 Spring Boot 開發流式接口(Server-Sent Events)時,我們遇到了一個令人困惑的問題:每次 SseEmitter 完成后,都會觸發第二次請求,導致重復請求檢測機制誤報。本文將詳細記錄問題的發現、分析過程以及最終的解決方案。
2. 系統架構背景
2.1 請求處理架構
我們的系統采用了標準的 Spring Boot 微服務架構,其中包含一個關鍵的請求攔截器組件:
@Component
@Slf4j
public class RequestHandlerInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 1. API 簽名校驗// 2. 重復請求過濾// 3. 用戶權限驗證// 4. 請求日志記錄return true;}
}
RequestHandlerInterceptor 的主要職責:
- API 簽名校驗:驗證請求的數字簽名,確保請求的完整性和來源可信
- 重復請求過濾:基于 Redis 緩存的防重復提交機制,防止用戶誤操作
- 用戶權限驗證:檢查用戶登錄狀態和訪問權限
- 請求日志記錄:記錄關鍵請求信息用于審計和問題排查
2.2 系統部署架構
客戶端 → Nginx → Spring Boot 微服務 → RequestHandlerInterceptor → Controller
3. 問題現象
3.1 初始癥狀
在我們的導出服務中,使用 SseEmitter 實現流式 PDF 生成功能時,RequestHandlerInterceptor 的 preHandle 方法出現了異常的重復調用現象:
@PostMapping("/monthReport/insurance/v3/pdf/stream")
public SseEmitter reportInsurancePDFV3Stream(@RequestBody ReportInsuranceV3StreamDTO dto) {// SseEmitter 實現
}
RequestHandlerInterceptor 日志顯示的異常行為:
2025-07-07 17:00:33.245 INFO [XNIO-1 task-9] RequestHandlerInterceptor.preHandle() - 第一次調用
2025-07-07 17:00:33.448 INFO [XNIO-1 task-9] === reportInsurancePDFV3Stream START ===
2025-07-07 17:01:21.978 INFO [async-IO-1] === CALLING emitter.complete() ===
2025-07-07 17:01:22.046 INFO [XNIO-1 task-13] RequestHandlerInterceptor.preHandle() - 第二次調用!
2025-07-07 17:01:22.054 WARN [XNIO-1 task-13] 重復請求檢測觸發,客戶端信息:...
2025-07-07 17:01:22.087 INFO [XNIO-1 task-14] === SseEmitter COMPLETION ===
3.2 關鍵發現
- 攔截器重復調用:
RequestHandlerInterceptor.preHandle()
方法被調用了兩次 - 時間差異:
emitter.complete()
調用和第二次攔截器調用之間有明顯的時間間隔 - 線程差異:第二次攔截器調用在不同的線程中執行
- 重復檢測觸發:第二次調用被重復請求檢測機制攔截,導致異常拋出
4. 問題分析歷程
4.1 第一階段:排除外部因素
面對這個奇怪的現象,我們首先懷疑是外部因素導致的重復請求。
4.1.1 排除前端重試機制
最初懷疑是前端 EventSource 存在重試邏輯:
eventSource.onerror = function(event) {// 可能的重試邏輯startExport();
};
驗證方法:使用 curl 直接測試
為了排除前端重復調用的可能性,我們繞過前端,直接使用 curl 命令測試接口:
curl -X POST "http://localhost:8080/api/stream" \-H "Content-Type: application/json" \-H "Authorization: Bearer eyJhbI1NiIsInR5cCI6IkpXVCJ9..." \-H "X-Ca-Nonce: 1234567890" \-d '{"patientId":"176900913670569984","startTime":"2023-07-01","endTime":"2023-07-31"}'
結果:問題依然存在!
即使繞過前端,直接使用 curl 訪問,RequestHandlerInterceptor.preHandle()
仍然被調用了兩次,這證明問題不在前端。
4.1.2 排除 Nginx 配置問題
接下來懷疑是 Nginx 的配置或重試機制導致的:
- Nginx 的 proxy_retry 配置
- Nginx 的超時重試機制
- Nginx 的負載均衡重試
驗證方法:繞過 Nginx 直接訪問微服務
我們直接訪問微服務端口,完全繞過 Nginx: