前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何提升系统的吞吐量?和你理解的不一样的异步场景

如何提升系统的吞吐量?和你理解的不一样的异步场景

作者头像
lyb-geek
发布2021-09-23 16:29:49
8210
发布2021-09-23 16:29:49
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路
先看下图,一个请求做注册用户(A业务)耗时1秒,在由A调用B业务(发送邮件)耗时3秒,以及C业务(发送短信)耗时2秒

我们常规的完成这个业务,一般这样操作,伪代码如下

代码语言:javascript
复制
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就会被再次调用并且处理,以异步产生的方式,向请求端返回值。这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量

  • 1、采用callable方式

可以看到以下结果:

  • 浏览器等待了大约5秒后返回结果
  • 打印日志中,Controller在6ms就执行结束
  • 打印日志中,实际的任务执行在一个名称为MvcAsync1的线程中执行,并且在Controller执行完2s后才执行结束

我们注意一下日志,上面有一段警告。意思就是没有指定线程池。会导致使用默认的SimpleAsyncTaskExecutor发现不停的在创建MvcAsync1这个线程,我就在想,难道没有用线程池?通过阅读WebAsyncManager源码才发现果真如此,WebAsyncManager是Spring MVC管理async processing的中心类。

默认是使用SimpleAsyncTaskExecutor,这个会为每次请求创建一个新的线程

代码语言:javascript
复制
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());

如果说任务指定了executor,就用任务指定的,没有就用默认的SimpleAsyncTaskExecutor

代码语言:javascript
复制
AsyncTaskExecutor executor = webAsyncTask.getExecutor();
if (executor != null) { this.taskExecutor = executor; }

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

因此可以得到结论:

返回Callable对象时,实际工作线程会在后台处理,Controller无需等待工作线程处理完成,但Spring会在工作线程处理完毕后才返回客户端。

它的执行流程是这样的:

  • 客户端请求服务
  • SpringMVC调用Controller,Controller返回一个Callback对象
  • SpringMVC调用ruquest.startAsync并且将Callback提交到TaskExecutor中去执行
  • DispatcherServlet以及Filters等从应用服务器线程中结束,但Response仍旧是打开状态,也就是说暂时还不返回给客户端
  • TaskExecutor调用Callback返回一个结果,SpringMVC将请求发送给应用服务器继续处理
  • DispatcherServlet再次被调用并且继续处理Callback返回的对象,最终将其返回给客户端

2、DeferredResult方式

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

该类包含以下日常使用相关的特性:

  • 超时配置:通过构造函数可以传入超时时间,单位为毫秒;因为需要等待设置结果后才能继续处理并返回客户端,如果一直等待会导致客户端一直无响应,因此必须有相应的超时机制来避免这个问题;实际上就算不设置这个超时时间,应用服务器或者Spring也会有一些默认的超时机制来处理这个问题。
  • 结果设置:它的结果存储在一个名称为result的属性中;可以通过调用setResult的方法来设置属性;由于这个DeferredResult天生就是使用在多线程环境中的,因此对这个result属性的读写是有加锁的。

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

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

  • 客户端请求服务
  • SpringMVC调用Controller,Controller返回一个DeferredResult对象
  • SpringMVC调用ruquest.startAsync
  • DispatcherServlet以及Filters等从应用服务器线程中结束,但Response仍旧是打开状态,也就是说暂时还不返回给客户端
  • 某些其它线程将结果设置到DeferredResult中,SpringMVC将请求发送给应用服务器继续处理
  • DispatcherServlet再次被调用并且继续处理DeferredResult中的结果,最终将其返回给客户端

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

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

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

此时第一个页面会返回结果。

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

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

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

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

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

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

作者:老顾聊技术 来源:https://www.toutiao.com/a6664747403435835912/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-09-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linyb极客之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档