异步 Controller
使用场景
大文件上传及上传后进行语音识别的应用场景
- 大文件上传: 企业员工上传完整的会议录音或录像文件
- 上传后进行语音识别: 对会议内容进行自动转写,生成会议纪要,便于后续查阅和分析讨论点
如果上传完成后不采用异步任务对文件进行语音识别,可能会出现以下问题:
- 响应延迟: 语音识别是一个计算密集型的操作,特别是当处理大文件时。如果在同一请求中直接进行语音识别,服务器响应时间将大幅增加,导致用户体验变差。
- 超时错误: 许多 Web 服务器和 HTTP 客户端都有请求超时的限制。长时间的同步处理可能导致请求超时,从而使得语音识别任务失败。
- 资源占用: 大量的同步语音识别任务可能会占用大量服务器资源,包括 CPU 和内存,影响服务器处理其他请求的能力,降低整体服务的可用性和稳定性。
- 用户界面冻结: 对于基于 Web 的应用,如果在前端直接等待语音识别的结果,可能导致用户界面冻结,无法进行其他操作,影响用户体验。
目前比较通用的做法是采用异步任务
- 文件上传
- 用户通过客户端(如 Web 页面、移动应用)上传文件。
- 服务器接收到文件后,存储在服务器上或云存储服务中,并立即返回一个响应告诉用户文件已成功上传。
- 异步任务创建
- 在文件上传成功后,服务器不直接进行语音识别处理,而是创建一个异步任务。
- 这个任务被发送到消息队列中,这样可以在系统资源允许的情况下按顺序或并行处理。
- 任务处理
- 后台工作进程监听消息队列。一旦发现新任务,就开始处理该任务。
- 在处理过程中,工作进程执行语音识别操作,将音频文件转写为文本。
- 更新状态和通知
- 一旦异步任务完成,系统会更新任务的状态(例如,从“处理中”变为“已完成”)。
- 系统可以通过不同的方式通知用户任务已完成,比如电子邮件通知、短信、应用内通知等。
- 用户可以通过客户端查询任务状态,或者直接获取任务结果(如转写好的文本)。
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);
}
}