首页
学习
活动
专区
圈层
工具
发布

Spring Web 异步响应实战:CF → RBE 全链路指南

在做 Web 开发的时候,大家多少都遇到过那种「接口一调用就要跑很久」的情况吧。比如导出一个几十万条数据的 Excel,或者调用第三方接口需要好几秒钟。传统 Servlet 模型下,一个请求线程要一直卡在那儿,结果就是线程池很快被占满,后面请求全挂。

Spring 其实早就给我们准备了几套异步工具,这里我就聊聊CompletableFuture(CF)ResponseBodyEmitter(RBE),它俩搭起来用,可以撑起一整条从服务层到 Controller 的异步链路。

CompletableFuture:服务层的第一块砖

很多人第一反应是@Async,但CompletableFuture更灵活。它不仅能异步执行,还能做组合,比如先查数据库,再调用远程接口,最后合并结果。

举个例子,假设我们要同时拉用户信息和订单信息:

@Service

publicclass UserService {

  @Async

  public CompletableFuture<User> getUser(Long id) {

      return CompletableFuture.supplyAsync(() -> {

          // 模拟IO耗时

          sleep(500);

          returnnew User(id, "张三");

      });

  }

  @Async

  public CompletableFuture<List<Order>> getOrders(Long userId) {

      return CompletableFuture.supplyAsync(() -> {

          sleep(800);

          return Arrays.asList(new Order("A123"), new Order("B456"));

      });

  }

  private void sleep(long ms) {

      try { Thread.sleep(ms); } catch (InterruptedException ignored) {}

  }

}

这里返回的是CompletableFuture,Spring 能识别它,并且自动异步执行。好处就是:这两个任务并行跑,总耗时只要 800ms 左右。

ResponseBodyEmitter:控制层的“流式出口”

如果你只是想返回 JSON,CF 直接返回就行。但有时候,结果不是一次性拼好的,而是逐步产生的,比如「批量任务进度推送」「大文件分片返回」。这时候ResponseBodyEmitter就派上用场了。

@RestController

@RequestMapping("/export")

publicclass ExportController {

  @Autowired

  private UserService userService;

  @GetMapping("/users")

  public ResponseBodyEmitter exportUsers() {

      ResponseBodyEmitter emitter = new ResponseBodyEmitter();

      CompletableFuture.runAsync(() -> {

          try {

              for (long i = 1; i <= 5; i++) {

                  User user = userService.getUser(i).join();

                  emitter.send("用户: " + user.getName() + "\n");

                  Thread.sleep(300);

              }

              emitter.complete();

          } catch (Exception e) {

              emitter.completeWithError(e);

          }

      });

      return emitter;

  }

}

你会发现这个接口一调用,前端不是等几秒才出结果,而是「一点点往下刷数据」,用户体验友好得多。

两者怎么串起来?

其实思路挺简单:

服务层用CompletableFuture去并行跑任务,充分利用多核和线程池。

控制层用ResponseBodyEmitter来把结果「一边算一边推」出去。

这样就形成了一个完整的链路:请求进来 异步执行 分批响应。线程池也不会一直被长任务堵死。

小坑提醒

线程池别偷懒:默认的@Async用的是SimpleAsyncTaskExecutor,没复用线程,生产环境分分钟爆。记得配个ThreadPoolTaskExecutor。

超时控制要加:长连接推数据,最好给 emitter 设置超时,或者结合网关超时参数。

异常兜底:CF 链式调用里,exceptionally/handle记得加上,否则一个异常直接炸全链路。

如果你遇到那种“耗时任务 + 大量并发 + 用户不能一直干等”的场景,可以大胆考虑 CF + RBE 这一套。写法不复杂,但能有效解决线程池压力,还能顺带提升前端体验。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/Or1GWCcVIMoWPtDznezXDV8Q0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券