基于Springboot吞吐量优化解决方案

一、异步执行

实现方式二种:

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线程将会继续执行。

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实例就可以作为这次调用的契约,在将来任何场合,用于获得最终的计算结果。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()定位扫包比@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方法");
 }
}

以上这篇基于Springboot吞吐量优化解决方案就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • springboot高并发下提高吞吐量的实现

    公司让做一个全文检索的项目,我使用的是elasticsearch.但是对性能有很高的要求,为了解决性能问题,我简直是寝食难安. es(elasticsearch)没有使用分布式,单台的. 开发完测试的时候,查询慢,吞吐量低. 网友们建议用异步--使用Callable来实现.webAsyncTask.Deferred方式等,我一一尝试了之后也没有明显效果,使用压测工具发现使用前后没有一点提升. 尝试这些方法花费了我两天的时间! 在不想使用redis缓存的情况下,我想到了多线程抱着试一试的心态. 没

  • 详解SpringBoot 应用如何提高服务吞吐量

    意外和明天不知道哪个先来.没有危机是最大的危机,满足现状是最大的陷阱. 背景 生产环境偶尔会有一些慢请求导致系统性能下降,吞吐量下降,下面介绍几种优化建议. 方案 1.undertow替换tomcat 电子商务类型网站大多都是短请求,一般响应时间都在100ms,这时可以将web容器从tomcat替换为undertow,下面介绍下步骤: 1.增加pom配置 <dependency> <groupid> org.springframework.boot </groupid>

  • Springboot实现高吞吐量异步处理详解(适用于高并发场景)

    技术要点 org.springframework.web.context.request.async.DeferredResult<T> 示例如下: 1.   新建Maven项目  async 2.   pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaL

  • 基于Springboot吞吐量优化解决方案

    一.异步执行 实现方式二种: 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

  • 基于springboot实现redis分布式锁的方法

    在公司的项目中用到了分布式锁,但只会用却不明白其中的规则 所以写一篇文章来记录 使用场景:交易服务,使用redis分布式锁,防止重复提交订单,出现超卖问题 分布式锁的实现方式 基于数据库乐观锁/悲观锁 Redis分布式锁(本文) Zookeeper分布式锁 redis是如何实现加锁的? 在redis中,有一条命令,实现锁 SETNX key value 该命令的作用是将 key 的值设为 value ,当且仅当 key 不存在.若给定的 key 已经存在,则 SETNX不做任何动作.设置成功,返

  • 基于SpringBoot与Mybatis实现SpringMVC Web项目

    一.热身 一个现实的场景是:当我们开发一个Web工程时,架构师和开发工程师可能更关心项目技术结构上的设计.而几乎所有结构良好的软件(项目)都使用了分层设计.分层设计是将项目按技术职能分为几个内聚的部分,从而将技术或接口的实现细节隐藏起来. 从另一个角度上来看,结构上的分层往往也能促进了技术人员的分工,可以使开发人员更专注于某一层业务与功能的实现,比如前端工程师只关心页面的展示与交互效果(例如专注于HTML,JS等),而后端工程师只关心数据和业务逻辑的处理(专注于Java,Mysql等).两者之间

  • 基于spring-boot和docker-java实现对docker容器的动态管理和监控功能[附完整源码下载]

    docker简介 Docker 是一个开源的应用容器引擎,和传统的虚拟机技术相比,Docker 容器性能开销极低,因此也广受开发者喜爱.随着基于docker的开发者越来越多,docker的镜像也原来越丰富,未来各种企业级的完整解决方案都可以直接通过下载镜像拿来即用.因此docker变得越来越重要. 本文目的 本文通过一个项目实例来介绍如果通过docker对外接口来实现对docker容器的管理和监控. 应用场景: 对服务器资源池通过docker进行统一管理,按需分配资源和创建容器,达到资源最大化利

  • 基于springboot处理date参数过程解析

    这篇文章主要介绍了基于springboot处理date参数过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言 最近在后台开发中遇到了时间参数的坑,就单独把这个问题提出来找时间整理了一下: 正文 测试方法 bean代码: public class DateModelNoAnnotation { private Integer id; private Date receiveDate; } controller代码: @RestContr

  • 基于SpringBoot服务端表单数据校验的实现方式

    SpringBoot服务端表单数据校验 (SpringBoot高级) 一.实现添加用户功能 1 创建项目 2 修改POM文件 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http:

  • 基于 SpringBoot 实现 MySQL 读写分离的问题

    -     前言     - 首先思考一个问题: 在高并发的场景中,关于数据库都有哪些优化的手段? 常用的实现方法有以下几种:读写分离.加缓存.主从架构集群.分库分表等,在互联网应用中,大部分都是读多写少的场景,设置两个库,主库和读库. 主库的职能是负责写,从库主要是负责读 , 可以建立读库集群 , 通过读写职能在数据源上的隔离达到减少读写冲突. 释压数据库负载.保护数据库的目的.在实际的使用中,凡是涉及到写的部分直接切换到主库,读的部分直接切换到读库,这就是典型的读写分离技术.本文将聚焦读写分

  • 如何基于SpringBoot实现人脸识别功能

    目录 前言 需求分析 一.人脸注册 二.人脸登录 具体实现 一.人脸注册 二.刷脸登录 总结 前言 去年在公司参与了一个某某机场建设智能机场的一个项目,人脸登机是其中的一个功能模块,当时只是写了后台的接口,调用人脸识别设备的api,给闸机回传数据信号,以保障该功能的正常使用. 当时因为项目进度紧张,手里还有其他项目赶进度,也就没时间去分享这个功能的实现.前几天刷脸进公司大楼的时候,突然想起来应该写一个功能类似的demo分享个人的一些小小的经验.在当时项目中刷脸的设备终端是采购某某AI公司,当然咱

  • 基于SpringBoot整合SSMP的详细教程

    目录 基于SpringBoot实现SSMP整合 整合JUnit 整合MyBatis 整合MyBatis-Plus 总结 基于SpringBoot实现SSMP整合 SpringBoot之所以好用,就是它能方便快捷的整合其他技术,这里我们先介绍四种技术的整合: 整合JUnit 整合MyBatis 整合MyBatis-Plus 整合Druid 整合JUnit ​ SpringBoot技术的定位用于简化开发,再具体点是简化Spring程序的开发.所以在整合任意技术的时候,如果你想直观感触到简化的效果,你

  • 基于SpringBoot上传任意文件功能的实现

    一.pom文件依赖的添加 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</gr

随机推荐