spring boot--使用异步请求,提高系统的吞吐量

SpringBoot异步接口实现:提高系统的吞吐量 Servlet 3.0之前:每一次Http请求都由一个线程从头到尾处理。Servlet 3.0之后,提供了异步处理请求:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。在springboot应用中,可以有4种方式实现异步接口(至于SseEmitter,不在本文介绍内,之后新写文章介绍):Callable第一中是Servlet层级的,比较原生的方式,本文不对此介绍(一般都不使用它,太麻烦了)。本文着重介绍后面三种方式。 阅读详情

spring boot--使用异步请求,提高系统的吞吐量

牛奋lch Java架构研究室 3天前

前言:

 

在我们的实际生产中,常常会遇到下面的这种情况,某个请求非常耗时(大约5s返回),当大量的访问该请求的时候,再请求其他服务时,会造成没有连接使用的情况,造成这种现象的主要原因是,我们的容器(tomcat)中线程的数量是一定的,例如500个,当这500个线程都用来请求服务的时候,再有请求进来,就没有多余的连接可用了,只能拒绝连接。要是我们在请求耗时服务的时候,能够异步请求(请求到controller中时,则容器线程直接返回,然后使用系统内部的线程来执行耗时的服务,等到服务有返回的时候,再将请求返回给客户端),那么系统的吞吐量就会得到很大程度的提升了。当然,大家可以直接使用Hystrix的资源隔离来实现,今天我们的重点是spring mvc是怎么来实现这种异步请求的。

 

一、使用Callable来实现

 

controller如下:

 

@RestController

public class HelloController {

 

private static final Logger logger = LoggerFactory.getLogger(HelloController.class);

 

@Autowired

private HelloService hello;

 

@GetMapping("/helloworld")

public String helloWorldController() {

return hello.sayHello();

}

 

/**

 * 异步调用restful

 * 当controller返回值是Callable的时候,springmvc就会启动一个线程将Callable交给TaskExecutor去处理

 * 然后DispatcherServlet还有所有的spring拦截器都退出主线程,然后把response保持打开的状态

 * 当Callable执行结束之后,springmvc就会重新启动分配一个request请求,然后DispatcherServlet就重新

 * 调用和处理Callable异步执行的返回结果, 然后返回视图

 * 

 * @return

 */

@GetMapping("/hello")

public Callable<String> helloController() {

logger.info(Thread.currentThread().getName() + " 进入helloController方法");

Callable<String> callable = new Callable<String>() {

 

@Override

public String call() throws Exception {

logger.info(Thread.currentThread().getName() + " 进入call方法");

String say = hello.sayHello();

logger.info(Thread.currentThread().getName() + " 从helloService方法返回");

return say;

}

};

logger.info(Thread.currentThread().getName() + " 从helloController方法返回");

return callable;

}

}

 

 

我们首先来看下上面这两个请求的区别

下面这个是没有使用异步请求的

 

 

我们可以看到,请求从头到尾都只有一个线程,并且整个请求耗费了2s钟的时间。

下面,我们再来看下使用Callable异步请求的结果:

 

 

从上面的结果中,我们可以看出,容器的线程http-nio-8060-exec-1这个线程进入controller之后,就立即返回了,具体的服务调用是通过MvcAsync2这个线程来做的,当服务执行完要返回后,容器会再启一个新的线程http-nio-8060-exec-2来将结果返回给客户端或浏览器,整个过程response都是打开的,当有返回的时候,再从server端推到response中去。

 

二、异步调用的另一种方式

 

 

上面的示例是通过callable来实现的异步调用,其实还可以通过WebAsyncTask,也能实现异步调用,下面看示例:

 

@RestController

public class HelloController {

 

private static final Logger logger = LoggerFactory.getLogger(HelloController.class);

 

@Autowired

private HelloService hello;

 

/**

 * 带超时时间的异步请求 通过WebAsyncTask自定义客户端超时间

 * 

 * @return

 */

@GetMapping("/world")

public WebAsyncTask<String> worldController() {

logger.info(Thread.currentThread().getName() + " 进入helloController方法");

 

// 3s钟没返回,则认为超时

WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, new Callable<String>() {

 

@Override

public String call() throws Exception {

logger.info(Thread.currentThread().getName() + " 进入call方法");

String say = hello.sayHello();

logger.info(Thread.currentThread().getName() + " 从helloService方法返回");

return say;

}

});

logger.info(Thread.currentThread().getName() + " 从helloController方法返回");

 

webAsyncTask.onCompletion(new Runnable() {

 

@Override

public void run() {

logger.info(Thread.currentThread().getName() + " 执行完毕");

}

});

 

webAsyncTask.onTimeout(new Callable<String>() {

 

@Override

public String call() throws Exception {

logger.info(Thread.currentThread().getName() + " onTimeout");

// 超时的时候,直接抛异常,让外层统一处理超时异常

throw new TimeoutException("调用超时");

}

});

return webAsyncTask;

}

 

/**

 * 异步调用,异常处理,详细的处理流程见MyExceptionHandler类

 * 

 * @return

 */

@GetMapping("/exception")

public WebAsyncTask<String> exceptionController() {

logger.info(Thread.currentThread().getName() + " 进入helloController方法");

Callable<String> callable = new Callable<String>() {

 

@Override

public String call() throws Exception {

logger.info(Thread.currentThread().getName() + " 进入call方法");

throw new TimeoutException("调用超时!");

}

};

logger.info(Thread.currentThread().getName() + " 从helloController方法返回");

return new WebAsyncTask<>(20000, callable);

}

 

}

 

运行结果如下:

 

 

这种方式和上面的callable方式最大的区别就是,WebAsyncTask支持超时,并且还提供了两个回调函数,分别是onCompletion和onTimeout,顾名思义,这两个回调函数分别在执行完成和超时的时候回调。

 

 

三、Deferred方式实现异步调用

 

在我们是生产中,往往会遇到这样的情景,controller中调用的方法很多都是和第三方有关的,例如JMS,定时任务,队列等,拿JMS来说,比如controller里面的服务需要从JMS中拿到返回值,才能给客户端返回,而从JMS拿值这个过程也是异步的,这个时候,我们就可以通过Deferred来实现整个的异步调用。

 

首先,我们来模拟一个长时间调用的任务,代码如下:

 

@Component

public class LongTimeTask {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Async

public void execute(DeferredResult<String> deferred){

logger.info(Thread.currentThread().getName() + "进入 taskService 的 execute方法");

try {

// 模拟长时间任务调用,睡眠2s

TimeUnit.SECONDS.sleep(2);

// 2s后给Deferred发送成功消息,告诉Deferred,我这边已经处理完了,可以返回给客户端了

deferred.setResult("world");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

 

接着,我们就来实现异步调用,controller如下:

 

@RestController

public class AsyncDeferredController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final LongTimeTask taskService;

    

    @Autowired

    public AsyncDeferredController(LongTimeTask taskService) {

        this.taskService = taskService;

    }

    

    @GetMapping("/deferred")

    public DeferredResult<String> executeSlowTask() {

        logger.info(Thread.currentThread().getName() + "进入executeSlowTask方法");

        DeferredResult<String> deferredResult = new DeferredResult<>();

        // 调用长时间执行任务

        taskService.execute(deferredResult);

        // 当长时间任务中使用deferred.setResult("world");这个方法时,会从长时间任务中返回,继续controller里面的流程

        logger.info(Thread.currentThread().getName() + "从executeSlowTask方法返回");

        // 超时的回调方法

        deferredResult.onTimeout(new Runnable(){

 

@Override

public void run() {

logger.info(Thread.currentThread().getName() + " onTimeout");

// 返回超时信息

deferredResult.setErrorResult("time out!");

}

});

        

        // 处理完成的回调方法,无论是超时还是处理成功,都会进入这个回调方法

        deferredResult.onCompletion(new Runnable(){

 

@Override

public void run() {

logger.info(Thread.currentThread().getName() + " onCompletion");

}

});

        

        return deferredResult;

    }

}

 

执行结果如下:

 

 

从上面的执行结果不难看出,容器线程会立刻返回,应用程序使用线程池里面的cTaskExecutor-1线程来完成长时间任务的调用,当调用完成后,容器又启了一个连接线程,来返回最终的执行结果。

 

这种异步调用,在容器线程资源非常宝贵的时候,能够大大的提高整个系统的吞吐量。

 

ps:异步调用可以使用AsyncHandlerInterceptor进行拦截,使用示例如下:

 

@Component

public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {

 

private static final Logger logger = LoggerFactory.getLogger(MyAsyncHandlerInterceptor.class);

 

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

throws Exception {

return true;

}

 

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

ModelAndView modelAndView) throws Exception {

// HandlerMethod handlerMethod = (HandlerMethod) handler;

logger.info(Thread.currentThread().getName()+ "服务调用完成,返回结果给客户端");

}

 

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)

throws Exception {

if(null != ex){

System.out.println("发生异常:"+ex.getMessage());

}

}

 

@Override

public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)

throws Exception {

 

// 拦截之后,重新写回数据,将原来的hello world换成如下字符串

String resp = "my name is chhliu!";

response.setContentLength(resp.length());

response.getOutputStream().write(resp.getBytes());

 

logger.info(Thread.currentThread().getName() + " 进入afterConcurrentHandlingStarted方法");

}

 

}

 

 

本篇文章的主题是异步调用,有兴趣的可以了解下本公众号 的其他文章。

--------------------- 

加速度计解算姿态角 姿态角(欧拉角) 欧拉角主要用于描述刚体在固定三维参考坐标系下由于绕三轴任意旋转而形成的姿态角,具体描述参看欧拉角说明 图中xyz为固定参考系坐标,XYZ为刚体自有坐标系 (假定右手定则,即大拇指指向某一轴正方向,握拳是其余手指的转动方向为姿态角变化的正方向),则定义 绕固定坐标系的z轴旋转:航向角yaw, 转动 y 角度 绕固定坐标系的y轴旋转:俯仰角pitch,转动p 角度 绕固定坐标系的x轴旋转:横滚角roll, 转动r 角度 可以看出欧拉角或者姿态角是相对各个轴的角度偏移,再假定旋转的顺规为Z 阅读详情

相关推荐

STM32F407与AD7766实战:如何用SPI+DMA实现高速ADC数据采集(附完整代码)

本文详细介绍了如何利用STM32F407的SPI接口与DMA控制器,高效稳定地驱动24位高精度ADC芯片AD7766,实现高速连续数据采集。通过对比传统中断方式的瓶颈,阐述了SPI+DMA方案如何大幅降低CPU负载,并提供了从硬件设计、CubeMX配置到核心驱动代码的完整实战指南,帮助工程师解决高速ADC数据采集中的性能难题。

weixin_29281941的博客 190

[Spring] DefferedResult 和 WebAsyncTask

Spring MVC 异步, Spring Boot, DefferedResult, WebAsyncTask, Callable, CompletableFuture

buildcourage的博客 2580

【测试】echo发送和接收TCP/UDP数据包|shell 发送TCP/UDP数据包

目录 通过/dev/tcp、/dev/udp shell给tcp或udp服务发送16进制报文指令 通过/dev/tcp、/dev/udp (https://blog.youkuaiyun.com/u010039418/article/details/86251470) 通过/dev/tcp、/dev/udp可以直接在shell脚本中发起tcp、udp连接,方便又高效,平时用于测试啥的还是挺方便的。 直接发送 格式 /dev/udp/ip/port 比如要向本地10001端口发送数据,可以使用 ec.

我的笔记本 1万+

SpringBoot项目使用多线程对项目进行优化

SpringBoot项目使用多线程对项目进行优化

Kblzxj的博客 797

C++总结笔记

/ ++操作 这是因为定义的数组,申请的是连续的内存空间,当ptr++(ptr是int类型的指针变量),地址空间向后偏移是以int类型的大小为单位进行偏移的,相当于int类型数组元素的向后偏移。使用场景:它是最通用的转换形式,用于将一种类型转换为另一种类型,例如基本数据类型的转换(如 float 转 int),指向基类的指针或引用转为指向派生类的指针或引用等。在派生类被调用时会调用派生类中的函数,不会调用基类中的被重写的函数(基类中的函数需要加上virtual才可以,即基类中的函数是虚函数)。

weixin_50890461的博客 5235

004.PADS VX2.4常用快捷键及无模命令

PADS系列文章

qq_36492361的博客 4967

Spring MVC一个请求的处理流程

先大致分析一下启动过程,然后详细分析请求的处理过程。 ​ 因为在web.xml文件中给Spring MVC的Servlet配置了load-on-startup,所以程序启动时会初始化Spring MVC,在HttpServletBean中将配置的contextConfigLocation属性设置到Servlet中,然后在FrameworkServlet中创建了WebApplicationConte...

执者失之的专栏 749

图神经网络实战(2)——图论基础

图论 (Graph theory) 是数学的一个基本分支,涉及对图研究。图是复杂数据结构的可视化表示,有助于理解不同实体之间的关系。图论提供了大量建模和分析现实问题的工具,如交通系统、社交网络和互联网等。在本节中,将介绍图论的基本原理,主要涉及三个方面:图属性、图概念和图算法。

盼小辉丶的博客 1万+

为什么spring Boot是一个线程处理一个web请求而不是一个线程处理一次tcp连接?

先不谈web-reactive,通过分析Spring MVC和Tomcat的交互,来浅析一下Spring与servlet容器交互的原理——Spring MVC基于Java EE的Servlet API,Servlet API定义了Servlet容器和具体的servlet代码交互的约定,Spring MVC通过注册一个名为DispatcherServlet的servlet到servlet容器中处理请求,并把实际工作交给Spring提供的组件bean执行。(以下是简介,并不代表Tomcat也是这样做的)

qq_43842093的博客 6152

【即插即用模块】注意力篇 | TGRS 2025 | CDFIM:通道+空间多尺度注意力,双模态特征融合增强,性能翻倍!

C2DFF-Net提出了一种轻量级跨模态特征融合模块CDFIM,通过模态差异感知和多尺度深度滤波增强双模态互补信息。该模块采用通道-空间注意力协同机制,以极低计算成本(仅增加0.26M参数)提升多模态目标检测性能,在三个遥感数据集上平均提升mAP约1.7%。适用于无人机等边缘设备的实时检测任务,为跨模态融合提供高效解决方案。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 626

【大数据Java基础-Java基础01】环境搭建(一)Sublime Text3搭建Java编译环境(Windows系统)

1. 首先配置好Java环境变量 我的jdk版本是1.8.0_191,存放目录是C:\Program Files\Java,因此添加以下环境变量 (1)系统变量→新建 JAVA_HOME 变量,变量值为: C:\Program Files\Java\jdk1.8.0_191 (2)系统变量→找到 Path 变量→编辑,末尾加入: ;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin 如果Path变量末尾本身有“;”,则上述路径前面则不需要加“;” (3)系统变量→新建 CL

微信搜:import_bigdata,大数据领域硬核原创作者 556

异步请求提高吞吐量

Servlet 3.0之前:每一次Http请求都由一个线程从头到尾处理。Servlet 3.0之后,提供了异步处理请求:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。在springboot应用中,可以有4种方式实现异步接口(至于SseEmitter,不在本文介绍内,之后新写文章介绍):Callable第一中是Servlet层级的,比较原生的方式,本文不对此介绍(一般都不使用它,太麻烦了)。本文着重介绍后面三种方式。

一名很普通的互联网打工人 861

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Spring Boot 使用WebAsyncTask异步返回结果

在Spring Boot中(Spring MVC)下请求默认都是同步的,一个请求过去到结束都是由一个线程负责的,很多时候为了能够提高吞吐量,需要将一些操作异步化,除了一些耗时的业务逻辑可以异步化,我们的查询接口也是可以做到异步执行。 一个请求到服务上,是用的web容器的线程接收的,比如线程http-nio-8084-exec-1 我们可以使用WebAsyncTask将这个请求分发给一个新的线程...

u010889990的专栏 3006

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

同步 vs 异步性能差10倍!SpringBoot 高吞吐接口实现终极方案

Servlet 3.0之后,提供了异步处理请求:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。在springboot应用中,可以有4种方式实现异步接口(至于SseEmitter,不在本文介绍内,之后新写文章介绍):Callable第一中是Servlet层级的,比较原生的方式,本文不对此介绍(一般都不使用它,太麻烦了)。本文着重介绍后面三种方式。特别说明:服务端的异步或同步对于客户端而言是不可见的。不会因为服务端使用了异步,接口的结果就和同步不一样了。

m0_50180963的博客 1006

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

SpringBoot异步接口怎么写才对?99%的人都用错了

Servlet 3.0之前:每一次Http请求都由一个线程从头到尾处理。Servlet 3.0之后,提供了异步处理请求:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。在springboot应用中,可以有4种方式实现异步接口(至于ResponseBodyEmitter、SseEmitter、StreamingResponseBody,不在本文介绍内,之后新写文章介绍):Callable。

jcc4261的博客 1065

SpringBoot接口卡成狗?

每一次Http请求都由一个线程从头到尾处理。可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。在springboot应用中,可以有4种方式实现异步接口(至于SseEmitter,不在本文介绍内):Callable第一中是Servlet层级的,比较原生的方式,本文不对此介绍(一般都不使用它,太麻烦了)。本文着重介绍后面三种方式。特别说明:服务端的异步或同步对于客户端而言是不可见的。不会因为服务端使用了异步,接口的结果就和同步不一样了。

m0_73735578的博客 857
上一篇: String、StringBuffer与StringBuilder之间区别
下一篇: hashcode详解
王树民
博客等级 码龄10年 912粉丝 560原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值