web应用服务器(tomcat等)的连接线程池实际上是有限制的;每一个连接请求都会耗掉线程池的一个连接数;倘若请求操作需要耗时,对连接长时间占用,就会导致所获取的这个连接无法及时被释放。如果此类连接数占用过多,服务器就很可能无法及时响应后续的每个请求。极端情况则有可能耗尽web应用服务器线程池的所有连接,导致服务“宕机”!!
为解决耗时任务占用应用服务器连接数,而客户端又必须等待这些耗时长任务返回才能处理下一步工作的场景,Spring引入了以下机制来处理:
1)使用Callable或者DeferredResult当
2)使用ResponseBodyEmitter/SseEmitter、StreamingResponseBody流式处理多个返回值
a)Callable
public Callable<String> syncCallable() {
logger.info("开始执行!");
Callable<String> callable = () -> {
//TODO 业务处理
return "succeed!";
};
logger.info("执行结束!");
return callable;
}
a1)client 发起请求
a2)SpringMVC处理后进入对应Controller,Controller返回一个Callback对象
a3)SpringMVC调用ruquest.startAsync将Callback提交到TaskExecutor中去执行
a4)DispatcherServlet以及Filters等从应用服务器线程中结束,保留Response打开状态,暂时挂起不返回
a5)TaskExecutor调用Callback返回一个结果,SpringMVC将请求发送给应用服务器继续处理
a6)DispatcherServlet再次被调用并且继续处理Callback返回的对象,Response 响应到client
b)DeferredResult
DeferredResult使用方式与Callable类似,只是在返回结果上不一样,返回时可能实际结果还没有生成,结果可能在另外的线程里面设置到DeferredResult中去。
该类包含以下日常使用相关的特性:
超时配置:通过构造函数可以传入超时时间,单位为毫秒;因为需要等待设置结果后才能继续处理并返回客户端,如果一直等待会导致客户端一直无响应,因此必须有相应的超时机制来避免这个问题;实际上就算不设置这个超时时间,应用服务器或者Spring也会有一些默认的超时机制来处理这个问题。
结果设置:它的结果存储在一个名称为result的属性中;可以通过调用setResult的方法来设置属性;由于这个DeferredResult天生就是使用在多线程环境中的,因此对这个result属性的读写是有加锁的。
@RequestMapping("/deferredResult") @ResponseBody public DeferredResult<String> deferredResult(HttpServletRequest request, HttpServletResponse response) { DeferredResult<MessagePacket> deferredResult = new DeferredResult<>(60 * 1000L); //异步处理业务逻辑并写入结果deferredResult.setErrorResult syncask.execute(deferredResult, request, response,false); deferredResult.onTimeout(() -> { logger.info(Thread.currentThread().getName() + " onTimeout"); // 返回超时信息 deferredResult.setErrorResult("超时"); }); deferredResult.onCompletion(() -> logger.info(Thread.currentThread().getName() + " onCompletion>>>ok" )); return deferredResult; }
它的执行过程如下所示:
b1)client请求服务
b2)SpringMVC处理后调用对应Controller,Controller返回一个DeferredResult对象
b3)SpringMVC调用ruquest.startAsync
b4)DispatcherServlet以及Filters等从应用服务器线程中结束,Response保持打开状态
b5)其它线程处理完业务,将结果写入到DeferredResult中,SpringMVC将请求发送给应用服务器继续处理
b6)DispatcherServlet再次被调用并且继续处理DeferredResult中的结果,Response响应到client
c)SseEmitter/ResponseBodyEmitter
Callback和DeferredResult用于设置单个结果,如果有多个结果需要返回给客户端时,可以使用SseEmitter、ResponseBodyEmitter,可以调用send()方法写入结果,直到调用complete()返回。