java 异步框架_JAVA开发中这种异步场景如何提升系统的吞吐量?

前言

降低延迟和提高吞吐量对应的方法有两种: 优化算法和将机器的硬件性能发挥到极致。

优化算法就是降低时间和空间复杂度,使得程序执行时间更短。

而将硬件的性能发挥到极致,具体的指提高I/O 和cpu的利用率。

那么,针对下面的异步场景,该如何优化和提升吞吐量呢?

先看下图,一个请求做注册用户(A业务)耗时1秒,在由A调用B业务(发送邮件)耗时3秒,以及C业务(发送短信)耗时2秒

a1b08a4c6ec3e3151255105040acb645.png

我们常规的完成这个业务,一般这样操作,伪代码如下public String doA(){

//......

//A业务完成

//调用B业务

doB(相关参数);

//调用C业务

doC(相关参数);

//返回值

return a;

}这种方式doA方法得到返回值需要的时间为6000ms = 1000ms(A业务耗时) + 3000ms(B业务耗时) + 2000ms(C业务耗时)

还有一点很多文章里面都没有提到,那就是线程的处理方式。doA(),doB(),doC()都是由同一个线程Thead1处理的;这种方式我们通常会称为同步操作。

优化方案

很多人看到此场景,想到的方案就是异步处理,就是B和C业务用其他线程处理,这样整个请求只要耗时1秒,也就是处理完A业务后,就返回了。B和C业务由后台执行。

但有些业务是不能这么做的,如doA的返回值,一定需要知道B或C业务的处理结果,也就是一定返回相关的B或C业务结果。有同学就会说,那不就是同步方案吗?前端浏览器等待所有业务执行完。话说的没有错,但这种同步方案中,有个很大的问题,系统吞吐量不高。

我们的应用部署到tomcat中,tomcat可以支持并发100个请求线程,那在处理A业务的时候,需要6秒;在此6秒内也就只能支持100个请求。我们如何提高吞吐量呢?我们可以采用分解的方式,在A业务完成后重新分配系统线程处理B和C业务,等待B和C业务处理后在返回给前端。前端得到返回值(6000ms) = 1000ms(A业务耗时,tomcat线程Thread1) + 3000ms(B业务耗时,线程Thread2) + 2000ms(C业务耗时,线程Thread3);虽然前端得到返回值耗时还是6秒,但A业务容器线程执行完业务就立刻归还线程给tomcat容器,他可以继续处理其他的请求。A业务会创建副线程进行B和C业务的处理。这样的话请求A业务tomcat可以达到1秒内支持100个请求,6秒内能达到600个请求,提供了6倍。这样就极大的提升了系统吞吐量。

这种方案spring给我们提供了DeferredResult和Callable方式实现。

官方文档中说DeferredResult和Callable都是为了异步生成返回值提供基本的支持。简单来说就是一个请求进来,如果你使用了DeferredResult或者Callable,在没有得到返回数据之前,DispatcherServlet和所有Filter就会退出Servlet容器线程,但响应保持打开状态,一旦返回数据有了,这个DispatcherServlet就会被再次调用并且处理,以异步产生的方式,向请求端返回值。 这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量

采用callable方式

e41f2990e085cdf7504b18a62acdc4c3.png

e7eecc7f5fec9578dc9e03243e25828c.png

可以看到以下结果:浏览器等待了大约5秒后返回结果

打印日志中,Controller在6ms就执行结束

打印日志中,实际的任务执行在一个名称为MvcAsync1的线程中执行,并且在Controller执行完2s后才执行结束我们注意一下日志,上面有一段警告。意思就是没有指定线程池。会导致使用默认的SimpleAsyncTaskExecutor,发现不停的在创建MvcAsync1这个线程。

我就在想,难道没有用线程池?通过阅读WebAsyncManager源码才发现果真如此,WebAsyncManager是Spring MVC管理async processing的中心类。

默认是使用SimpleAsyncTaskExecutor,这个会为每次请求创建一个新的线程private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());

源码如下,如任务指定了executor,就用任务指定的,没有就用默认的SimpleAsyncTaskExecutorAsyncTaskExecutor executor = webAsyncTask.getExecutor();

if (executor != null) {

this.taskExecutor = executor;

}

我们可以配置async 的线程池,不需要为每个任务单独指定

857efd5cc54c00a3879dd078ca5d5d7c.png

577f03ee10ea9fb0e265ba0e51b04879.png

因此可以得到结论:返回Callable对象时,实际工作线程会在后台处理,Controller无需等待工作线程处理完成,但Spring会在工作线程处理完毕后才返回客户端。

它的执行流程是这样的:客户端请求服务

SpringMVC调用Controller,Controller返回一个Callback对象

SpringMVC调用ruquest.startAsync并且将Callback提交到TaskExecutor中去执行

DispatcherServlet以及Filters等从应用服务器线程中结束,但Response仍旧是打开状态,也就是说暂时还不返回给客户端

TaskExecutor调用Callback返回一个结果,SpringMVC将请求发送给应用服务器继续处理

DispatcherServlet再次被调用并且继续处理Callback返回的对象,最终将其返回给客户端

DeferredResult方式

DeferredResult使用方式与Callable类似,但在返回结果上不一样,它返回的时候实际结果可能没有生成,实际的结果可能会在另外的线程里面设置到DeferredResult中去。

该类包含以下日常使用相关的特性:超时配置:通过构造函数可以传入超时时间,单位为毫秒;因为需要等待设置结果后才能继续处理并返回客户端,如果一直等待会导致客户端一直无响应,因此必须有相应的超时机制来避免这个问题;实际上就算不设置这个超时时间,应用服务器或者Spring也会有一些默认的超时机制来处理这个问题。

结果设置:它的结果存储在一个名称为result的属性中;可以通过调用setResult的方法来设置属性;由于这个DeferredResult天生就是使用在多线程环境中的,因此对这个result属性的读写是有加锁的。

DeferredResult流程

接下来将对DeferredResult的处理流程进行说明,并实现一个较为简单的示例。

DeferredResult的处理过程与Callback类似,不一样的地方在于它的结果不是DeferredResult直接返回的,而是由其它线程通过同步的方式设置到该对象中。它的执行过程如下所示:客户端请求服务

SpringMVC调用Controller,Controller返回一个DeferredResult对象

SpringMVC调用ruquest.startAsync

DispatcherServlet以及Filters等从应用服务器线程中结束,但Response仍旧是打开状态,也就是说暂时还不返回给客户端

某些其它线程将结果设置到DeferredResult中,SpringMVC将请求发送给应用服务器继续处理

DispatcherServlet再次被调用并且继续处理DeferredResult中的结果,最终将其返回给客户端

ced97df12f659dfec5905990ea50fe99.png

第一步先访问:http://localhost:8080/testDeferredResult

此时客户端将会一直等待,直到一定时长后会超时

第二步再新开页面访问:http://localhost:8080/setDeferredResult

此时第一个页面会返回结果。Callback和DeferredResult用于设置单个结果。如果有多个结果需要返回给客户端时,可以使用SseEmitter以及ResponseBodyEmitter等;

下面直接看示例,与DeferredResult的示例类似:

078cbc2e7ab5df055ca56d3c4f33340d.png

136c51e00461492e09fd580497ac3cf2.png

第一步访问:http://localhost:8080/testSseEmitter一直等待结果

第二步连续访问:http://localhost:8080/setSseEmitter

第三步访问:http://localhost:8080/completeSseEmitter

只有当第三步执行后,第一步才可以看到结果,第一步的访问才算结束。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值