目录
一、Servlet的同步与异步
Servlet2.x规范中,所有处理都是同步。即:解析请求、业务处理、响应结果在同一个线程中。如下步骤:
step1:Tomcat解析请求,返回请求对象、响应对象;
step2:Servlet处理业务;
step3:响应处理结果
Servlet3后规范中,可以启动异步上下文(默认同步),业务可以通过线程池处理,达到请求解析与业务处理线程池分离,如下图所示:
二、实现实例
1. 创建线程池并处理业务
注意问题:
a. 没有用注解@Async,主要是业务进行分级,如:核心业务用一级线程池等;
b. 启用异步上下文,需要进行asyncContext.complete()操作,否则直到线程池超时;
package com.common.instance.demo.core.serviceLevel;
import com.alibaba.fastjson.JSON;
import com.log.util.LogUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;
/**
* @description 一级业务线程池处理业务
* @author tcm
* @version 1.0.0
* @date 2021/9/29 16:01
**/
@Component
public class OneLevelAsyncContext implements InitializingBean {
private final String URI = "uri";
private final String PARAMS = "params";
private AsyncListener asyncListener;
private LinkedBlockingDeque<Runnable> queue;
private ThreadPoolExecutor executor;
public Object submitFuture(final HttpServletRequest request, final Callable<Object> task) throws ExecutionException, InterruptedException {
// 获取请求URI
final String uri = request.getRequestURI();
// 获取请求参数
final Map<String, String[]> params = request.getParameterMap();
// 开启异步上下文
final AsyncContext asyncContext = request.startAsync();
asyncContext.getRequest().setAttribute(URI, uri);
asyncContext.getRequest().setAttribute(PARAMS, params);
// 超时设置
asyncContext.setTimeout(2 * 1000);
if (Objects.nonNull(asyncListener)) {
asyncContext.addListener(this.asyncListener);
}
// 线程池处理业务
Future<Object> future = executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
// 业务处理
Object result = task.call();
return result;
}
});
// 完成异步上下文,否则报超时等异常
asyncContext.complete();
return future.get();
}
// 完成初始化配置
@Override
public void afterPropertiesSet() throws Exception {
// 线程池大小
int corePoolSize = Integer.parseInt("100");
// 最大线程池大小
int maxNumPoolSize = Integer.parseInt("200");
// 任务队列
queue = new LinkedBlockingDeque<Runnable>();
// 创建线程池
executor = new ThreadPoolExecutor(corePoolSize, maxNumPoolSize, 100, TimeUnit.MILLISECONDS, queue);
executor.allowCoreThreadTimeOut(true);
// 线程池饱和处理
executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (r instanceof CanceledCallable) {
CanceledCallable cc = ((CanceledCallable) r);
AsyncContext asyncContext = cc.asyncContext;
try {
ServletRequest request = asyncContext.getRequest();
String uri = (String) request.getAttribute(URI);
Map params = (Map) request.getAttribute(PARAMS);
LogUtil.error(String.format("async request %s, uri:%s, params:%s", "rejectedExecution", uri, JSON.toJSONString(params)));
} catch (Exception ex) {
}
try {
HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
asyncContext.complete();
}
}
}
});
// 创建监听器
if (Objects.isNull(asyncListener)) {
asyncListener = new AsyncListener() {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
AsyncContext asyncContext = asyncEvent.getAsyncContext();
try {
ServletRequest request = asyncContext.getRequest();
String uri = (String) request.getAttribute(URI);
Map params = (Map) request.getAttribute(PARAMS);
LogUtil.error(String.format("async request timeout, uri:%s, params:%s", uri, JSON.toJSONString(params)));
} catch (Exception e) {}
try {
HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
asyncContext.complete();
}
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
AsyncContext asyncContext = asyncEvent.getAsyncContext();
try {
ServletRequest request = asyncContext.getRequest();
String uri = (String) request.getAttribute(URI);
Map params = (Map) request.getAttribute(PARAMS);
LogUtil.error(String.format("async request error, uri:%s, params:%s", uri, JSON.toJSONString(params)));
} catch (Exception e) {}
try {
HttpServletResponse resp = (HttpServletResponse) asyncContext.getResponse();
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
asyncContext.complete();
}
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
}
};
}
}
}
package com.common.instance.demo.core.serviceLevel;
import javax.servlet.AsyncContext;
import java.util.concurrent.Callable;
/**
* @description 线程池饱和处理
* @author tcm
* @version 1.0.0
* @date 2021/9/30 10:56
**/
public class CanceledCallable implements Runnable, Callable<Object> {
public AsyncContext asyncContext;
public CanceledCallable(AsyncContext asyncContext){
this.asyncContext = asyncContext;
}
@Override
public void run() {
}
@Override
public Object call() throws Exception {
return null;
}
}
2. 使用业务线程池
package com.common.instance.demo.controller;
import com.common.instance.demo.core.Response;
import com.common.instance.demo.core.serviceLevel.OneLevelAsyncContext;
import com.common.instance.demo.entity.WcPendantTab;
import com.common.instance.demo.service.WcPendantTabService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* Tab菜单(WcPendantTab)表控制层
*
* @author tcm
* @since 2021-01-14 15:02:08
*/
@RestController
@RequestMapping("/tab")
@Api(tags = "活动tab测试")
public class WcPendantTabController {
@Resource
private WcPendantTabService wcPendantTabService;
@Resource
private OneLevelAsyncContext oneLevelAsyncContext;
@PostMapping("/oneLevelAsyncContext")
@ApiOperation("测试一级业务线程池")
public Response<Object> testOneLevelAsyncContext(HttpServletRequest request, WcPendantTab tab) {
try {
Object result = oneLevelAsyncContext.submitFuture(request, () -> wcPendantTabService.queryAll(tab));
return Response.success(result);
} catch (Exception e) {
e.printStackTrace();
}
return Response.error("请稍后重试");
}
}
3. 压测结果
异步线程池提高了吞吐量了,但是也增加了响应时间。说明开启异步处理业务并不能降低响应时间。但是如果业务分级,将核心业务处理进行隔离。