异步 Controller

使用场景

大文件上传及上传后进行语音识别的应用场景

  • 大文件上传: 企业员工上传完整的会议录音或录像文件
  • 上传后进行语音识别: 对会议内容进行自动转写,生成会议纪要,便于后续查阅和分析讨论点

如果上传完成后不采用异步任务对文件进行语音识别,可能会出现以下问题:

  • 响应延迟: 语音识别是一个计算密集型的操作,特别是当处理大文件时。如果在同一请求中直接进行语音识别,服务器响应时间将大幅增加,导致用户体验变差。
  • 超时错误: 许多 Web 服务器和 HTTP 客户端都有请求超时的限制。长时间的同步处理可能导致请求超时,从而使得语音识别任务失败。
  • 资源占用: 大量的同步语音识别任务可能会占用大量服务器资源,包括 CPU 和内存,影响服务器处理其他请求的能力,降低整体服务的可用性和稳定性。
  • 用户界面冻结: 对于基于 Web 的应用,如果在前端直接等待语音识别的结果,可能导致用户界面冻结,无法进行其他操作,影响用户体验。

目前比较通用的做法是采用异步任务

  1. 文件上传
  • 用户通过客户端(如 Web 页面、移动应用)上传文件。
  • 服务器接收到文件后,存储在服务器上或云存储服务中,并立即返回一个响应告诉用户文件已成功上传。
  1. 异步任务创建
  • 在文件上传成功后,服务器不直接进行语音识别处理,而是创建一个异步任务。
  • 这个任务被发送到消息队列中,这样可以在系统资源允许的情况下按顺序或并行处理。
  1. 任务处理
  • 后台工作进程监听消息队列。一旦发现新任务,就开始处理该任务。
  • 在处理过程中,工作进程执行语音识别操作,将音频文件转写为文本。
  1. 更新状态和通知
  • 一旦异步任务完成,系统会更新任务的状态(例如,从“处理中”变为“已完成”)。
  • 系统可以通过不同的方式通知用户任务已完成,比如电子邮件通知、短信、应用内通知等。
  • 用户可以通过客户端查询任务状态,或者直接获取任务结果(如转写好的文本)。

tio-boot 实现异步

  • tio-boot 内置了线程池工具类 com.litongjava.tio.utils.thread.ThreadUtils

配置线程池

package com.litongjava.apps.asrgpt.config;

import java.util.concurrent.ExecutorService;

import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.utils.thread.ThreadUtils;

@AConfiguration
public class ExecutorServiceConfig {

  @AInitialization
  public void config() {
    // 创建包含10个线程的线程池
    ExecutorService executor = ThreadUtils.newFixedThreadPool(10);

    // 项目关闭时,关闭线程池
    TioBootServer.me().addDestroyMethod(() -> {
      if (executor != null && !executor.isShutdown()) {
        executor.shutdown();
      }
    });

  }
}

在 Controller 中使用线程池,开启异步任务

import com.litongjava.apps.asrgpt.services.AsrSubmitService;
import com.litongjava.apps.asrgpt.validator.AsrSubmitValidator;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.common.UploadFile;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.resp.RespVo;
import com.litongjava.tio.utils.thread.ThreadUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AsrSubmitController {

  public HttpResponse submit(HttpRequest request) {
    UploadFile uploadFile = request.getUploadFile("file");
    String email = request.getParam("email");
    log.info("upload file size:{}", uploadFile.getData().length);
    log.info("email:{}", email);

    //validator
    RespVo respVo = Aop.get(AsrSubmitValidator.class).submit(email);
    if (respVo != null) {
      return Resps.json(request, respVo);
    }

    // 使用ExecutorService异步执行任务
    ThreadUtils.getFixedThreadPool().submit(() -> {
      try {
        RespVo result = Aop.get(AsrSubmitService.class).submit(uploadFile, email);
        // 在这里处理异步操作的结果,例如更新数据库或发送通知等
        log.info("异步任务执行完成, 结果: {}", result);
      } catch (Exception e) {
        log.error("异步任务执行异常", e);
      }
    });

    // 立即响应客户端,表示文件上传请求已接收,正在处理中
    RespVo ok = RespVo.ok();
    ok.setMsg("文件上传成功,正在处理中...");
    return Resps.json(request, ok);
  }
}