首页 > 其他分享 >SpringBoot 如何利用异步接口,提高系统吞吐量

SpringBoot 如何利用异步接口,提高系统吞吐量

时间:2024-09-17 22:51:15浏览次数:10  
标签:异步 SpringBoot 接口 Callable 线程 DeferredResult 请求

一、前言

Servlet 3.0之前:每一次Http请求都由一个线程从头到尾处理。Servlet 3.0之后,提供了异步处理请求:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。

在springboot应用中,可以有4种方式实现异步接口:

  • AsyncContext
  • Callable
  • WebAsyncTask
  • DeferredResult

第一中AsyncContext是Servlet层级的,比较原生的方式,本文不对此介绍(一般都不使用它,太麻烦了)。本文着重介绍后面三种方式。

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

二、基于Callable实现

Controller中,返回一个java.util.concurrent.Callable包装的任何值,都表示该接口是一个异步接口:

package com.example.dataproject.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Callable;

/**
 * @author qx
 * @date 2024/9/14
 * @des
 */
@RestController
public class TestController {

    @GetMapping("/testCallable")
    public Callable<String> testCallable() {
        return new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000);
                return "test callable";
            }
        };
    }

}

运行结果:

SpringBoot 如何利用异步接口,提高系统吞吐量_Callable

服务器端的异步处理对客户端来说是不可见的。例如,上述接口,最终返回的客户端的是一个String,和同步接口中,直接返回String的效果是一样的。

allable 处理过程如下:

控制器返回一个 Callable 。

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

Callable默认使用SimpleAsyncTaskExecutor类来执行,这个类非常简单而且没有重用线程。在实践中,需要使用AsyncTaskExecutor类来对线程进行配置。

三、基于WebAsyncTask实现

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

@GetMapping("/webAsyncTask")
    public WebAsyncTask<String> testWebAsyncTask() {
        log.info("webAsyncTask 开始请求");
        WebAsyncTask<String> result = new WebAsyncTask<>(2000, () -> {
            return "webAsyncTask success";
        });
        result.onTimeout(() -> {
            System.out.println("请求超时");
            return "timeout callback";
        });
        result.onCompletion(() -> {
            log.info("webAsyncTask 请求完成");
        });
        return result;
    }

这里额外提一下,WebAsyncTask可以配置一个超时时间,这里配置的超时时间比全局配置的超时时间优先级都高(会覆盖全局配置的超时时间)。

运行结果:

SpringBoot 如何利用异步接口,提高系统吞吐量_Callable_02

SpringBoot 如何利用异步接口,提高系统吞吐量_工作线程_03

四、基于DeferredResult实现

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

//定义一个全局的变量,用来存储DeferredResult对象
    private Map<String, DeferredResult<String>> deferredResultMap = new ConcurrentHashMap<>();

    @GetMapping("/testtDeferredResult")
    public DeferredResult<String> deferredResult() {
        DeferredResult<String> deferredResult = new DeferredResult<>();
        deferredResultMap.put("test", deferredResult);
        return deferredResult;
    }

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

我们重新写一个接口模拟

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

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

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

DeferredResult 处理过程如下:

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

五、线程池

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

这里提到一个其他线程,那么这个其他线程我们一般都不适用默认的,都是根据自身情况提供一个线程池供异步请求使用:

package com.example.dataproject.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @author qx
 * @date 2024/8/1
 * @des 线程池配置类
 */
@Configuration
public class ExecutorConfig {


    @Bean("customExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(20);
        //设置队列大小
        executor.setQueueCapacity(20);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(30);
        // 设置线程名前缀+分组名称
        executor.setThreadNamePrefix("customThread-");
        executor.setThreadGroupName("customThreadGroup");
        // 所有任务结束后关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 初始化
        executor.initialize();
        return executor;
    }
}

什么时候使用异步请求?

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

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

由于异步请求增加了更多的线程切换(同步请求是同一个工作线程一直处理),所以理论上会增加接口的耗时。但,这个耗时很短很短。

标签:异步,SpringBoot,接口,Callable,线程,DeferredResult,请求
From: https://blog.51cto.com/u_13312531/12039030

相关文章

  • Java基于SpringBoot的付费自习室管理系统+Vue[毕业设计]
    文末获取资源,收藏关注不迷路文章目录项目介绍技术介绍项目界面关键代码目录项目介绍费自习室管理系统采用B/S架构,数据库是MySQL。网站的搭建与开发采用了先进的java进行编写,使用了springboot框架。该系统从两个对象:由管理员和用户来对系统进行设计构建。主要功能......
  • 前端大模型入门:掌握langchain的核心Runnable接口(一)
    在构建复杂的对话式AI应用程序时,Langchain是一个绕不开的工具,它帮助开发人员轻松地处理各种语言模型的集成与管理。v0.3是该框架的一个重要版本,它进一步优化了功能,提升了在JavaScript/TypeScript环境下的易用性。本文将介绍Langchainv0.3的核心功能,并特别着重于其Runnab......
  • Java基于SpringBoot的个人健康管理网站+Vue[毕业设计]
    文末获取资源,收藏关注不迷路文章目录项目介绍技术介绍项目界面关键代码目录项目介绍系统根据现有的管理模块进行开发和扩展,采用面向对象的开发的思想和结构化的开发方法对个人健康管理的现状进行系统调查。采用结构化的分析设计,该方法要求结合一定的图表,在模块化......
  • 后台上传大文件时提示上传接口错误
    后台上传大文件时提示上传接口错误,通常有以下几个可能的原因:文件大小限制:服务器或框架对上传文件的大小有限制。内存限制:PHP或其他后端语言的内存限制。超时限制:上传大文件时可能会超过默认的执行时间限制。存储空间不足:服务器上的存储空间不足。网络问题:网络不稳定或中断......
  • 前后端分离Vue3+SpringBoot学生宿舍水电信息管理系统
    目录功能和开发技术介绍具体实现截图开发核心技术介绍:技术创新点vue3和vue2的区别:核心代码部分展示非功能需求分析系统开发流程系统运行步骤软件测试源码获取功能和开发技术介绍本课题拟采用主流的MVC架构、开发工具idea、java语言编程、MySQL数据库技术、Vue.js技......
  • 前后端分离Vue3+SpringBoot银行信用卡额度管理系统的设计与实现
    目录功能和开发技术介绍具体实现截图开发核心技术介绍:技术创新点vue3和vue2的区别:核心代码部分展示非功能需求分析系统开发流程系统运行步骤软件测试源码获取功能和开发技术介绍本课题拟采用主流的MVC架构、开发工具idea、java语言编程、MySQL数据库技术、Vue.js技......
  • 基于java的城市公交查询系统Vue3+SpringBoot
    目录功能和开发技术介绍具体实现截图开发核心技术介绍:技术创新点vue3和vue2的区别:核心代码部分展示非功能需求分析系统开发流程系统运行步骤软件测试源码获取功能和开发技术介绍本课题拟采用主流的MVC架构、开发工具idea、java语言编程、MySQL数据库技术、Vue.js技......
  • Vue3+SpringBoot高校毕业生实习及就业去向信息管理系统
    目录功能和开发技术介绍具体实现截图开发核心技术介绍:技术创新点vue3和vue2的区别:核心代码部分展示非功能需求分析系统开发流程系统运行步骤软件测试源码获取功能和开发技术介绍本课题拟采用主流的MVC架构、开发工具idea、java语言编程、MySQL数据库技术、Vue.js技......
  • 【F156】基于Springboot+vue实现的驾校预约学习系统
    主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。收藏点赞不迷路,关注作者有好处项目描述一切系统都要遵循系统设计的最基本全过程,系统也是如此。它还要通过市场调查、需求分析报告、汇总设计、详尽设计......
  • PageHelper在SpringBoot中的使用和原理分析
    PageHelper在SpringBoot中的使用和原理分析在SpringBoot项目中使用Mybatis的PageHelper分页插件进行分页查询1、导入相关依赖<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></depe......