Spring Boot SseEmitter 重复请求问题深度分析与解决方案

发布于:2025-08-16 ⋅ 阅读:(17) ⋅ 点赞:(0)

1. 前言

在使用 Spring Boot 开发流式接口(Server-Sent Events)时,我们遇到了一个令人困惑的问题:每次 SseEmitter 完成后,都会触发第二次请求,导致重复请求检测机制误报。本文将详细记录问题的发现、分析过程以及最终的解决方案。

2. 系统架构背景

2.1 请求处理架构

我们的系统采用了标准的 Spring Boot 微服务架构,其中包含一个关键的请求拦截器组件:

@Component
@Slf4j
public class RequestHandlerInterceptor extends HandlerInterceptorAdapter {
   
   

    @Override
    public 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 关键发现

  1. 拦截器重复调用RequestHandlerInterceptor.preHandle() 方法被调用了两次
  2. 时间差异emitter.complete() 调用和第二次拦截器调用之间有明显的时间间隔
  3. 线程差异:第二次拦截器调用在不同的线程中执行
  4. 重复检测触发:第二次调用被重复请求检测机制拦截,导致异常抛出

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:


网站公告

今日签到

点亮在社区的每一天
去签到