7 种提升 Spring Boot 吞吐量神技

目录

二、增加内嵌Tomcat的最大连接数

三、使用@ComponentScan()定位扫包比@SpringBootApplication扫包更快

四、默认tomcat容器改为Undertow(Jboss下的服务器,Tomcat吞吐量5000,Undertow吞吐量8000)

五、使用 BufferedWriter 进行缓冲

六、Deferred方式实现异步调用

七、异步调用可以使用AsyncHandlerInterceptor进行拦截


一、异步执行

实现方式二种:

  1. 使用异步注解@aysnc、启动类:添加@EnableAsync注解

  2. JDK 8本身有一个非常好用的Future类——CompletableFuture

@AllArgsConstructor
public class AskThread implements Runnable{
private CompletableFuture<Integer> re = null;
public void run() {
int myRe = 0;
try {
myRe = re.get() * re.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(myRe);
}
public static void main(String[] args) throws InterruptedException {
final CompletableFuture<Integer> future = new CompletableFuture<>();
new Thread(new AskThread(future)).start();
//模拟长时间的计算过程
Thread.sleep(1000);
//告知完成结果
future.complete(60);
}
}

在该示例中,启动一个线程,此时AskThread对象还没有拿到它需要的数据,执行到 myRe = re.get() * re.get()会阻塞。我们用休眠1秒来模拟一个长时间的计算过程,并将计算结果告诉future执行结果,AskThread线程将会继续执行。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

public class Calc {
public static Integer calc(Integer para) {
try {
//模拟一个长时间的执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return para * para;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> calc(50))
.thenApply((i) -> Integer.toString(i))
.thenApply((str) -> """ + str + """)
.thenAccept(System.out::println);
future.get();
}
}

CompletableFuture.supplyAsync方法构造一个CompletableFuture实例,在supplyAsync()方法中,它会在一个新线程中,执行传入的参数。在这里它会执行calc()方法,这个方法可能是比较慢的,但这并不影响CompletableFuture实例的构造速度,supplyAsync()会立即返回。

而返回的CompletableFuture实例就可以作为这次调用的契约,在将来任何场合,用于获得最终的计算结果。最近整理了一份最新的面试资料,里面收录了2021年各个大厂的面试题,打算跳槽的小伙伴不要错过,点击领取吧!

supplyAsync用于提供返回值的情况,CompletableFuture还有一个不需要返回值的异步调用方法runAsync(Runnable runnable),一般我们在优化Controller时,使用这个方法比较多。这两个方法如果在不指定线程池的情况下,都是在ForkJoinPool.common线程池中执行,而这个线程池中的所有线程都是Daemon(守护)线程,所以,当主线程结束时,这些线程无论执行完毕都会退出系统。

核心代码:

CompletableFuture.runAsync(() ->
this.afterBetProcessor(betRequest,betDetailResult,appUser,id)
);
异步调用使用Callable来实现
@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;
}
}

异步调用的方式 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);
}
}

二、增加内嵌Tomcat的最大连接数

@Configuration
public class TomcatConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory tomcatFactory = new TomcatServletWebServerFactory();
tomcatFactory.addConnectorCustomizers(new MyTomcatConnectorCustomizer());
tomcatFactory.setPort(8005);
tomcatFactory.setContextPath("/api-g");
return tomcatFactory;
}
class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
//设置最大连接数
protocol.setMaxConnections(20000);
//设置最大线程数
protocol.setMaxThreads(2000);
protocol.setConnectionTimeout(30000);
}
}
}

三、使用@ComponentScan()

使用 @ComponentScan() 定位扫包比 @SpringBootApplication 扫包更快。 

四、默认tomcat容器改为Undertow(Jboss下的服务器,Tomcat吞吐量5000,Undertow吞吐量8000)

<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>改为:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

五、使用 BufferedWriter 进行缓冲

 这里不给大家举例,可自行尝试。

六、Deferred方式实现异步调用

@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;
}
}

七、异步调用可以使用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方法");
}
}


---------------------
作者:北游学Java
来源:CSDN
原文:https://blog.csdn.net/fly1north/article/details/124230857
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Atitit.ati orm的设计and架构总结 适用于java c# php版

Atitit.ati orm的设计and架构总结 适用于java c# php版 1. Orm的目标 1 1.1. 动态obj 1 1.2. Hb的api(meger,save,update,del) 1 2. Orm的概念 1 3. 动态obj 2 4. 參考 4 1. Orm的目标 1.1. 动态obj 1.2. Hb的api(meger,save,update,del) 2. Orm的概念 saveOrUpdate后的对象会纳…

【ArcGIS微课1000例】0043:ArcGIS缩略图的创建及应用

缩略图通常出现在地图文档中&#xff0c;便于在启动页面中快速打开指定的地图文档&#xff0c;提高效率。 文章目录一、缩略图预览二、缩略图创建一、缩略图预览 打开ArcMap软件&#xff0c;弹出启动窗口&#xff0c;在最近打开的文档中&#xff0c;可以看到两类&#xff0c;一…

JSP简单登录系统

Login登陆界面 <body> 登陆 <% session.invalidate();%> <form action"TestPW.jsp" method"post">用户名<input type"text" name"username"> 密码<input type"password" name"password&quo…

手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践

本文主要讲解了如何把ABP官方的在线生成解决方案运行起来&#xff0c;并说明了解决方案中项目间的依赖关系。然后手动实践了如何从0搭建了一个简化的解决方案。ABP官方的在线生成解决方案源码下载参考[3]&#xff0c;手动搭建的简化的解决方案源码下载参考[4]。一.ABP官方在线生…

Java捕获并处理线程失败抛出的异常

使用 UncaughtExceptionHandler 示例代码如下&#xff1a; Thread.UncaughtExceptionHandler handler new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread th, Throwable ex) {System.out.println("Uncaught exception: " ex);} }; Th…

【ArcGIS微课1000例】0044:ArcGIS使用山体阴影显示DEM的3种方法

本文讲解了ArcGIS使用山体阴影显示DEM的3种方法:“影像分析”窗口、使用山体阴影效果和山体阴影效果工具的不同之处。 文章目录 一、“影像分析”窗口二、使用山体阴影效果三、山体阴影工具一、“影像分析”窗口 使用山体阴影显示 DEM 的方法有两种。最简单并且最具交互效果的…

区块链每日投资指南(0129)-证监会副主席表示数字货币需要监管

上一周的走势依然是工作日下跌&#xff0c;周末拉升的结局。这主要原因依然是&#xff0c;周末不上班。最终政策出炉之前&#xff0c;市场恐怕还将继续震荡。下周的工作日恐怕会重演下跌的节奏。但是经过了17号&#xff0c;23号&#xff0c;26号三次筑底来看&#xff0c;如果政…

蓝绿发布、滚动发布、灰度发布,有什么区别?

在项目迭代的过程中&#xff0c;不可避免需要”上线“。上线对应着部署&#xff0c;或者重新部署&#xff1b;部署对应着修改&#xff1b;修改则意味着风险。目前有很多部署发布的技术, 这儿将常见的做一个总结。 上面所说难免有些抽象, 举一个情景例子, 加入你是微博项目负责…

iOS 音频开发

音频基础知识 组成 音频文件的组成&#xff1a;文件格式(或者音频容器) 数据格式(或者音频编码)。 文件格式(或音频容器)是用于形容文件本身的格式。 我们可以通过多种不同的方法为真正的音频数据编码。例如CAF文件便是一种文件格式&#xff0c;它能够包含MP3格式&#xff0c;…

【ArcGIS微课1000例】0045:ArcGIS制图模板的自定义与使用方法

怎样在ArcGIS中保存地图模板以在地图制图与打印之前使用呢? 文章目录 一、地图模板简介二、地图模板创建1. 创建模板2. 创建缩略图3. 保存模板三、地图模板使用一、地图模板简介 使用ArcMap打开一个已有的地图模板,【文件】→【新建】,任选一个模板,这里选择一个传统模板。…

api 接口开发理论 在php中调用接口以及编写接口

如&#xff1a;http://localhost/openUser.php?actget_user_list&typejson 在这里openUser.php相当于一个接口&#xff0c;其中get_user_list 是一个API&#xff08;获取用户列表&#xff09;&#xff0c;讲求返回的数据类型为JSON格式。 你只需要在你PHP代码中执行这条链…

怎么样的框架对于开发者是友好的?

云原生离.NET开发到底有多远&#xff1f;云原生的概念由来不久&#xff0c;故事从“上云”开始&#xff0c;伴随dorker、k8s等技术的推出&#xff0c;以及CNCF与各大云厂商的共同加持&#xff0c;云原生逐渐被大家所熟知。云原生不依赖具体的云&#xff0c;不管公有云还是私有云…

JS 烧脑面试题大赏

本文精选了20多道具有一定迷惑性的js题&#xff0c;主要考察的是类型判断、作用域、this指向、原型、事件循环等知识点&#xff0c;每道题都配有详细傻瓜式的解析&#xff0c;偏向于初学者&#xff0c;大佬请随意。 第1题 let a 1 function b(a) {a 2console.log(a) } b(a)…

苹果ios用js的Date()出现NaN问题解决办法

原文:苹果ios用js的Date&#xff08;&#xff09;出现NaN问题解决办法ios使用如下方法获得NaN&#xff0c;安卓手机则是正常计算&#xff0c;解决方法是换个这个时间的格式 new Date("2017-04-28 23:59:59").getTime() 换成如下方式就正常了&#xff0c;就是‘-’换成…

Thinkphp 验证码、文件上传

一、验证码 验证码参数 例题&#xff1a;登录时验证下验证码 LoginController.class.php <?php namespace Home\Controller; use Think\Controller; class LoginController extends Controller {public function Login(){if(empty($_POST)){$this->display(); } e…

ArcGIS实验教程——实验四十七:数据驱动页工具批量制作甘肃省各地级市人口七普专题图集

本实验详细讲解利用ArcGIS数据驱动页工具,制作甘肃省各地级市人口七普专题图集。 文章目录 1. 数据驱动页工具简介2. 甘肃省各地级市人口七普专题图集2.1 符号化及标注2.2 数据驱动页的创建2.3 数据驱动页面文本操作2.4 数据驱动页的导出1. 数据驱动页工具简介 数据驱动页面是…

为什么Java有GC调优而没听说过有CLR的GC调优?

前言在很多的场合我都遇到过一些群友提这样的一些问题&#xff1a;为什么Java有GC调优而CLR没有听说过有GC调优呢&#xff1f;到底是Java的JVM GC比较强还是C#使用的.NET CLR的GC比较强呢&#xff1f;其实业内已经有几位大佬的高赞文章和大家分享一下&#xff0c;主要讨论JVM和…

Ubuntu16.04 - 安装RabbitVCS,linux下的TortoiseSVN!!!

RabbitVCS 官网&#xff1a;http://rabbitvcs.org/ 1&#xff0c;添加PPA源。在shell里面执行下面命令&#xff1a; sudo add-apt-repository ppa:rabbitvcs/ppa 这个命令执行完毕后&#xff0c;查看执行结果看是否密钥导入成功&#xff0c;成功截图&#xff1a; 如果导入密钥失…

8 种最坑的SQL错误用法

1、LIMIT 语句 2、隐式转换 3、关联更新、删除 4、混合排序 5、EXISTS语句 6、条件下推 7、提前缩小范围 8、中间结果集下推 总结 sql语句的执行顺序&#xff1a; FROM <left_table>ON <join_condition><join_type>JOIN <right_table>WHERE &…

Vue根据菜单json数据动态按需加载路由Vue-router

每个菜单项对应一个页面组件&#xff0c;根据菜单项动态按需加载路由 路由配置的正确写法&#xff1a; /*router/index.js*/ import Vue from vue import Router from vue-router import url from ./url import store from ../storeVue.use(Router)const router new Router({/…