通过八种不同的方式增加SpringBoot吞吐量

本文介绍了Spring Boot中实现异步处理的多种方式,包括使用`@Async`注解、自定义异步配置类以及`DeferredResult`实现异步调用。同时,还探讨了通过缓存、业务拆分、调整Tomcat最大连接数、切换到Undertow容器等方法来提升应用性能。这些技术对于优化服务响应速度和提高系统吞吐量具有重要意义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

方式一、使用异步注解@aysnc

用法1、

1.在启动类或者Controller类加上@EnableAsync注解

/**
 * @EnableAsync注解可以开启多线程,
 * 可标注在方法、类上
 */
@SpringBootApplication
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2.在需要进行异步处理的方法上加@Async注解

/**
 * 无参返回方法
 * 使用@Async注解的类需要是spring管理的类
 */
@Async
public void testAsync() {
    long startTime = System.currentTimeMillis();
    try {
        //模拟耗时
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    long endTime = System.currentTimeMillis();
    System.out.println(Thread.currentThread().getName() + ":void testAsync1(),耗时:" +     
    (endTime - startTime));
}


/**
 * 有参返回方法
 * 使用@Async注解的类需要是spring管理的类
 */
@Override
@Async
public Future<String> testAsync2() {
    long startTime = System.currentTimeMillis();
    try {
        //模拟耗时
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    long endTime = System.currentTimeMillis();
    return new AsyncResult<>("testAsync4:耗时:" + (endTime - startTime));
}

用法2

通用配置类

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 异步任务通用配置类
 */
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(5);
        // 设置最大线程数
        executor.setMaxPoolSize(10);
        // 设置队列容量
        executor.setQueueCapacity(20);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("Test-Async");
        // 设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

方法上添加注解

@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方法");
 }
 
}

方式二、采用缓存方式存储部分固定数据

将部分热点数据或者静态数据放到本地缓存或者Redis中,如果有需要可以定时更新缓存数据

方式三、业务拆分

将比较耗时或者不同的业务拆分出来提供单节点的吞吐量

方式四、增加内嵌 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

默认 Tomcat 容器改为 Undertow。

Undertow是Jboss 下的服务器,Tomcat 吞吐量 5000,Undertow 吞吐量 8000。

<!-- tomcat容器 -->
<exclusions>
  <exclusion>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
  </exclusion>
</exclusions>

<!-- undertow容器 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

方式七、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方法");
    }
}
### Spring Boot 批量插入 MySQL 数据库的最佳实践 #### 配置批处理模式 为了提升批量插入性能,在 `application.yml` 文件中需配置数据库连接字符串并启用批处理功能。具体做法是在 JDBC URL 中添加 `rewriteBatchedStatements=true` 参数,这会指示 MySQL Connector/J 使用优化后的语句执行方式[^3]。 ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/dbname?useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true username: user password: pass driver-class-name: com.mysql.cj.jdbc.Driver ``` #### 设置合理的JDBC参数 除了上述URL配置外,还需考虑调整其他影响性能的因素: - **max_allowed_packet**: 增大此值允许更大的SQL包传输,默认可能太小导致大批量数据无法一次性发送成功。 - **innodb_buffer_pool_size**: 对于InnoDB表而言适当增加缓冲池大小有助于加速磁盘I/O操作。 - **bulk_insert_buffer_size**: 提高该变量能加快LOAD DATA INFILE 和多行INSERT的速度。 这些设置应在MySQL服务器端完成修改[^4]。 #### 编程层面实现高效批量插入 针对不同场景可以选择合适的技术手段来达成目的: ##### MyBatis框架下的批量插入 通过构建动态SQL语句的方式来进行批量加载,这种方式灵活性较高且易于理解。下面是一个简单的例子展示如何利用MyBatis特性完成这一目标[^2]: ```java @Insert("<script>" + "INSERT INTO table_name (column1, column2)" + "<foreach collection='list' item='item' separator=','>" + "(#{item.column1}, #{item.column2})" + "</foreach>" + "</script>") void batchInsert(@Param("list") List<Record> records); ``` ##### JPA环境下避开IDENTITY主键冲突 由于IDENTITY类型的自增列会在每次调用save()方法时触发新的ID分配过程,从而破坏了真正的批量效果。因此建议采用SEQUENCE或其他替代方案作为主键生成策略,这样就能充分发挥Hibernate内置的batch insert机制的优势[^1]. ```properties hibernate.jdbc.batch_size=50 hibernate.order_inserts=true hibernate.order_updates=true ``` 以上措施能够有效减少网络往返次数以及降低锁竞争概率,进而显著改善整体吞吐率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值