SpringMVC异步化请求初探
同步请求
在servlet3.0之前,servlet在同一个线程中解析,处理,响应http请求
异步化请求
servlet3.0之后,servlet提供asyncContext支持异步请求,是的解析,返回请求的线程和处理请求的线程资源分离。
异步化使得原本同步调用时阻塞的线程资源释放出来,可以提高服务器并发性能(有利有弊,同时增加了平均响应时长)。
同时因为处理请求的线程池交由自己管理,就可以根据业务重要性创建多个线程池,特别是在一个项目中有多个业务在运行时:
- 把业务分为核心业务和非核心业务
- 为不同级别的业务定义不同的线程池,线程池之间是隔离的
- 根据不同业务量设置线程池大小
此时在一个池中的业务发生接口或者数据库访问慢的情况,并不会对其他线程池产生影响。
我们还可以(作死)在线上动态调整线程池的大小,或者在服务器线程满的情况下reject一部分请求,保证服务的持续性。
SpringMVC异步化请求
SpringMVC已经对servlet async context使用进行了封装,方便我们使用异步请求。
我们需要改写Controller中handler方法的返回值类型为DeferredResult,
然后在其他线程中调用deferredResult.setResult(result)完成响应。
简单的代码示例:
private ExecutorService executorService = Executors.newFixedThreadPool(10);
@ResponseBody
@RequestMapping("/async")
public DeferredResult<Map<String ,Object>> test(){
DeferredResult<Map<String, Object>> deferredResult = new DeferredResult<>();
executorService.submit(()->asyncTest(deferredResult));
return deferredResult;
}
public void asyncTest(DeferredResult<Map<String, Object>> deferredResult) {
Map<String, Object> result = Maps.newHashMap();
result.put("key","value");
deferredResult.setResult(result);
}
使用RxJava2 进行异步化
SpringMVC 4.0还不支持Reactive编程…不过提供了不少扩展点,可以让我们使用RxJava方便线程切换。
先实现AsyncHandlerMethodReturnValueHandler,让Controller handler可以处理RxJava2.0的Single对象。
public class SingleReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
@Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
return returnValue != null && supportsReturnType(returnType);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return Single.class.isAssignableFrom(returnType.getParameterType());
}
@SuppressWarnings("unchecked")
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
final Single<?> single = Single.class.cast(returnValue);
DeferredResult<Object> deferredResult = new DeferredResult<>();
single.subscribe(deferredResult::setResult, deferredResult::setErrorResult);
WebAsyncUtils.getAsyncManager(webRequest)
.startDeferredResultProcessing(deferredResult, mavContainer);
}
}
然后在mvc中注册一下:
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(Single.class)
public SingleReturnValueHandler singleReturnValueHandler() {
return new SingleReturnValueHandler();
}
@Configuration
public static class RxJavaWebConfiguration {
@Autowired
private List<AsyncHandlerMethodReturnValueHandler> handlers = Collections.emptyList();
@Bean
public WebMvcConfigurer rxJavaWebMvcConfiguration() {
return new WebMvcConfigurerAdapter() {
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
returnValueHandlers.addAll(handlers);
}
};
}
}
现在在Controller中返回Single对象,SpringMVC就可以异步处理请求了。
@GetMapping("/sayHello")
Single<String> sayHello() {
return observerService.sayHello();
}
@Profile("dev")
@Service
public class ObserverService {
Single<String> sayHello() {
return Single
.create((SingleOnSubscribe<String>) e -> e.onSuccess(helloWorld()))
.subscribeOn(Schedulers.computation());
}
String helloWorld() {
//throw new IllegalArgumentException();
return "hello world" + Thread.currentThread().getName();
}
}
执行结果为:
简单的性能压测
先测试同步请求的性能,先把服务器并发线程调整为200:
server.tomcat.max-threads=200
get一下:
sagedeMac-mini:~ SAGE$ siege -g http://192.168.1.102/update/sayHello
[alert] Zip encoding disabled; siege requires zlib support to enable it
HEAD /update/sayHello HTTP/1.0
Host: 192.168.1.102
Accept: */*
User-Agent: Mozilla/5.0 (apple-x86_64-darwin16.0.0) Siege/4.0.2
Connection: close
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 31
Date: Sun, 12 Mar 2017 14:32:40 GMT
Connection: close
Transactions: 1 hits
Availability: 100.00 %
Elapsed time: 0.44 secs
Data transferred: 0.00 MB
Response time: 0.01 secs
Transaction rate: 2.27 trans/sec
Throughput: 0.00 MB/sec
Concurrency: 0.02
Successful transactions: 1
Failed transactions: 0
Longest transaction: 0.01
Shortest transaction: 0.01
Connection close 这个会影响性能…先按这个标准测试吧…
siege -c100 -t60s http://192.168.1.102/update/sayHello
100并发+60秒,注意我这里没有加-b参数
Transactions: 16307 hits
Availability: 100.00 %
Elapsed time: 59.08 secs
Data transferred: 0.47 MB
Response time: 0.01 secs
Transaction rate: 276.02 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 2.13
Successful transactions: 16307
Failed transactions: 0
Longest transaction: 0.08
Shortest transaction: 0.00
siege -c255 -t60s http://192.168.1.102/update/sayHello
255并发+60秒
已经有报错了:
socket: 369811456 connection timed out.: Operation timed out
Transactions: 16874 hits
Availability: 98.51 %
Elapsed time: 59.70 secs
Data transferred: 0.49 MB
Response time: 0.02 secs
Transaction rate: 282.65 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 4.94
Successful transactions: 16874
Failed transactions: 255
Longest transaction: 0.14
Shortest transaction: 0.00
现在再来测测异步请求,先把tomcat并发改成1,线程池为固定200
server.tomcat.max-threads=1
siege -c100 -t60s http://192.168.1.102/update/sayHelloAsync
Transactions: 16355 hits
Availability: 100.00 %
Elapsed time: 59.69 secs
Data transferred: 0.43 MB
Response time: 0.04 secs
Transaction rate: 274.00 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 9.73
Successful transactions: 16355
Failed transactions: 0
Longest transaction: 7.59
Shortest transaction: 0.00
siege -c255 -t60s http://192.168.1.102/update/sayHelloAsync
Transactions: 16787 hits
Availability: 98.50 %
Elapsed time: 59.50 secs
Data transferred: 0.44 MB
Response time: 0.02 secs
Transaction rate: 282.13 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 5.13
Successful transactions: 16787
Failed transactions: 255
Longest transaction: 0.10
Shortest transaction: 0.00
什么情况,并发量没有上去啊,我是不是用了假的异步请求呀…