SpringMVC异步化请求初探

SpringMVC异步化请求初探

同步请求

在servlet3.0之前,servlet在同一个线程中解析,处理,响应http请求

Created with Raphaël 2.1.0 客户端 客户端 服务器 服务器 http request 解析,处理,生成响应 http response

异步化请求

servlet3.0之后,servlet提供asyncContext支持异步请求,是的解析,返回请求的线程和处理请求的线程资源分离。

Created with Raphaël 2.1.0 客户端 客户端 服务器 服务器 业务线程池 业务线程池 http request 解析请求 submit 处理请求 complete 返回响应 http response

异步化使得原本同步调用时阻塞的线程资源释放出来,可以提高服务器并发性能(有利有弊,同时增加了平均响应时长)。

同时因为处理请求的线程池交由自己管理,就可以根据业务重要性创建多个线程池,特别是在一个项目中有多个业务在运行时:

  1. 把业务分为核心业务和非核心业务
  2. 为不同级别的业务定义不同的线程池,线程池之间是隔离的
  3. 根据不同业务量设置线程池大小

此时在一个池中的业务发生接口或者数据库访问慢的情况,并不会对其他线程池产生影响。

我们还可以(作死)在线上动态调整线程池的大小,或者在服务器线程满的情况下reject一部分请求,保证服务的持续性。

SpringMVC异步化请求

SpringMVC已经对servlet async context使用进行了封装,方便我们使用异步请求。

我们需要改写Controller中handler方法的返回值类型为DeferredResult,
然后在其他线程中调用deferredResult.setResult(result)完成响应。

简单的代码示例:

    private ExecutorService executorService = Executors.newFixedThreadPool(10);

    @ResponseBody
    @RequestMapping("/async")
    public DeferredResult> test(){
        DeferredResult> deferredResult = new DeferredResult<>();
        executorService.submit(()->asyncTest(deferredResult));
        return deferredResult;
    }

    public void asyncTest(DeferredResult> 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 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 handlers = Collections.emptyList();

        @Bean
        public WebMvcConfigurer rxJavaWebMvcConfiguration() {
            return new WebMvcConfigurerAdapter() {
                @Override
                public void addReturnValueHandlers(List returnValueHandlers) {
                    returnValueHandlers.addAll(handlers);
                }
            };
        }
    }

现在在Controller中返回Single对象,SpringMVC就可以异步处理请求了。

    @GetMapping("/sayHello")
    Single<String> sayHello() {
        return observerService.sayHello();
    }
@Profile("dev")
@Service
public class ObserverService {

    Single sayHello() {
        return Single
                .create((SingleOnSubscribe) e -> e.onSuccess(helloWorld()))
                .subscribeOn(Schedulers.computation());
    }


    String helloWorld() {
        //throw new IllegalArgumentException();
        return "hello world" + Thread.currentThread().getName();
    }
}

执行结果为:

SpringMVC异步化请求初探_第1张图片

简单的性能压测

先测试同步请求的性能,先把服务器并发线程调整为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

什么情况,并发量没有上去啊,我是不是用了假的异步请求呀…

你可能感兴趣的:(spring,boot,架构)