当前位置:首页 > 科技  > 软件

SpringBoot异步接口实现:提高系统的吞吐量

来源: 责编: 时间:2024-09-10 09:46:03 29观看
导读前言Servlet 3.0之前:每一次Http请求都由一个线程从头到尾处理。Servlet 3.0之后,提供了异步处理请求:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。在springboot应用中,可以有4种方式实

前言

Servlet 3.0之前:每一次Http请求都由一个线程从头到尾处理。ZJ428资讯网——每日最新资讯28at.com

Servlet 3.0之后,提供了异步处理请求:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。ZJ428资讯网——每日最新资讯28at.com

在springboot应用中,可以有4种方式实现异步接口(至于ResponseBodyEmitter、SseEmitter、StreamingResponseBody,不在本文介绍内,之后新写文章介绍):ZJ428资讯网——每日最新资讯28at.com

  • AsyncContext
  • Callable
  • WebAsyncTask
  • DeferredResult

第一中AsyncContext是Servlet层级的,比较原生的方式,本文不对此介绍(一般都不使用它,太麻烦了)。本文着重介绍后面三种方式。ZJ428资讯网——每日最新资讯28at.com

ZJ428资讯网——每日最新资讯28at.com

特别说明:服务端的异步或同步对于客户端而言是不可见的。不会因为服务端使用了异步,接口的结果就和同步不一样了。另外,对于单个请求而言,使用异步接口会导致响应时间比同步大,但不特别明显。具体后文分析。ZJ428资讯网——每日最新资讯28at.com

基于Callable实现

Controller中,返回一个java.util.concurrent.Callable包装的任何值,都表示该接口是一个异步接口:ZJ428资讯网——每日最新资讯28at.com

@GetMapping("/testCallAble")public Callable<String> testCallAble() {    return () -> {        Thread.sleep(40000);        return "hello";    };}

服务器端的异步处理对客户端来说是不可见的。例如,上述接口,最终返回的客户端的是一个String,和同步接口中,直接返回String的效果是一样的。ZJ428资讯网——每日最新资讯28at.com

Callable 处理过程如下:ZJ428资讯网——每日最新资讯28at.com

控制器返回一个 Callable 。ZJ428资讯网——每日最新资讯28at.com

  • Spring MVC 调用 request.startAsync() 并将 Callable 提交给 AsyncTaskExecutor 以在单独的线程中进行处理。
  • 同时, DispatcherServlet 和所有过滤器退出 Servlet 容器线程,但response保持打开状态。
  • 最终 Callable 产生结果,Spring MVC将请求分派回Servlet容器以完成处理。
  • 再次调用 DispatcherServlet ,并使用 Callable 异步生成的返回值继续处理。

Callable默认使用SimpleAsyncTaskExecutor类来执行,这个类非常简单而且没有重用线程。在实践中,需要使用AsyncTaskExecutor类来对线程进行配置。ZJ428资讯网——每日最新资讯28at.com

基于WebAsyncTask实现

Spring提供的WebAsyncTask是对Callable的包装,提供了更强大的功能,比如:处理超时回调、错误回调、完成回调等。本质上,和Callable区别不大,但是由于它额外封装了一些事件的回调,所有,通常都使用WebAsyncTask而不是Callable:ZJ428资讯网——每日最新资讯28at.com

@GetMapping("/webAsyncTask")public WebAsyncTask<String> webAsyncTask() {    WebAsyncTask<String> result = new WebAsyncTask<>(30003, () -> {        return "success";    });    result.onTimeout(() -> {        log.info("timeout callback");        return "timeout callback";    });    result.onCompletion(() -> log.info("finish callback"));    return result;}

这里额外提一下,WebAsyncTask可以配置一个超时时间,这里配置的超时时间比全局配置的超时时间优先级都高(会覆盖全局配置的超时时间)。ZJ428资讯网——每日最新资讯28at.com

基于DeferredResult实现

DeferredResult使用方式与Callable类似,但在返回结果时不一样,它返回的时实际结果可能没有生成,实际的结果可能会在另外的线程里面设置到DeferredResult中去。ZJ428资讯网——每日最新资讯28at.com

//定义一个全局的变量,用来存储DeferredResult对象private Map<String, DeferredResult<String>> deferredResultMap = new ConcurrentHashMap<>();@GetMapping("/testDeferredResult")public DeferredResult<String> testDeferredResult(){    DeferredResult<String> deferredResult = new DeferredResult<>();    deferredResultMap.put("test", deferredResult);    return deferredResult;}

如果调用以上接口,会发现客户端的请求一直是在pending状态——等待后端响应。这里,我简单的将该接口返回的DeferredResult对象存放在了一个Map集合中,实际应用中可以设计一个对象管理器来统一管理这些个对象。ZJ428资讯网——每日最新资讯28at.com

ZJ428资讯网——每日最新资讯28at.com

注意:要考虑定时轮询(或其他方式)这些对象,将已经处理过或无效的DeferredResult对象清理掉(DeferredResult.isSetOrExpired方法可以判断是否还有效),避免内存泄露。ZJ428资讯网——每日最新资讯28at.com

这里我又写了一个接口,模拟:ZJ428资讯网——每日最新资讯28at.com

@GetMapping("/testSetDeferredResult")public String testSetDeferredResult() throws InterruptedException {    DeferredResult<String> deferredResult = deferredResultMap.get("test");    boolean flag = deferredResult.setResult("testSetDeferredResult");    if(!flag){        log.info("结果已经被处理,此次操作无效");    }    return "ok";}

其他线程修改DeferredResult的值:首先是从之前存放DeferredResult的map中拿到DeferredResult的值,然后设置它的返回值。当执行deferredResult.setResult之后,可以看到之前pending状态的接口完成了响应,得到的结果,就是这里设置的值。ZJ428资讯网——每日最新资讯28at.com

这里也额外说下:在返回DeferredResult时也可以设置超时时间,这个时间的优先级也是大于全局设置的。另外,判断DeferredResult是否有效,只是一个简单的判断,实际中判断有效的并不一定是有效的(比如:客户端取消了请求,服务端是不知道的),但是一般判断为无效的,那肯定是无效了。ZJ428资讯网——每日最新资讯28at.com

DeferredResult 处理过程如下:ZJ428资讯网——每日最新资讯28at.com

  • 控制器返回一个 DeferredResult 并将其保存在可以访问的内存队列或列表中。
  • Spring MVC 调用 request.startAsync() 。
  • 同时,DispatcherServlet 和所有配置的过滤器退出请求处理线程,但响应保持打开状态。
  • 应用程序从某个线程设置 DeferredResult ,Spring MVC 将请求分派回 Servlet 容器。
  • 再次调用 DispatcherServlet ,并使用异步生成的返回值继续处理。

提供一个线程池

异步请求,不会一直占用请求的主线程(tomcat容器中处理请求的线程),而是通过一个其他的线程来处理异步任务。也正是如此,在相同的最大请求数配置下,异步请求由于迅速的释放了主线程,所以才能提高吞吐量。ZJ428资讯网——每日最新资讯28at.com

这里提到一个其他线程,那么这个其他线程我们一般都不适用默认的,都是根据自身情况提供一个线程池供异步请求使用:(我给的参数都是测试用的,实际中不可照搬)。ZJ428资讯网——每日最新资讯28at.com

@Bean("mvcAsyncTaskExecutor")public AsyncTaskExecutor asyncTaskExecutor() {    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();    // 线程池维护线程的最少数量    // asyncServiceExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors() + 1);    executor.setCorePoolSize(5);    // 线程池维护线程的最大数量    executor.setMaxPoolSize(10);    // 线程池所使用的缓冲队列    executor.setQueueCapacity(10);    //   asyncServiceExecutor.prefersShortLivedTasks();    executor.setThreadNamePrefix("fyk-mvcAsyncTask-Thread-");    asyncServiceExecutor.setBeanName("TaskId" + taskId);    //  asyncServiceExecutor.setKeepAliveSeconds(20);    //调用者执行    //   asyncServiceExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());    // 线程全部结束才关闭线程池    executor.setWaitForTasksToCompleteOnShutdown(true);    // 如果超过60s还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住    executor.setAwaitTerminationSeconds(30);    executor.initialize();    return executor;}

把这个线程池配置设置到异步请求配置中:ZJ428资讯网——每日最新资讯28at.com

@Configurationpublic class FykWebMvcConfigurer implements WebMvcConfigurer {    @Autowired    @Qualifier("mvcAsyncTaskExecutor")    private AsyncTaskExecutor asyncTaskExecutor;    @Override    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {        //异步操作的超时时间,值为0或者更小,表示永不超时        configurer.setDefaultTimeout(60001);        configurer.setTaskExecutor(asyncTaskExecutor);    }}

什么时候使用异步请求

异步请求能提高吞吐量,这个是建立在相同配置(这里的配置指的是:最大连接数、最大工作线程数)的情况下。因此并不是说任何接口都可以使用异步请求。比如:一个请求是进行大量的计算(总之就是在处理这个请求的业务方法时CPU是没有休息的),这种情况使用异步请求就没有多大意义了,因为这时的异步请求只是把一个任务从tomcat的工作线程搬到了另一个线程罢了。ZJ428资讯网——每日最新资讯28at.com

直接调大最大工作线程数配置也能到达要求。所以,真正使用异步请求的场景应该是该请求的业务代码中,大量的时间CPU是休息的(比如:在业务代码中请求其他系统的接口,在其他系统响应之前,CPU是阻塞等待的),这个时候使用异步请求,就可以释放tomcat的工作线程,让释放的工作线程可以处理其他的请求,从而提高吞吐量。ZJ428资讯网——每日最新资讯28at.com

由于异步请求增加了更多的线程切换(同步请求是同一个工作线程一直处理),所以理论上会增加接口的耗时。但,这个耗时很短很短。ZJ428资讯网——每日最新资讯28at.com

本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-112714-0.htmlSpringBoot异步接口实现:提高系统的吞吐量

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 时间序列结构变化分析:Python实现时间序列变化点检测

下一篇: flat() 和 flatMap() 有什么区别?

标签:
  • 热门焦点
  • MIX Fold3包装盒泄露 新机本月登场

    MIX Fold3包装盒泄露 新机本月登场

    小米的全新折叠屏旗舰MIX Fold3将于本月发布,近日该机的真机包装盒在网上泄露。从图上来看,新的MIX Fold3包装盒在外观设计方面延续了之前的方案,变化不大,这也是目前小米旗舰
  • 三言两语说透设计模式的艺术-简单工厂模式

    三言两语说透设计模式的艺术-简单工厂模式

    一、写在前面工厂模式是最常见的一种创建型设计模式,通常说的工厂模式指的是工厂方法模式,是使用频率最高的工厂模式。简单工厂模式又称为静态工厂方法模式,不属于GoF 23种设计
  • Rust中的高吞吐量流处理

    Rust中的高吞吐量流处理

    作者 | Noz编译 | 王瑞平本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库,还使用这些库实现了一个流处理程序
  • 服务存储设计模式:Cache-Aside模式

    服务存储设计模式:Cache-Aside模式

    Cache-Aside模式一种常用的缓存方式,通常是把数据从主存储加载到KV缓存中,加速后续的访问。在存在重复度的场景,Cache-Aside可以提升服务性能,降低底层存储的压力,缺点是缓存和底
  • K8S | Service服务发现

    K8S | Service服务发现

    一、背景在微服务架构中,这里以开发环境「Dev」为基础来描述,在K8S集群中通常会开放:路由网关、注册中心、配置中心等相关服务,可以被集群外部访问;图片对于测试「Tes」环境或者
  • 梁柱接棒两年,腾讯音乐闯出新路子

    梁柱接棒两年,腾讯音乐闯出新路子

    文丨田静 出品丨牛刀财经(niudaocaijing)7月5日,企鹅FM发布官方公告称由于业务调整,将于9月6日正式停止运营,这意味着腾讯音乐长音频业务走向消亡。腾讯在长音频领域还在摸索。为
  • 华为举行春季智慧办公新品发布会 首次推出电子墨水屏平板

    华为举行春季智慧办公新品发布会 首次推出电子墨水屏平板

    北京时间2月27日晚,华为在巴塞罗那举行春季智慧办公新品发布会,在海外市场推出之前已经在中国市场上市的笔记本、平板、激光打印机等办公产品,并首次推出搭载
  • 亲历马斯克血洗Twitter,硅谷的苦日子在后头

    亲历马斯克血洗Twitter,硅谷的苦日子在后头

    文/刘哲铭  编辑/李薇  马斯克再次挥下裁员大刀。  美国时间11月14日,Twitter约4400名外包员工遭解雇,此次被解雇的员工的主要工作为内容审核等。此前,T
  • Meta盲目扩张致超万人被裁,重金押注元宇宙而前景未明

    Meta盲目扩张致超万人被裁,重金押注元宇宙而前景未明

    图片来源:图虫创意日前,Meta创始人兼CEO 马克&middot;扎克伯发布公开信,宣布Meta计划裁员超11000人,占其员工总数13%。他公开承认了自己的预判失误:&ldquo;不仅
Top
Baidu
map