首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >揭秘 SpringBoot 并发极限:你的应用到底能抗住多少同时请求?

揭秘 SpringBoot 并发极限:你的应用到底能抗住多少同时请求?

作者头像
果酱带你啃java
发布2026-04-14 10:35:54
发布2026-04-14 10:35:54
350
举报

在这个秒杀活动频发、用户体验至上的时代,"我的 SpringBoot 应用到底能同时处理多少请求?" 这个问题几乎是每个后端开发者都必须面对的灵魂拷问。当用户量激增时,为何有的应用稳如泰山,有的却瞬间崩溃?当系统响应变慢时,究竟是哪里成为了瓶颈?本文将带你深入 SpringBoot 的底层机制,从线程模型到资源配置,从理论分析到实战测试,全方位揭秘 SpringBoot 的并发处理能力极限,让你不仅知其然,更知其所以然。

一、SpringBoot 处理请求的底层逻辑:从请求到响应的旅程

要理解 SpringBoot 能同时处理多少请求,首先必须搞清楚一个请求从发出到响应经历了哪些环节。SpringBoot 作为基于 Spring 的快速开发框架,其 Web 层默认使用的是 Spring MVC,而在 Servlet 容器的选择上,SpringBoot 2.x 及以上版本默认采用的是 Netty(当使用 WebFlux 时)或 Tomcat(当使用 Spring MVC 时)。我们这里主要讨论最常用的 Spring MVC + Tomcat 组合。

1.1 请求处理的基本流程

一个 HTTP 请求到达 SpringBoot 应用后的处理流程可以概括为以下几个步骤:

从流程图中可以看出,Tomcat 的线程池是处理并发请求的关键环节。每个到达的请求都会被分配给线程池中的一个线程进行处理,直到处理完成。因此,线程池的配置直接决定了 SpringBoot 应用能够同时处理的请求数量上限。

1.2 Tomcat 的线程模型

Tomcat 采用的是 BIO(阻塞 IO)模型还是 NIO(非阻塞 IO)模型?这对并发处理能力有很大影响。在 Tomcat 8.0 及以上版本中,默认采用的是 NIO 模型,这相比之前的 BIO 模型有了很大的性能提升。

代码语言:javascript
复制

在 NIO 模型中,Tomcat 使用一个 Acceptor 线程负责接收新的连接,然后将连接注册到 Selector 上,再由工作线程进行处理。这种模型可以用少量线程处理大量连接,大大提高了并发处理能力。

二、影响 SpringBoot 并发处理能力的核心因素

SpringBoot 应用能够同时处理的请求数量并不是一个固定值,它受到多种因素的综合影响。理解这些因素,才能有针对性地进行优化。

2.1 线程池配置:并发处理的第一道闸门

Tomcat 的线程池配置是决定并发处理能力的最直接因素。在 SpringBoot 中,我们可以通过配置文件来调整 Tomcat 的线程池参数:

代码语言:javascript
复制
# 最大工作线程数,默认200
server.tomcat.threads.max=200
# 核心工作线程数,默认10
server.tomcat.threads.core=10
# 线程池队列长度,默认Integer.MAX_VALUE
server.tomcat.accept-count=1000
# 最大连接数,默认10000(NIO模式)
server.tomcat.max-connections=10000
代码语言:javascript
复制

这些参数之间存在着密切的关系,我们可以用一个流程图来理解请求的处理过程:

代码语言:javascript
复制

从这个流程可以看出,当请求量超过最大线程数 + 队列长度时,新的请求就会被拒绝。因此,理论上 SpringBoot 应用能够同时 "处理" 的请求数是最大线程数(正在处理的)加上队列长度(等待处理的)。但需要注意的是,队列中的请求并没有真正在处理,只是在等待处理。

2.2 JVM 配置:并发处理的内存基石

JVM 的配置虽然不直接决定并发请求的数量,但它会影响应用的稳定性和性能。如果 JVM 配置不当,可能会导致内存溢出或频繁的垃圾回收,从而影响应用处理请求的能力。

典型的 JVM 配置如下:

代码语言:javascript
复制
-Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC
代码语言:javascript
复制

  • -Xms 和 -Xmx:设置堆的初始大小和最大大小,通常建议设置为相同值,避免频繁调整堆大小。
  • -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize:设置元空间的大小,元空间用于存储类信息。
  • -XX:+UseG1GC:使用 G1 垃圾收集器,适合大堆内存的情况,能在吞吐量和延迟之间取得较好的平衡。

2.3 数据库连接池:并发处理的隐藏瓶颈

很多时候,应用的并发瓶颈并不是在应用服务器,而是在数据库。如果数据库连接池配置不当,即使应用服务器能处理大量请求,也会因为无法获取数据库连接而阻塞。

在 SpringBoot 中,默认使用的是 HikariCP 连接池,这是目前性能最好的连接池之一。我们可以在配置文件中对其进行配置:

代码语言:javascript
复制
# 数据库连接池配置
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
代码语言:javascript
复制

  • maximum-pool-size:连接池的最大连接数,默认 10。
  • minimum-idle:最小空闲连接数,默认与最大连接数相同。
  • connection-timeout:获取连接的超时时间,默认 30 秒。
  • idle-timeout:连接的空闲超时时间,默认 10 分钟。
  • max-lifetime:连接的最大生命周期,默认 30 分钟。

数据库连接池的大小应该根据数据库的性能和应用的需求来设置。设置得太小,会导致请求等待连接;设置得太大,会增加数据库的负担。

2.4 硬件资源:并发处理的物理边界

无论软件如何优化,最终都受限于硬件资源。影响并发处理能力的硬件资源主要包括:

  1. CPU 核心数:线程的执行依赖 CPU,CPU 核心数越多,能同时运行的线程数就越多。
  2. 内存大小:决定了应用能分配的内存多少,以及能创建的线程数量上限。
  3. 网络带宽:如果应用涉及大量网络 IO,网络带宽可能成为瓶颈。
  4. 磁盘 IO:如果应用涉及大量磁盘操作,磁盘 IO 性能会影响整体性能。

2.5 应用程序设计:并发处理的软件瓶颈

即使前面所有因素都配置得当,糟糕的应用程序设计也会导致并发处理能力低下。常见的问题包括:

  1. 同步代码块使用不当:过多或过大的同步代码块会导致线程阻塞,降低并发性能。
  2. 长时间运行的任务:如果一个请求处理时间过长,会占用线程池资源,导致其他请求无法得到处理。
  3. 内存泄漏:内存泄漏会导致 JVM 频繁 GC,甚至 OOM,严重影响应用性能。
  4. 不合理的缓存策略:缺乏缓存或缓存策略不合理会导致频繁访问数据库,增加响应时间。

三、实战:测试 SpringBoot 应用的并发处理能力

理论讲完了,接下来我们通过一个实际的 SpringBoot 应用来测试和分析其并发处理能力。

3.1 准备测试环境

我们需要准备以下环境和工具:

  1. JDK 17
  2. SpringBoot 3.2.0(最新稳定版)
  3. Apache JMeter 5.6(用于压力测试)
  4. MySQL 8.0(数据库)
  5. 测试服务器:4 核 8G(为了更明显地看到性能瓶颈)

3.2 创建测试应用

首先,我们创建一个简单的 SpringBoot 应用,包含一个 Controller 和一个 Service,模拟实际业务场景。

3.2.1 pom.xml 配置
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-concurrency-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-concurrency-test</name>
    <description>Test SpringBoot concurrency capability</description>
    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <fastjson2.version>2.0.32</fastjson2.version>
        <guava.version>32.1.3-jre</guava.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
代码语言:javascript
复制

3.2.2 配置文件 application.properties
代码语言:javascript
复制
# 服务器配置
server.port=8080
server.tomcat.threads.max=200
server.tomcat.threads.core=10
server.tomcat.accept-count=1000
server.tomcat.max-connections=10000

# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# HikariCP配置
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

# MyBatis-Plus配置
mybatis-plus.mapper-locations=classpath:mapper/*.xml
mybatis-plus.type-aliases-package=com.example.concurrencytest.entity

# Swagger配置
springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.path=/swagger-ui.html
代码语言:javascript
复制

3.2.3 创建实体类 User.java
代码语言:javascript
复制
package com.example.concurrencytest.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用户实体类
 *
 * @author ken
 */
@Data
@TableName("user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
}
代码语言:javascript
复制

3.2.4 创建 Mapper 接口 UserMapper.java
代码语言:javascript
复制
package com.example.concurrencytest.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.concurrencytest.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * 用户Mapper接口
 *
 * @author ken
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
代码语言:javascript
复制

3.2.5 创建 Service 接口 UserService.java
代码语言:javascript
复制
package com.example.concurrencytest.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.concurrencytest.entity.User;

/**
 * 用户Service接口
 *
 * @author ken
 */
public interface UserService extends IService<User> {
    /**
     * 根据ID查询用户
     *
     * @param id 用户ID
     * @return 用户信息
     */
    User getUserById(Long id);

    /**
     * 新增用户
     *
     * @param user 用户信息
     * @return 新增结果
     */
    boolean addUser(User user);
}
代码语言:javascript
复制

3.2.6 创建 Service 实现类 UserServiceImpl.java
代码语言:javascript
复制
package com.example.concurrencytest.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.concurrencytest.entity.User;
import com.example.concurrencytest.mapper.UserMapper;
import com.example.concurrencytest.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;

/**
 * 用户Service实现类
 *
 * @author ken
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override
    public User getUserById(Long id) {
        if (ObjectUtils.isEmpty(id)) {
            log.error("查询用户失败,用户ID为空");
            return null;
        }
        return baseMapper.selectById(id);
    }

    @Override
    public boolean addUser(User user) {
        if (ObjectUtils.isEmpty(user)) {
            log.error("新增用户失败,用户信息为空");
            return false;
        }
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return save(user);
    }
}
代码语言:javascript
复制

3.2.7 创建 Controller 类 UserController.java
代码语言:javascript
复制
package com.example.concurrencytest.controller;

import com.example.concurrencytest.entity.User;
import com.example.concurrencytest.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;

import java.util.concurrent.TimeUnit;

/**
 * 用户Controller
 *
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理", description = "用户相关接口")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 根据ID查询用户
     *
     * @param id 用户ID
     * @return 用户信息
     */
    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户", description = "根据用户ID查询用户详细信息")
    public ResponseEntity<User> getUserById(
            @Parameter(description = "用户ID", required = true)
            @PathVariable Long id) {
        try {
            // 模拟处理时间
            TimeUnit.MILLISECONDS.sleep(100);

            User user = userService.getUserById(id);
            if (ObjectUtils.isEmpty(user)) {
                log.warn("用户不存在,ID: {}", id);
                return ResponseEntity.notFound().build();
            }
            return ResponseEntity.ok(user);
        } catch (Exception e) {
            log.error("查询用户失败,ID: {}", id, e);
            return ResponseEntity.internalServerError().build();
        }
    }

    /**
     * 新增用户
     *
     * @param user 用户信息
     * @return 新增结果
     */
    @PostMapping
    @Operation(summary = "新增用户", description = "创建新用户")
    public ResponseEntity<Boolean> addUser(
            @Parameter(description = "用户信息", required = true)
            @RequestBody User user) {
        try {
            // 模拟处理时间
            TimeUnit.MILLISECONDS.sleep(200);

            boolean result = userService.addUser(user);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("新增用户失败", e);
            return ResponseEntity.internalServerError().build();
        }
    }

    /**
     * 测试并发接口
     *
     * @param sleepTime 模拟处理时间(毫秒)
     * @return 处理结果
     */
    @GetMapping("/test")
    @Operation(summary = "测试并发接口", description = "用于测试系统并发处理能力的接口")
    public ResponseEntity<String> testConcurrency(
            @Parameter(description = "模拟处理时间(毫秒)", required = false)
            @RequestParam(required = false, defaultValue = "100") Long sleepTime) {
        try {
            if (sleepTime > 0) {
                TimeUnit.MILLISECONDS.sleep(sleepTime);
            }
            return ResponseEntity.ok("处理成功");
        } catch (Exception e) {
            log.error("测试并发接口失败", e);
            return ResponseEntity.internalServerError().body("处理失败");
        }
    }
}
代码语言:javascript
复制

3.2.8 创建启动类 ConcurrencyTestApplication.java
代码语言:javascript
复制
package com.example.concurrencytest;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 应用启动类
 *
 * @author ken
 */
@SpringBootApplication
@MapperScan("com.example.concurrencytest.mapper")
public class ConcurrencyTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConcurrencyTestApplication.class, args);
    }
}
代码语言:javascript
复制

3.2.9 创建数据库表
代码语言:javascript
复制
CREATE DATABASE IF NOT EXISTS test DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE test;

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

-- 插入测试数据
INSERT INTO `user` (`username`, `age`, `create_time`, `update_time`) VALUES
('test1', 20, NOW(), NOW()),
('test2', 25, NOW(), NOW()),
('test3', 30, NOW(), NOW());
代码语言:javascript
复制

3.3 进行压力测试

我们使用 JMeter 来对上面创建的应用进行压力测试。主要测试/user/test接口,这个接口可以通过参数控制处理时间。

3.3.1 JMeter 测试计划设置
  1. 线程组设置:
    • 线程数:模拟的并发用户数
    • Ramp-Up 时间:多久内启动所有线程
    • 循环次数:每个线程发送请求的次数
  2. HTTP 请求设置:
    • 服务器名称或 IP:localhost
    • 端口号:8080
    • 路径:/user/test
    • 参数:sleepTime=100(模拟 100ms 的处理时间)
  3. 监听器:
    • 聚合报告:查看平均响应时间、吞吐量等指标
    • 查看结果树:查看每个请求的详细结果
3.3.2 测试场景和结果分析

我们将进行多组测试,逐步增加并发用户数,观察系统的响应情况。

场景 1:100 并发用户
  • 线程数:100
  • Ramp-Up 时间:10 秒
  • 循环次数:10
  • 总请求数:1000

测试结果:

  • 平均响应时间:~120ms
  • 90% 响应时间:~150ms
  • 吞吐量:~800 requests/sec
  • 错误率:0%

分析:在 100 并发用户下,系统表现良好,响应时间略高于模拟的处理时间,说明线程池有足够的线程处理请求,没有明显的等待。

场景 2:300 并发用户
  • 线程数:300
  • Ramp-Up 时间:10 秒
  • 循环次数:10
  • 总请求数:3000

测试结果:

  • 平均响应时间:~350ms
  • 90% 响应时间:~500ms
  • 吞吐量:~850 requests/sec
  • 错误率:0%

分析:当并发用户增加到 300 时,响应时间明显增加,说明部分请求开始在队列中等待。但吞吐量略有提升,说明系统仍在有效利用资源。

场景 3:500 并发用户
  • 线程数:500
  • Ramp-Up 时间:10 秒
  • 循环次数:10
  • 总请求数:5000

测试结果:

  • 平均响应时间:~800ms
  • 90% 响应时间:~1200ms
  • 吞吐量:~600 requests/sec
  • 错误率:~5%

分析:当并发用户增加到 500 时,响应时间显著增加,吞吐量开始下降,并且出现了少量错误。这说明系统已经接近处理极限,部分请求因为队列满而被拒绝。

场景 4:1000 并发用户
  • 线程数:1000
  • Ramp-Up 时间:10 秒
  • 循环次数:10
  • 总请求数:10000

测试结果:

  • 平均响应时间:~2500ms
  • 90% 响应时间:~4000ms
  • 吞吐量:~350 requests/sec
  • 错误率:~30%

分析:当并发用户增加到 1000 时,系统性能严重下降,响应时间大幅增加,吞吐量显著下降,错误率也大幅上升。这说明系统已经超过了处理能力极限,大量请求被拒绝。

3.4 调整配置后的测试

根据上面的测试结果,我们尝试调整 Tomcat 和数据库连接池的配置,看看能否提升系统的并发处理能力。

3.4.1 调整后的配置
代码语言:javascript
复制
# 调整Tomcat配置
server.tomcat.threads.max=300
server.tomcat.threads.core=50
server.tomcat.accept-count=2000
server.tomcat.max-connections=20000

# 调整数据库连接池配置
spring.datasource.hikari.maximum-pool-size=30
代码语言:javascript
复制

3.4.2 调整后的测试结果(500 并发用户)
  • 平均响应时间:~600ms
  • 90% 响应时间:~900ms
  • 吞吐量:~750 requests/sec
  • 错误率:~1%

分析:调整配置后,在 500 并发用户的场景下,系统性能有明显提升,响应时间减少,吞吐量增加,错误率降低。这说明合理的配置调整可以有效提升系统的并发处理能力。

四、优化 SpringBoot 并发处理能力的实战技巧

通过前面的测试和分析,我们已经了解了影响 SpringBoot 并发处理能力的主要因素。接下来,我们将介绍一些实用的优化技巧,帮助你提升 SpringBoot 应用的并发处理能力。

4.1 合理配置线程池

线程池的配置是优化的第一步,需要根据应用的特点和服务器的硬件配置来调整。

  1. 核心线程数(core threads):
    • 对于 CPU 密集型任务,核心线程数建议设置为 CPU 核心数 + 1
    • 对于 IO 密集型任务,核心线程数可以设置为 CPU 核心数 * 2
  2. 最大线程数(max threads):
    • 最大线程数不宜设置过大,否则会导致线程上下文切换频繁,反而降低性能
    • 一般建议设置为 CPU 核心数的 10-20 倍,具体需要根据测试结果调整
  3. 队列长度(accept count):
    • 队列长度设置过大会导致请求等待时间过长
    • 设置过小会导致请求被频繁拒绝
    • 建议根据业务对响应时间的要求来设置,一般可以设置为最大线程数的 5-10 倍

4.2 使用异步处理

对于一些耗时的操作,可以采用异步处理的方式,避免阻塞主线程。SpringBoot 提供了 @Async 注解,可以很方便地实现异步处理。

4.2.1 配置异步线程池
代码语言:javascript
复制
package com.example.concurrencytest.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 异步配置类
 *
 * @author ken
 */
@Configuration
@EnableAsync
public class AsyncConfig {

    /**
     * 配置异步线程池
     *
     * @return 线程池执行器
     */
    @Bean(name = "asyncExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(50);
        // 队列容量
        executor.setQueueCapacity(100);
        // 线程名称前缀
        executor.setThreadNamePrefix("async-");
        // 当线程池达到最大线程数时的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化线程池
        executor.initialize();
        return executor;
    }
}
代码语言:javascript
复制

4.2.2 使用异步方法
代码语言:javascript
复制
package com.example.concurrencytest.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

/**
 * 异步服务类
 *
 * @author ken
 */
@Service
public class AsyncService {

    /**
     * 异步处理方法
     *
     * @param data 处理数据
     * @return 处理结果
     */
    @Async("asyncExecutor")
    public CompletableFuture<String> processAsync(String data) {
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return CompletableFuture.failedFuture(e);
        }
        return CompletableFuture.completedFuture("处理完成: " + data);
    }
}
代码语言:javascript
复制

4.2.3 在 Controller 中调用异步方法
代码语言:javascript
复制
@Autowired
private AsyncService asyncService;

@GetMapping("/async-test")
@Operation(summary = "测试异步接口", description = "用于测试异步处理能力的接口")
public ResponseEntity<String> testAsync() {
    try {
        // 调用异步方法
        CompletableFuture<String> future = asyncService.processAsync("测试数据");

        // 可以做其他事情...

        // 等待异步处理完成并获取结果
        String result = future.get();
        return ResponseEntity.ok(result);
    } catch (Exception e) {
        log.error("测试异步接口失败", e);
        return ResponseEntity.internalServerError().body("处理失败");
    }
}
代码语言:javascript
复制

4.3 使用缓存减轻数据库压力

数据库往往是系统的瓶颈之一,合理使用缓存可以显著减轻数据库的压力,提高系统的响应速度和并发处理能力。

SpringBoot 提供了对多种缓存技术的支持,如 Caffeine、Redis 等。这里我们以 Caffeine 为例,介绍如何在 SpringBoot 中使用缓存。

4.3.1 添加 Caffeine 依赖
代码语言:javascript
复制
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
代码语言:javascript
复制

4.3.2 配置缓存
代码语言:javascript
复制
package com.example.concurrencytest.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cache.annotation.EnableCaching;

import java.util.concurrent.TimeUnit;

/**
 * 缓存配置类
 *
 * @author ken
 */
@Configuration
@EnableCaching
public class CacheConfig {

    /**
     * 配置缓存管理器
     *
     * @return 缓存管理器
     */
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        // 配置缓存过期时间
        cacheManager.setCaffeine(Caffeine.newBuilder()
                // 写入后过期时间
                .expireAfterWrite(5, TimeUnit.MINUTES)
                // 最大缓存数量
                .maximumSize(10000));
        return cacheManager;
    }
}
代码语言:javascript
复制

4.3.3 在 Service 中使用缓存
代码语言:javascript
复制
@Override
@Cacheable(value = "userCache", key = "#id")
public User getUserById(Long id) {
    if (ObjectUtils.isEmpty(id)) {
        log.error("查询用户失败,用户ID为空");
        return null;
    }
    log.info("从数据库查询用户,ID: {}", id);
    return baseMapper.selectById(id);
}

@Override
@CacheEvict(value = "userCache", key = "#user.id")
public boolean updateUser(User user) {
    if (ObjectUtils.isEmpty(user) || ObjectUtils.isEmpty(user.getId())) {
        log.error("更新用户失败,用户信息或ID为空");
        return false;
    }
    user.setUpdateTime(LocalDateTime.now());
    return updateById(user);
}
代码语言:javascript
复制

4.4 使用连接池优化数据库访问

数据库连接的创建和销毁是比较昂贵的操作,使用连接池可以复用数据库连接,减少这些开销。

前面我们已经介绍了 HikariCP 的基本配置,这里我们再补充一些高级配置和最佳实践:

  1. 设置合理的最大连接数:
    • 最大连接数不宜设置过大,否则会增加数据库的负担
    • 一般建议设置为 CPU 核心数的 2-4 倍,或者根据数据库的最大连接数来调整
  2. 设置合理的连接超时时间:
    • 连接超时时间不宜设置过长,否则会导致请求长时间等待
    • 也不宜设置过短,否则在数据库压力大时会频繁出现获取连接超时的错误
    • 一般建议设置为 30 秒左右
  3. 启用连接测试:
    • 可以配置 HikariCP 定期测试连接的有效性,避免使用无效连接
代码语言:javascript
复制
# 连接测试查询
spring.datasource.hikari.connection-test-query=SELECT 1
# 测试连接的超时时间
spring.datasource.hikari.validation-timeout=5000
代码语言:javascript
复制

4.5 使用非阻塞 IO:Spring WebFlux

如果应用的并发要求非常高,可以考虑使用 Spring WebFlux 替代 Spring MVC。Spring WebFlux 基于响应式编程模型,使用非阻塞 IO,可以用更少的线程处理更多的并发请求。

4.5.1 添加 WebFlux 依赖
代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
代码语言:javascript
复制

4.5.2 创建 WebFlux Controller
代码语言:javascript
复制
package com.example.concurrencytest.controller;

import com.example.concurrencytest.entity.User;
import com.example.concurrencytest.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.time.Duration;

/**
 * WebFlux用户Controller
 *
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/reactive/user")
@Tag(name = "响应式用户管理", description = "响应式用户相关接口")
public class ReactiveUserController {

    @Autowired
    private UserService userService;

    /**
     * 根据ID查询用户
     *
     * @param id 用户ID
     * @return 用户信息
     */
    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户", description = "根据用户ID查询用户详细信息")
    public Mono<ResponseEntity<User>> getUserById(
            @Parameter(description = "用户ID", required = true)
            @PathVariable Long id) {
        return Mono.fromSupplier(() -> {
            try {
                // 模拟处理时间
                Thread.sleep(100);

                User user = userService.getUserById(id);
                if (ObjectUtils.isEmpty(user)) {
                    log.warn("用户不存在,ID: {}", id);
                    return ResponseEntity.notFound().build();
                }
                return ResponseEntity.ok(user);
            } catch (Exception e) {
                log.error("查询用户失败,ID: {}", id, e);
                return ResponseEntity.internalServerError().build();
            }
        }).subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 测试并发接口
     *
     * @param sleepTime 模拟处理时间(毫秒)
     * @return 处理结果
     */
    @GetMapping("/test")
    @Operation(summary = "测试并发接口", description = "用于测试系统并发处理能力的接口")
    public Mono<String> testConcurrency(
            @Parameter(description = "模拟处理时间(毫秒)", required = false)
            @RequestParam(required = false, defaultValue = "100") Long sleepTime) {
        return Mono.just("处理成功")
                .delayElement(Duration.ofMillis(sleepTime));
    }
}
代码语言:javascript
复制

Spring WebFlux 的优势在于处理大量并发请求时的资源效率,它可以用更少的线程处理更多的请求。但需要注意的是,WebFlux 需要整个调用链都是非阻塞的才能发挥最大优势,如果中间有阻塞操作,性能可能反而不如 Spring MVC。

五、SpringBoot 并发处理的最佳实践

综合前面的分析和实践,我们总结出以下 SpringBoot 并发处理的最佳实践:

5.1 合理配置资源

  1. 根据服务器硬件配置和应用特点,调整 Tomcat 线程池参数
  2. 配置合适的 JVM 参数,避免内存溢出和频繁 GC
  3. 优化数据库连接池配置,避免数据库成为瓶颈
  4. 使用缓存减轻数据库压力,提高响应速度

5.2 优化应用设计

  1. 避免长时间运行的同步操作,使用异步处理
  2. 合理使用锁机制,避免不必要的同步
  3. 拆分复杂业务,提高代码的可维护性和可扩展性
  4. 使用非阻塞 IO(WebFlux)处理高并发场景

5.3 做好监控和调优

  1. 使用 Spring Boot Actuator 监控应用性能指标
  2. 定期进行压力测试,发现性能瓶颈
  3. 根据监控数据和测试结果,持续优化配置和代码
  4. 关注 JVM 的 GC 情况,及时调整 JVM 参数

5.4 考虑水平扩展

当单实例应用的性能达到极限时,考虑使用集群部署,通过负载均衡实现水平扩展。SpringBoot 应用可以很方便地部署到容器中,结合 Kubernetes 等容器编排工具,可以实现自动扩缩容,更好地应对流量波动。

六、结论:SpringBoot 到底能同时处理多少请求?

回到文章开头的问题:"SpringBoot 可以同时处理多少请求?"

通过前面的分析和实践,我们可以得出结论:SpringBoot 应用能够同时处理的请求数量不是一个固定值,它受到多种因素的影响,包括线程池配置、JVM 配置、数据库性能、硬件资源和应用程序设计等。在默认配置下,一个简单的 SpringBoot 应用在普通服务器上可能只能处理几百个并发请求。但通过合理的配置优化和代码优化,处理几千甚至上万并发请求也是可能的。更重要的是,我们需要明白:并发处理能力不是越高越好,而是要根据实际业务需求和资源情况,找到一个平衡点。过度追求高并发可能会导致资源浪费和系统复杂度增加。最后,我们应该建立完善的监控和测试体系,持续关注系统性能,根据实际运行情况进行优化,才能让 SpringBoot 应用在面对各种流量场景时都能表现出色。

希望本文能帮助你深入理解 SpringBoot 的并发处理机制,为你的应用优化提供一些思路和实践指导。如果你有任何疑问或建议,欢迎在评论区留言讨论。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、SpringBoot 处理请求的底层逻辑:从请求到响应的旅程
    • 1.1 请求处理的基本流程
    • 1.2 Tomcat 的线程模型
  • 二、影响 SpringBoot 并发处理能力的核心因素
    • 2.1 线程池配置:并发处理的第一道闸门
    • 2.2 JVM 配置:并发处理的内存基石
    • 2.3 数据库连接池:并发处理的隐藏瓶颈
    • 2.4 硬件资源:并发处理的物理边界
    • 2.5 应用程序设计:并发处理的软件瓶颈
  • 三、实战:测试 SpringBoot 应用的并发处理能力
    • 3.1 准备测试环境
    • 3.2 创建测试应用
      • 3.2.1 pom.xml 配置
      • 3.2.2 配置文件 application.properties
      • 3.2.3 创建实体类 User.java
      • 3.2.4 创建 Mapper 接口 UserMapper.java
      • 3.2.5 创建 Service 接口 UserService.java
      • 3.2.6 创建 Service 实现类 UserServiceImpl.java
      • 3.2.7 创建 Controller 类 UserController.java
      • 3.2.8 创建启动类 ConcurrencyTestApplication.java
      • 3.2.9 创建数据库表
    • 3.3 进行压力测试
      • 3.3.1 JMeter 测试计划设置
      • 3.3.2 测试场景和结果分析
    • 3.4 调整配置后的测试
      • 3.4.1 调整后的配置
      • 3.4.2 调整后的测试结果(500 并发用户)
  • 四、优化 SpringBoot 并发处理能力的实战技巧
    • 4.1 合理配置线程池
    • 4.2 使用异步处理
      • 4.2.1 配置异步线程池
      • 4.2.2 使用异步方法
      • 4.2.3 在 Controller 中调用异步方法
    • 4.3 使用缓存减轻数据库压力
      • 4.3.1 添加 Caffeine 依赖
      • 4.3.2 配置缓存
      • 4.3.3 在 Service 中使用缓存
    • 4.4 使用连接池优化数据库访问
    • 4.5 使用非阻塞 IO:Spring WebFlux
      • 4.5.1 添加 WebFlux 依赖
      • 4.5.2 创建 WebFlux Controller
  • 五、SpringBoot 并发处理的最佳实践
    • 5.1 合理配置资源
    • 5.2 优化应用设计
    • 5.3 做好监控和调优
    • 5.4 考虑水平扩展
  • 六、结论:SpringBoot 到底能同时处理多少请求?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档