Spring Boot 1.x 接口性能优化:从 3 秒到 200 毫秒的实战调优之路

Spring Boot 1.x 接口性能优化:从 3 秒到 200 毫秒的实战调优之路

前言:那个让老板差点把我开了的接口

某年的年底,我们团队负责的电商订单查询接口突然成了"性能杀手"。线上高峰期,一个简单的订单查询接口响应时间直接飙到 3 秒,用户投诉电话打爆了客服,老板在周会上直接拍桌子:"2 天内必须优化到 200ms 以内,否则这个项目组全部扣绩效!"

当时我整个人都懵了。这个接口之前明明跑得好好的,怎么突然就慢成这样了?更让人头疼的是,我们项目用的是 Spring Boot 1.5.22(因为历史原因,公司规定不能升级到 2.x),很多新版本的优化工具都用不了。

⚠️ 版本风险提示: Spring Boot 1.x 已在 2019 年停止官方维护,若业务允许,优先评估升级到 2.x/3.x 的成本;若无法升级,需关注第三方依赖(如 Druid、Redis)的安全更新。本文所有配置和代码均基于 Spring Boot 1.5.22.RELEASE 验证可用。

但没办法,硬着头皮上吧。经过 2 天的疯狂调优,最终我们把接口响应时间从 3000ms 降到了 180ms,QPS 从 100 提升到了 1000,错误率从 5% 降到了 0.1%。

今天就把这次调优的完整过程分享出来,希望能帮到同样在 Spring Boot 1.x 项目中挣扎的兄弟们。

一、环境准备:调优前的必备工具清单

在开始调优之前,先确保你的环境满足以下要求,避免在调优过程中因为工具版本不兼容而踩坑。

1.1 基础环境要求

工具/组件 版本要求 说明
JDK 1.8u102+ Spring Boot 1.5.22 要求 JDK 8,建议使用 u102 及以上版本(修复了部分 GC Bug)
Spring Boot 1.5.22.RELEASE 本文所有配置基于此版本验证
MySQL 5.6 / 5.7 避免使用 MySQL 8.0(与 1.x 数据源可能存在兼容性问题)
Redis 3.2+ 避免使用 Redis 6.0+(高版本可能有兼容性问题)

1.2 诊断工具安装

Arthas 1.3.1(兼容 Spring Boot 1.x):

bash 复制代码
# 下载 Arthas 1.3.1(注意:不要用最新版,可能不兼容 1.x)
wget https://github.com/alibaba/arthas/releases/download/arthas-all-3.8.4/arthas-boot.jar

# 启动 Arthas(需要 Java 进程运行权限)
java -jar arthas-boot.jar
# 执行后会列出所有 Java 进程,输入进程号选择目标应用即可 attach

JVisualVM(JDK 自带):

bash 复制代码
# JDK 8 自带,路径通常在:
# macOS: /Library/Java/JavaVirtualMachines/jdk1.8.0_xxx.jdk/Contents/Home/bin/jvisualvm
# Linux: $JAVA_HOME/bin/jvisualvm

JMeter(压测工具):

bash 复制代码
# 下载 JMeter 5.4.1(稳定版本)
wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.4.1.tgz
tar -xzf apache-jmeter-5.4.1.tgz

1.3 完整依赖清单(pom.xml)

为了避免依赖冲突,这里提供完整的依赖清单:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.22.RELEASE</version>
    </parent>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring Boot Actuator 1.x(监控) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- Redis(注意:1.x 用 data-redis,不是 starter-redis) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>1.5.22.RELEASE</version>
        </dependency>
        
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        
        <!-- Druid 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>
        
        <!-- Caffeine Cache(本地缓存,性能优于 Guava Cache) -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.8.8</version>
        </dependency>
        
        <!-- Guava(仅用于布隆过滤器,Caffeine 不提供布隆过滤器功能) -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>27.1-jre</version>
        </dependency>
        
        <!-- Prometheus 监控(兼容 1.x) -->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <version>1.3.9</version>
        </dependency>
        
        <!-- Commons Pool 2(对象池) -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.2</version>
        </dependency>
    </dependencies>
</project>

注意: 如果项目中使用的是 spring-boot-starter-redis(旧版本),建议升级到 spring-boot-starter-data-redis,后者在 1.5.x 中更稳定。

二、调优前的性能诊断:找到真正的瓶颈

2.1 问题现象:接口慢得像蜗牛

先来看看优化前的性能数据:

指标 优化前 优化后 提升幅度
平均响应时间 3000ms 180ms 优化后较优化前降低 94%
P99 响应时间 8000ms 350ms 优化后较优化前降低 95.6%
QPS 100 1000 优化后较优化前提升 900%
错误率 5% 0.1% 优化后较优化前降低 98%
CPU 使用率 85% 45% 优化后较优化前降低 47%

看到这个数据,我当时第一反应是:这接口到底在干什么?为什么这么慢?

2.2 诊断工具选型:Spring Boot 1.x 的兼容性坑

在开始诊断之前,我先踩了一个大坑:想用 Spring Boot Actuator 2.x 的新功能,结果发现项目是 1.5.22,很多新特性都用不了。所以这里先给大家列一下 Spring Boot 1.x 能用的诊断工具:

推荐工具清单(Spring Boot 1.x 兼容):

  • Spring Boot Actuator 1.x:查看接口耗时分布、JVM 指标
  • Arthas 1.3.1:方法级性能分析、线程堆栈分析
  • JVisualVM:JVM 内存、GC 分析
  • MySQL 慢查询日志:SQL 性能分析
  • JMeter:压测工具

不推荐(版本不兼容):

  • Spring Boot Actuator 2.x(需要 Spring Boot 2.x)
  • Micrometer 1.4+(1.x 版本支持有限,建议用 1.3.9)
  • Spring Boot Admin 2.x

2.3 ✅ 第一步:用 Actuator 定位慢接口

⚠️ 关键修正: Spring Boot 1.x 的 Actuator 配置与 2.x 完全不同,切勿混用!

首先,我在 application.properties 中开启了 Actuator 的监控端点(Spring Boot 1.5.x 专属配置):

properties 复制代码
# Spring Boot 1.x Actuator 配置(1.5.22.RELEASE 验证可用)
# 注意:1.x 与 2.x Actuator 配置差异极大,切勿混用!

# 关闭安全验证(生产环境建议开启并配置用户名密码)
management.security.enabled=false

# 开启监控端点
endpoints.health.sensitive=false
endpoints.metrics.enabled=true
endpoints.prometheus.enabled=true

# 开启 HTTP 追踪(用于查看接口耗时)
management.trace.http.enabled=true

补充说明: 1.x 版本中,端点配置使用 endpoints.xxx 格式,而不是 2.x 的 management.endpoints.web.exposure.include。如果使用 2.x 语法会报错。

然后访问 http://localhost:8080/metrics,看到了接口的耗时分布:

erlang 复制代码
GET /api/order/query - 平均耗时: 2850ms
  - 数据库查询: 2200ms (77%)
  - 业务逻辑: 450ms (16%)
  - 序列化: 200ms (7%)

看到这个数据,我心里有数了:数据库查询是最大的瓶颈,占了 77% 的时间

2.4 ✅ 第二步:用 Arthas 深入方法级分析

接下来,我用 Arthas 来查看具体是哪个方法在拖慢性能。这里又踩了一个坑:Arthas 在 Spring Boot 1.x 中启动时,如果端口被占用会报错。

Arthas 启动命令(解决端口冲突):

bash 复制代码
# 如果 3658 端口被占用,可以指定其他端口
java -jar arthas-boot.jar --target-ip 0.0.0.0 --telnet-port 3658 --http-port 8563

# 操作说明:执行后会列出所有 Java 进程,输入进程号,选择目标应用即可 attach

启动后,我用 trace 命令追踪订单查询方法:

bash 复制代码
# 追踪 OrderService.queryOrder 方法
trace com.example.service.OrderService queryOrder '#cost > 100'

输出结果让我发现了问题:

scss 复制代码
+---[100.00% 2850ms ] com.example.service.OrderService.queryOrder()
    +---[77.19% 2200ms ] com.example.dao.OrderMapper.selectOrder() 
    |   +---[45.23% 1295ms ] com.example.dao.OrderMapper.selectOrderItems()  # 循环查询!
    |   +---[31.96% 905ms  ] com.example.dao.OrderMapper.selectOrderDetail()
    +---[15.79% 450ms  ] com.example.service.OrderService.processOrderData()
    +---[7.02%  200ms  ] com.example.util.JsonUtil.toJson()

关键发现:

  1. selectOrderItems() 方法耗时 1295ms,而且是在循环中调用的(N+1 查询问题
  2. selectOrderDetail() 方法也慢,可能是 SQL 没有走索引

术语解释:N+1 查询问题

  • 通俗理解: 查 1 个订单(1 次查询)+ 循环查 10 个订单项(10 次查询)= 11 次查询,即 N+1(N=10)
  • 问题: 如果订单有 100 个商品,就要执行 101 次数据库查询,性能极差

2.5 ✅ 第三步:分析 MySQL 慢查询日志

开启 MySQL 慢查询日志:

sql 复制代码
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过 1 秒的查询记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow-query.log';

查看慢查询日志,发现了几个问题 SQL:

sql 复制代码
-- 问题 SQL 1:全表扫描,没有走索引
SELECT * FROM order_item WHERE order_id = 12345;
-- 执行时间: 1200ms,扫描行数: 50000

-- 问题 SQL 2:关联查询没有优化(注意:order 是 MySQL 关键字,需要加反引号)
SELECT o.*, u.name, u.phone 
FROM `order` o 
LEFT JOIN `user` u ON o.user_id = u.id 
WHERE o.order_no = 'ORD20240101001';
-- 执行时间: 800ms,使用了临时表

⚠️ SQL 关键字修正: order 是 MySQL 关键字,表名必须加反引号 order,否则可能报错或执行异常。

2.6 第四步:JVM 问题排查

用 JVisualVM 连接应用,发现 GC 非常频繁:

diff 复制代码
GC 统计(优化前):
- Young GC 频率: 每 10 秒 1 次
- Full GC 频率: 每 2 分钟 1 次
- Full GC 平均耗时: 800ms
- 堆内存使用率: 85%(接近 OOM 风险)

问题分析:

  • 默认堆内存太小(只有 1g)
  • 使用的是串行 GC(-XX:+UseSerialGC),不适合生产环境
  • 对象创建频繁,导致 Young GC 压力大

2.7 诊断总结:性能瓶颈清单

经过一轮诊断,我整理出了性能瓶颈清单:

瓶颈类型 影响程度 预估优化空间
SQL 查询慢(N+1 问题) ⭐⭐⭐⭐⭐ 70%
缺少缓存 ⭐⭐⭐⭐ 60%
JVM 配置不合理 ⭐⭐⭐ 30%
Tomcat 线程池配置低 ⭐⭐ 20%
代码层面优化 ⭐⭐ 15%

接下来,我就按照"效果从易到难、成本从低到高"的原则,逐个击破这些瓶颈。

三、核心调优实战:分模块击破

3.1 ✅ SQL 层优化:见效最快的优化

问题 1:N+1 查询问题

问题原因: 原来的代码是这样的:

java 复制代码
// OrderService.java(优化前)
public OrderVO queryOrder(String orderNo) {
    // 第一次查询:获取订单基本信息
    Order order = orderMapper.selectByOrderNo(orderNo);
    
    // 第二次查询:循环查询订单商品(N+1 问题!)
    // 如果订单有 10 个商品,这里就要执行 10 次数据库查询
    List<OrderItem> items = new ArrayList<>();
    for (Long itemId : order.getItemIds()) {
        OrderItem item = orderItemMapper.selectById(itemId);  // 循环查询数据库
        items.add(item);
    }
    
    // 第三次查询:查询订单详情
    OrderDetail detail = orderDetailMapper.selectByOrderId(order.getId());
    
    return buildOrderVO(order, items, detail);
}

如果订单有 10 个商品,这个接口就要执行 1 + 10 + 1 = 12 次数据库查询!

解决方案:批量查询代替循环查询

java 复制代码
// OrderService.java(优化后)
public OrderVO queryOrder(String orderNo) {
    // 第一次查询:获取订单基本信息
    Order order = orderMapper.selectByOrderNo(orderNo);
    if (order == null) {
        return null;
    }
    
    // 第二次查询:批量查询订单商品(一次查询搞定)
    // 核心优化:批量查询替代循环查询,从 N+1 次查询降为 1 次
    List<OrderItem> items = orderItemMapper.selectByIds(order.getItemIds());
    
    // 第三次查询:查询订单详情
    OrderDetail detail = orderDetailMapper.selectByOrderId(order.getId());
    
    return buildOrderVO(order, items, detail);
}

MyBatis 批量查询实现:

xml 复制代码
<!-- OrderItemMapper.xml -->
<select id="selectByIds" resultType="OrderItem">
    SELECT * FROM order_item 
    WHERE id IN
    <!-- 核心优化:使用 foreach 实现批量查询 -->
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

效果对比:

  • 优化前:12 次数据库查询,总耗时 2200ms
  • 优化后:3 次数据库查询,总耗时 800ms
  • 提升:63.6%
问题 2:SQL 没有走索引

问题原因:

sql 复制代码
-- 慢查询 SQL
SELECT * FROM order_item WHERE order_id = 12345;
-- 执行计划:全表扫描,扫描了 50000 行

解决方案:添加联合索引

sql 复制代码
-- 添加联合索引(订单ID + 商品ID)
CREATE INDEX idx_order_item_order_id ON order_item(order_id, item_id);

-- 优化后的执行计划:使用索引,只扫描 10 行
EXPLAIN SELECT * FROM order_item WHERE order_id = 12345;
-- type: ref, rows: 10

效果对比:

  • 优化前:全表扫描 50000 行,耗时 1200ms
  • 优化后:索引扫描 10 行,耗时 15ms
  • 提升:98.75%
问题 3:关联查询优化

问题原因:

sql 复制代码
-- 原来的关联查询(注意:order 是关键字,需要加反引号)
SELECT o.*, u.name, u.phone 
FROM `order` o 
LEFT JOIN `user` u ON o.user_id = u.id 
WHERE o.order_no = 'ORD20240101001';
-- 使用了临时表,耗时 800ms

解决方案:优化 JOIN 顺序和索引

sql 复制代码
-- 1. 确保 order_no 有唯一索引
CREATE UNIQUE INDEX uk_order_no ON `order`(order_no);

-- 2. 确保 user.id 有主键索引(通常已有)

-- 3. 优化后的 SQL(MySQL 会自动优化 JOIN 顺序)
SELECT o.*, u.name, u.phone 
FROM `order` o 
LEFT JOIN `user` u ON o.user_id = u.id 
WHERE o.order_no = 'ORD20240101001';
-- 执行计划:先通过索引找到 order,再 JOIN user,耗时 50ms

效果对比:

  • 优化前:使用临时表,耗时 800ms
  • 优化后:索引查询 + JOIN,耗时 50ms
  • 提升:93.75%
问题 4:避免 SELECT *

优化前:

java 复制代码
// 查询所有字段,包括不必要的大字段
SELECT * FROM `order` WHERE order_no = ?

优化后:

java 复制代码
// 只查询需要的字段
SELECT id, order_no, user_id, amount, status, create_time 
FROM `order` 
WHERE order_no = ?

效果: 减少网络传输和内存占用,响应时间减少约 10-15%

问题 5:数据源连接池优化

Spring Boot 1.x 中,数据源配置在 application.properties

properties 复制代码
# Spring Boot 1.x 数据源配置(Druid)
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

# 连接池配置(关键参数)
spring.datasource.druid.initial-size=10
spring.datasource.druid.min-idle=10
spring.datasource.druid.max-active=50
spring.datasource.druid.max-wait=3000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.validation-query=SELECT 1
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false

# 开启 SQL 监控(用于诊断)
spring.datasource.druid.filters=stat,wall,log4j

参数说明:

  • max-active=50:最大连接数,根据服务器配置和并发量调整
  • max-wait=3000:获取连接的最大等待时间(毫秒)
  • time-between-eviction-runs-millis:连接池空闲连接回收的时间间隔

SQL 层优化总结:

  • 响应时间:2200ms → 600ms
  • 提升:72.7%

3.2 ✅ 缓存层优化:性价比最高的优化

问题:接口完全没有缓存

问题原因: 每次查询订单都要访问数据库,即使同一个订单被查询 100 次,也要执行 100 次 SQL。

解决方案:二级缓存架构(本地缓存 + 分布式缓存)

我设计了一个二级缓存架构:

  • L1 缓存(本地缓存):用 Caffeine Cache,缓存热点数据,响应时间 < 1ms(Caffeine 是 Guava Cache 的高性能替代品,性能提升约 30%)
  • L2 缓存(分布式缓存):用 Redis,缓存所有查询结果,响应时间 < 10ms
第一步:集成 Redis(Spring Boot 1.x 版本)

⚠️ 关键修正: Spring Boot 1.5.x 推荐使用 spring-boot-starter-data-redis,而不是 spring-boot-starter-redis(后者已废弃)。

xml 复制代码
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>1.5.22.RELEASE</version>
</dependency>
properties 复制代码
# application.properties
# Spring Boot 1.x Redis 配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
spring.redis.timeout=3000
spring.redis.pool.max-active=50
spring.redis.pool.max-idle=10
spring.redis.pool.min-idle=5
spring.redis.pool.max-wait=3000

Redis 配置类(Spring Boot 1.x):

java 复制代码
@Configuration
@EnableCaching  // 开启缓存支持
public class RedisConfig {
    
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        // 设置缓存过期时间:30 分钟
        cacheManager.setDefaultExpiration(1800);
        return cacheManager;
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用 Jackson2JsonRedisSerializer 序列化
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        
        // ⚠️ 关键修正:enableDefaultTyping 已废弃,使用替代方案
        // 替代方案:手动指定序列化类型,避免安全风险
        om.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance, 
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );
        serializer.setObjectMapper(om);
        
        template.setValueSerializer(serializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

补充说明: enableDefaultTyping 方法在 Jackson 2.10+ 中已废弃,存在安全风险。使用 activateDefaultTyping 替代,并指定 LaissezFaireSubTypeValidator 验证器。

第二步:实现二级缓存工具类
java 复制代码
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

@Component
public class TwoLevelCacheManager {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // L1 缓存:Caffeine Cache(本地缓存,性能优于 Guava Cache)
    private final Cache<String, Object> localCache = Caffeine.newBuilder()
            .maximumSize(1000)  // 最多缓存 1000 个 key
            .expireAfterWrite(5, TimeUnit.MINUTES)  // 5 分钟过期
            .recordStats()  // 开启统计
            .build();
    
    /**
     * 获取缓存(先查 L1,再查 L2)
     */
    public <T> T get(String key, Class<T> type) {
        // 1. 先查本地缓存
        Object value = localCache.getIfPresent(key);
        if (value != null) {
            return (T) value;
        }
        
        // 2. 再查 Redis
        value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            // 回填到本地缓存
            localCache.put(key, value);
            return (T) value;
        }
        
        return null;
    }
    
    /**
     * 设置缓存(同时写入 L1 和 L2)
     */
    public void put(String key, Object value, long timeout, TimeUnit unit) {
        // 写入 Redis
        redisTemplate.opsForValue().set(key, value, timeout, unit);
        // 写入本地缓存
        localCache.put(key, value);
    }
    
    /**
     * 删除缓存(同时删除 L1 和 L2)
     */
    public void evict(String key) {
        localCache.invalidate(key);
        redisTemplate.delete(key);
    }
}

补充说明:

  • 为什么选择 Caffeine 而不是 Guava Cache?
    • Caffeine 是 Guava Cache 的高性能重写版本,性能提升约 30%
    • Caffeine 使用更高效的算法(如 W-TinyLFU),命中率更高
    • API 与 Guava Cache 兼容,迁移成本低
    • 在 Spring Boot 2.x+ 中,Spring Cache 默认使用 Caffeine
  • 为什么还需要 Guava? Guava 的布隆过滤器功能 Caffeine 不提供,所以需要同时引入 Guava 依赖用于布隆过滤器
第三步:在 Service 层使用缓存
java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private TwoLevelCacheManager cacheManager;
    
    private static final String CACHE_KEY_PREFIX = "order:query:";
    private static final long CACHE_TIMEOUT = 30;  // 30 分钟
    
    public OrderVO queryOrder(String orderNo) {
        String cacheKey = CACHE_KEY_PREFIX + orderNo;
        
        // 1. 先查缓存
        OrderVO cached = cacheManager.get(cacheKey, OrderVO.class);
        if (cached != null) {
            return cached;
        }
        
        // 2. 缓存未命中,查询数据库
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order == null) {
            return null;
        }
        
        List<OrderItem> items = orderItemMapper.selectByIds(order.getItemIds());
        OrderDetail detail = orderDetailMapper.selectByOrderId(order.getId());
        
        OrderVO orderVO = buildOrderVO(order, items, detail);
        
        // 3. 写入缓存
        cacheManager.put(cacheKey, orderVO, CACHE_TIMEOUT, TimeUnit.MINUTES);
        
        return orderVO;
    }
}
第四步:防止缓存穿透和击穿

缓存穿透: 查询不存在的数据,每次都穿透到数据库

术语解释:缓存穿透

  • 通俗理解: 用户查询不存在的订单号(如 ORD999999),缓存中没有,每次都绕过缓存查数据库,导致数据库压力增大
  • 解决方案: 布隆过滤器 + 空值缓存

解决方案:布隆过滤器 + 空值缓存

java 复制代码
@Service
public class OrderService {
    
    // 使用 Guava 的布隆过滤器
    private final BloomFilter<String> orderNoBloomFilter = BloomFilter.create(
            Funnels.stringFunnel(Charset.defaultCharset()),
            100000,  // 预期插入数量
            0.01     // 误判率
    );
    
    public OrderVO queryOrder(String orderNo) {
        String cacheKey = CACHE_KEY_PREFIX + orderNo;
        
        // 1. 布隆过滤器判断(核心优化:快速过滤不存在的订单号)
        if (!orderNoBloomFilter.mightContain(orderNo)) {
            // 肯定不存在,直接返回 null
            return null;
        }
        
        // 2. 查缓存
        OrderVO cached = cacheManager.get(cacheKey, OrderVO.class);
        if (cached != null) {
            // 如果是空值标记,返回 null
            if (cached == NULL_OBJECT) {
                return null;
            }
            return cached;
        }
        
        // 3. 查数据库
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order == null) {
            // 缓存空值,防止穿透(设置较短的过期时间)
            cacheManager.put(cacheKey, NULL_OBJECT, 5, TimeUnit.MINUTES);
            return null;
        }
        
        // 4. 构建结果并缓存
        OrderVO orderVO = buildOrderVO(order, items, detail);
        cacheManager.put(cacheKey, orderVO, CACHE_TIMEOUT, TimeUnit.MINUTES);
        
        // 5. 加入布隆过滤器
        orderNoBloomFilter.put(orderNo);
        
        return orderVO;
    }
    
    private static final OrderVO NULL_OBJECT = new OrderVO();  // 空值标记
}

缓存击穿: 热点 key 过期,大量请求同时访问数据库

术语解释:缓存击穿

  • 通俗理解: 热点订单(如双11爆款商品订单)的缓存过期了,瞬间有 1000 个请求同时查询数据库,导致数据库压力激增
  • 解决方案: 分布式锁,只允许一个线程查询数据库,其他线程等待

解决方案:分布式锁(Redis 实现,健壮版本)

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private TwoLevelCacheManager cacheManager;
    
    public OrderVO queryOrder(String orderNo) {
        String cacheKey = CACHE_KEY_PREFIX + orderNo;
        
        // 1. 先查缓存
        OrderVO cached = cacheManager.get(cacheKey, OrderVO.class);
        if (cached != null && cached != NULL_OBJECT) {
            return cached;
        }
        
        // 2. 缓存未命中,尝试获取分布式锁
        String lockKey = "lock:" + cacheKey;
        // ⚠️ 核心优化:使用 UUID 标识锁归属,避免误删其他线程的锁
        String lockValue = UUID.randomUUID().toString();
        Boolean lockAcquired = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
        
        if (lockAcquired != null && lockAcquired) {
            try {
                // 双重检查:再次查缓存(可能其他线程已经写入)
                cached = cacheManager.get(cacheKey, OrderVO.class);
                if (cached != null && cached != NULL_OBJECT) {
                    return cached;
                }
                
                // 查询数据库
                OrderVO orderVO = queryFromDatabase(orderNo);
                
                // 写入缓存
                if (orderVO != null) {
                    cacheManager.put(cacheKey, orderVO, CACHE_TIMEOUT, TimeUnit.MINUTES);
                } else {
                    cacheManager.put(cacheKey, NULL_OBJECT, 5, TimeUnit.MINUTES);
                }
                
                return orderVO;
            } finally {
                // ⚠️ 核心优化:释放锁时校验归属,避免误删其他线程的锁
                String currentLockValue = (String) redisTemplate.opsForValue().get(lockKey);
                if (lockValue.equals(currentLockValue)) {
                    redisTemplate.delete(lockKey);
                }
            }
        } else {
            // 获取锁失败,等待一小段时间后重试
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return queryOrder(orderNo);  // 递归重试
        }
    }
}

补充说明:

  • 原生 Redis 锁适合中小流量(QPS < 5000),高并发场景建议使用 Redisson 1.x 版本(支持自动续期、可重入锁等高级特性)
  • Redisson 1.x 兼容 Spring Boot 1.x: redisson-spring-boot-starter 3.8.2 版本
第五步:缓存更新策略

问题: 订单数据变更后(如修改订单状态),缓存未失效,导致脏数据

解决方案: 在数据更新时同步失效缓存

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private TwoLevelCacheManager cacheManager;
    
    /**
     * 更新订单状态(需同步失效缓存)
     */
    @Transactional
    public void updateOrderStatus(String orderNo, Integer status) {
        // 1. 更新数据库
        orderMapper.updateStatus(orderNo, status);
        
        // 2. 失效缓存(核心优化:缓存更新需与事务同步)
        String cacheKey = CACHE_KEY_PREFIX + orderNo;
        cacheManager.evict(cacheKey);
    }
}

补充说明: 缓存更新需与事务同步,避免更新后缓存未失效导致脏数据。如果使用 @Transactional,建议在事务提交后失效缓存(使用 @TransactionalEventListener 监听事务提交事件)。

缓存层优化总结:

  • 响应时间:600ms → 150ms(缓存命中时 < 10ms)
  • 缓存命中率:85%
  • 提升:75%

3.3 ✅ JVM 与容器调优:基础但关键

问题:默认 JVM 参数不合理

问题原因: Spring Boot 1.x 默认的 JVM 参数比较保守,不适合生产环境:

  • 堆内存默认只有 512MB(通过 -Xmx 设置)
  • 使用串行 GC(-XX:+UseSerialGC),GC 停顿时间长
  • 没有设置新生代大小,导致频繁 Full GC
解决方案:优化 JVM 参数

启动脚本优化(start.sh):

bash 复制代码
#!/bin/bash

# ⚠️ 场景化配置说明:
# -Xms2g -Xmx2g:适用于 4 核 8G 服务器
# 2 核 4G 服务器建议调整为:-Xms1g -Xmx1g
# 8 核 16G 服务器可调整为:-Xms4g -Xmx4g

JAVA_OPTS="-server \
  -Xms2g \
  -Xmx2g \
  -XX:NewRatio=2 \
  -XX:SurvivorRatio=8 \
  -XX:+UseParallelGC \
  -XX:ParallelGCThreads=4 \
  -XX:MaxGCPauseMillis=200 \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/var/log/heapdump.hprof \
  -XX:+PrintGCDetails \
  -XX:+PrintGCDateStamps \
  -Xloggc:/var/log/gc.log \
  -XX:+UseGCLogFileRotation \
  -XX:NumberOfGCLogFiles=5 \
  -XX:GCLogFileSize=20M"

java $JAVA_OPTS -jar app.jar

参数说明:

  • -Xms2g -Xmx2g:堆内存初始值和最大值都是 2GB(避免动态扩容)
    • 场景化说明: 适用于 4 核 8G 服务器,2 核 4G 服务器建议调整为 -Xms1g -Xmx1g
  • -XX:NewRatio=2:新生代与老年代比例 1:2(新生代占 1/3)
  • -XX:SurvivorRatio=8:Eden 区与 Survivor 区比例 8:1
  • -XX:+UseParallelGC:使用并行 GC(适合 Spring Boot 1.x,比串行 GC 快)
  • -XX:MaxGCPauseMillis=200:最大 GC 停顿时间 200ms(注意:这是目标停顿时间,非强制值

补充说明:

  • JDK 8u20+ 可尝试 G1 GC(-XX:+UseG1GC,但需测试稳定性,Spring Boot 1.x 项目谨慎使用

  • G1 GC 参数示例(仅供参考,需压测验证):

    bash 复制代码
    -XX:+UseG1GC \
    -XX:MaxGCPauseMillis=200 \
    -XX:G1HeapRegionSize=16m
Tomcat 线程池优化

Spring Boot 1.x 内置的是 Tomcat 8,线程池配置在 application.properties

properties 复制代码
# Tomcat 线程池配置
server.tomcat.max-threads=200
server.tomcat.min-spare-threads=20
server.tomcat.accept-count=100
server.tomcat.max-connections=10000
server.tomcat.connection-timeout=20000

# ⚠️ 线程池配置说明:
# max-threads 建议值:
# - CPU 密集型:max-threads = (CPU 核心数 * 2) + 1
#   比如 4 核 CPU,建议设置为 9-20 之间
# - IO 密集型:max-threads = (CPU 核心数 * 10)
#   比如 4 核 CPU,建议设置为 40-80 之间
# 注意:不要设置太大,否则会导致 OOM

参数说明:

  • max-threads=200:最大工作线程数(根据服务器配置调整,不要盲目设大)
  • min-spare-threads=20:最小空闲线程数
  • accept-count=100:等待队列长度(超过这个数会拒绝连接)
  • max-connections=10000:最大连接数

踩坑提醒: 我之前把 max-threads 设成了 1000,结果服务器直接 OOM 了。后来才知道,线程数太多会导致上下文切换开销增大,反而降低性能。

效果对比

GC 统计(优化后):

diff 复制代码
GC 统计(优化后):
- Young GC 频率: 每 30 秒 1 次(优化前:每 10 秒 1 次)
- Full GC 频率: 每 10 分钟 1 次(优化前:每 2 分钟 1 次)
- Full GC 平均耗时: 200ms(优化前:800ms)
- 堆内存使用率: 60%(优化前:85%)

JVM 与容器调优总结:

  • Full GC 频率降低 80%
  • Full GC 耗时降低 75%
  • 整体响应时间提升:15-20%

3.4 代码与框架调优:细节决定成败

问题 1:对象频繁创建

问题原因: 在循环中频繁创建对象,导致 GC 压力大:

java 复制代码
// 优化前:每次循环都创建新对象
public List<OrderVO> queryOrders(List<String> orderNos) {
    List<OrderVO> result = new ArrayList<>();
    for (String orderNo : orderNos) {
        OrderVO vo = new OrderVO();  // 频繁创建对象
        // ... 设置属性
        result.add(vo);
    }
    return result;
}

解决方案:使用对象池(Commons Pool 2)

xml 复制代码
<!-- pom.xml -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.2</version>
</dependency>
java 复制代码
@Component
public class OrderVOPool {
    
    private final GenericObjectPool<OrderVO> pool;
    
    public OrderVOPool() {
        PooledObjectFactory<OrderVO> factory = new BasePooledObjectFactory<OrderVO>() {
            @Override
            public OrderVO create() {
                return new OrderVO();
            }
            
            @Override
            public PooledObject<OrderVO> wrap(OrderVO obj) {
                return new DefaultPooledObject<>(obj);
            }
            
            @Override
            public void passivateObject(PooledObject<OrderVO> p) {
                // 重置对象状态
                OrderVO vo = p.getObject();
                vo.setId(null);
                vo.setOrderNo(null);
                // ... 重置其他字段
            }
        };
        
        GenericObjectPoolConfig<OrderVO> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(100);
        config.setMaxIdle(20);
        config.setMinIdle(5);
        
        this.pool = new GenericObjectPool<>(factory, config);
    }
    
    public OrderVO borrowObject() throws Exception {
        return pool.borrowObject();
    }
    
    public void returnObject(OrderVO obj) {
        pool.returnObject(obj);
    }
}

使用对象池:

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private OrderVOPool orderVOPool;
    
    public List<OrderVO> queryOrders(List<String> orderNos) {
        List<OrderVO> result = new ArrayList<>();
        for (String orderNo : orderNos) {
            try {
                OrderVO vo = orderVOPool.borrowObject();  // 从对象池获取
                // ... 设置属性
                result.add(vo);
            } catch (Exception e) {
                // 如果对象池获取失败,创建新对象
                result.add(new OrderVO());
            }
        }
        return result;
    }
}

注意: 对象池适合频繁创建、生命周期短的对象。对于简单对象(如 String、Integer),对象池的开销可能大于直接创建,不建议使用。

问题 2:Spring Bean 作用域不合理

问题原因: 有些 Service 使用了默认的单例模式,但内部有状态,导致线程安全问题:

java 复制代码
// 问题代码:单例 Bean 但有状态
@Service
public class OrderService {
    private OrderVO currentOrder;  // 有状态,线程不安全!
    
    public OrderVO queryOrder(String orderNo) {
        currentOrder = orderMapper.selectByOrderNo(orderNo);  // 多线程并发会覆盖
        return currentOrder;
    }
}

解决方案:

  1. 无状态 Bean 用单例(默认)
  2. 有状态 Bean 用原型模式(@Scope("prototype")
java 复制代码
// 优化后:无状态设计
@Service
public class OrderService {
    // 移除有状态字段,所有方法都是无状态的
    
    public OrderVO queryOrder(String orderNo) {
        OrderVO order = orderMapper.selectByOrderNo(orderNo);  // 局部变量,线程安全
        return order;
    }
}
问题 3:懒加载优化

问题原因: Spring Boot 启动时,所有单例 Bean 都会立即初始化,导致启动慢:

java 复制代码
// 优化前:启动时就初始化
@Service
public class HeavyService {
    public HeavyService() {
        // 初始化耗时操作
        initHeavyResource();  // 耗时 5 秒
    }
}

解决方案:使用 @Lazy 注解

java 复制代码
// 优化后:延迟初始化
@Service
@Lazy
public class HeavyService {
    public HeavyService() {
        // 只有在第一次使用时才初始化
        initHeavyResource();
    }
}

代码与框架调优总结:

  • 对象创建开销降低 30%
  • 启动时间减少 20%
  • 整体响应时间提升:10-15%

四、效果验证:数据说话

4.1 优化前后对比

经过 3 天的调优,最终的性能数据如下:

指标 优化前 优化后 提升幅度
平均响应时间 3000ms 180ms 优化后较优化前降低 94%
P50 响应时间 2800ms 150ms 优化后较优化前降低 94.6%
P95 响应时间 5000ms 250ms 优化后较优化前降低 95%
P99 响应时间 8000ms 350ms 优化后较优化前降低 95.6%
QPS 100 1000 优化后较优化前提升 900%
错误率 5% 0.1% 优化后较优化前降低 98%
CPU 使用率 85% 45% 优化后较优化前降低 47%
内存使用率 85% 60% 优化后较优化前降低 29%
Full GC 频率 每 2 分钟 每 10 分钟 优化后较优化前降低 80%

4.2 压测验证(JMeter)

压测配置:

  • 线程数:200
  • 循环次数:1000
  • 接口:GET /api/order/query?orderNo=ORD20240101001

JMeter 压测计划配置(关键步骤):

  1. 创建线程组:

    • 线程数:200
    • Ramp-up 时间:10 秒(逐步增加并发)
    • 循环次数:1000
  2. 创建 HTTP 请求:

    • 协议:http
    • 服务器名称:localhost
    • 端口:8080
    • 路径:/api/order/query
    • 参数:orderNo=ORD20240101001
  3. 添加聚合报告:

    • 查看平均响应时间、最大响应时间、错误率等指标

压测结果:

diff 复制代码
优化前:
- 总请求数:200,000
- 成功请求:190,000(95%)
- 平均响应时间:2850ms
- 最大响应时间:12000ms
- 错误率:5%

优化后:
- 总请求数:200,000
- 成功请求:199,800(99.9%)
- 平均响应时间:175ms
- 最大响应时间:450ms
- 错误率:0.1%

如何通过压测验证优化效果:

  1. 对比优化前后的平均响应时间、P95/P99 响应时间
  2. 观察错误率是否降低
  3. 监控服务器 CPU、内存使用率是否降低
  4. 检查数据库连接池是否还有等待连接的情况

4.3 线上监控(Grafana + Prometheus)

Spring Boot 1.x 集成 Prometheus 需要额外配置:

xml 复制代码
<!-- pom.xml -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.3.9</version>  <!-- 兼容 Spring Boot 1.x 的版本 -->
</dependency>
properties 复制代码
# application.properties(Spring Boot 1.x 配置)
# 注意:1.x 版本使用 endpoints.prometheus.enabled,不是 management.metrics.export.prometheus.enabled
endpoints.prometheus.enabled=true

访问 http://localhost:8080/prometheus 可以看到 Prometheus 格式的指标(注意:1.x 版本路径是 /prometheus,不是 /actuator/prometheus)。

监控指标示例:

ini 复制代码
# 接口响应时间
http_server_requests_seconds_sum{method="GET",uri="/api/order/query"} 175.5
http_server_requests_seconds_count{method="GET",uri="/api/order/query"} 1000

# JVM 内存使用
jvm_memory_used_bytes{area="heap"} 1200000000
jvm_memory_max_bytes{area="heap"} 2000000000

# GC 统计
jvm_gc_pause_seconds_sum{action="end_of_minor_gc"} 2.5
jvm_gc_pause_seconds_count{action="end_of_minor_gc"} 20

Grafana Dashboard 配置步骤:

  1. 添加 Prometheus 数据源:

    • URL: http://localhost:9090(Prometheus 服务地址)
  2. 创建 Dashboard,添加关键指标:

    • 接口响应时间: rate(http_server_requests_seconds_sum{uri="/api/order/query"}[5m])
    • QPS: rate(http_server_requests_seconds_count{uri="/api/order/query"}[5m])
    • JVM 堆内存使用率: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} * 100
    • GC 频率: rate(jvm_gc_pause_seconds_count[5m])
  3. 设置告警规则:

    • 响应时间 > 500ms 时告警
    • 错误率 > 1% 时告警

如何实时监控优化后的性能:

  • 在 Grafana 中查看接口响应时间曲线,应该稳定在 150-200ms 之间
  • 观察 QPS 曲线,应该稳定在 1000 左右
  • 检查 GC 频率和耗时,应该明显降低

五、调优风险控制:回滚与灰度策略

5.1 SQL 索引优化回滚方案

风险: 添加索引可能影响写入性能,或索引创建失败导致表锁

回滚方案:

sql 复制代码
-- 添加索引前备份表结构
CREATE TABLE order_item_backup AS SELECT * FROM order_item WHERE 1=0;

-- 如果优化失败,执行回滚
DROP INDEX idx_order_item_order_id ON order_item;

灰度策略:

  • 先在测试环境验证索引效果
  • 生产环境在业务低峰期(如凌晨 2-4 点)执行索引创建
  • 监控索引创建期间的数据库性能,如有异常立即回滚

5.2 缓存优化回滚方案

风险: 缓存配置错误导致数据不一致,或缓存穿透导致数据库压力增大

回滚方案:

java 复制代码
// 临时关闭缓存:注释掉缓存相关代码,恢复数据库查询模式
// @Cacheable(value = "order", key = "#orderNo")  // 临时注释
public OrderVO queryOrder(String orderNo) {
    // 直接查询数据库,不查缓存
    return orderMapper.selectByOrderNo(orderNo);
}

灰度策略:

  • 生产环境调优先小流量(10%)验证 1 小时,无异常再全量发布
  • 使用 Spring Cloud 的灰度发布功能,或 Nginx 的流量切分功能

5.3 JVM 参数调优回滚方案

风险: JVM 参数设置不当导致 OOM 或 GC 频繁

回滚方案:

bash 复制代码
# 恢复默认 JVM 参数
java -jar app.jar
# 或使用备份的启动脚本
./start_backup.sh

灰度策略:

  • 先在测试环境压测验证 JVM 参数效果
  • 生产环境逐步调整参数(如先调整堆内存,观察 1 天后再调整 GC 参数)

六、避坑指南与总结

6.1 调优禁忌:这些坑我帮你踩过了

禁忌 1:盲目调参

错误做法: 看到接口慢,直接调大 max-threads 到 1000,结果服务器 OOM。

正确做法: 先诊断瓶颈,再针对性优化。比如:

  • SQL 慢 → 优化 SQL 和索引
  • 缓存未命中 → 优化缓存策略
  • 线程池满 → 调整线程池参数
禁忌 2:过度优化

错误做法: 为了追求极致性能,把所有数据都缓存,结果内存爆了。

正确做法: 遵循"二八定律":优化 20% 的关键代码,解决 80% 的性能问题。

禁忌 3:忽略业务场景

错误做法: 看到别人用 Redis Cluster,自己也上,结果配置复杂,维护成本高。

正确做法: 根据业务场景选择方案:

  • 中小流量(QPS < 5000)→ 单机 Redis + 本地缓存
  • 大流量(QPS > 10000)→ Redis Cluster + 分库分表

6.2 经验总结:性能调优的核心原则

经过这次调优,我总结出了几个核心原则:

  1. 先诊断,后优化

    • 不要凭感觉优化,要用数据说话
    • 用工具(Arthas、Actuator、慢查询日志)定位瓶颈
  2. 优先优化瓶颈最大的环节

    • 按照"效果从易到难、成本从低到高"排序
    • SQL 优化通常见效最快,缓存优化性价比最高
  3. 小步快跑,持续验证

    • 每次优化后都要验证效果
    • 用压测工具验证,不要凭感觉
  4. 关注整体,不要局部优化

    • 不要只优化一个接口,要关注整个系统
    • 避免"按下葫芦浮起瓢"的问题

6.3 后续规划:还有哪些优化空间?

虽然这次优化已经达到了目标(200ms 以内),但还有进一步优化的空间:

  1. 分库分表

    • 当订单表数据量超过 1000 万时,考虑分库分表
    • Spring Boot 1.x 可以用 Sharding-JDBC 3.1.0(兼容 1.x)
  2. 引入消息队列削峰填谷

    • 高峰期订单查询压力大,可以用 MQ 异步处理
    • Spring Boot 1.x 可以用 RabbitMQ 或 RocketMQ
  3. CDN 加速静态资源

    • 如果接口返回的数据包含图片等静态资源,可以用 CDN 加速
  4. 升级到 Spring Boot 2.x(如果允许)

    • Spring Boot 2.x 在性能上有不少优化
    • 但升级需要评估成本和风险

6.4 进阶优化:读写分离(可选)

适用场景: 订单查询量远大于写入量,单库压力大

Spring Boot 1.x 集成 Sharding-JDBC 3.1.0 实现读写分离:

xml 复制代码
<!-- pom.xml -->
<dependency>
    <groupId>io.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>3.1.0</version>
</dependency>
properties 复制代码
# application.properties
# 主库(写)
sharding.jdbc.datasource.names=master,slave

sharding.jdbc.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.master.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.master.url=jdbc:mysql://localhost:3306/order_db
sharding.jdbc.datasource.master.username=root
sharding.jdbc.datasource.master.password=123456

# 从库(读)
sharding.jdbc.datasource.slave.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.slave.url=jdbc:mysql://localhost:3307/order_db
sharding.jdbc.datasource.slave.username=root
sharding.jdbc.datasource.slave.password=123456

# 读写分离规则
sharding.jdbc.config.masterslave.name=ms
sharding.jdbc.config.masterslave.master-data-source-name=master
sharding.jdbc.config.masterslave.slave-data-source-names=slave
sharding.jdbc.config.masterslave.load-balance-algorithm-type=round_robin

补充说明: 读写分离适合查询量远大于写入量的场景,但会增加系统复杂度,需评估维护成本。


写在最后

这次性能调优让我深刻体会到:性能优化不是一蹴而就的,而是一个持续的过程。从 3 秒到 200 毫秒,每一步都是踩坑、排查、优化的循环。

如果你也在 Spring Boot 1.x 项目中遇到性能问题,希望这篇文章能给你一些启发。记住:先诊断,后优化,用数据说话,小步快跑

你在 Spring Boot 1.x 调优中遇到过哪些坑?欢迎在评论区交流!


优化后的核心资源清单

工具下载地址

依赖版本清单(Spring Boot 1.5.22.RELEASE 兼容)

依赖 版本 说明
spring-boot-starter-data-redis 1.5.22.RELEASE Redis 支持
mybatis-spring-boot-starter 1.3.2 MyBatis 支持
druid-spring-boot-starter 1.1.23 连接池
caffeine 2.8.8 本地缓存(高性能)
guava 27.1-jre 布隆过滤器(Caffeine 不提供)
micrometer-registry-prometheus 1.3.9 Prometheus 监控
commons-pool2 2.6.2 对象池
sharding-jdbc-spring-boot-starter 3.1.0 读写分离(可选)
redisson-spring-boot-starter 3.8.2 分布式锁(可选)

相关资源

相关推荐
Hx_Ma1613 小时前
Map集合的5种遍历方式
java·前端·javascript
小手cool13 小时前
Java 列表中查找最小值和最大值最有效率的方法
java
惊讶的猫13 小时前
多线程同步问题及解决
java·开发语言·jvm
wfsm13 小时前
工厂模式创建动态代理实现类
java·开发语言
好好研究13 小时前
总结SSM设置欢迎页的方式
xml·java·后端·mvc
Hui Baby13 小时前
java -jar 启动原理
java·pycharm·jar
weixin_5112552113 小时前
更新jar内资源和代码
java·jar
木井巳14 小时前
【递归算法】验证二叉搜索树
java·算法·leetcode·深度优先·剪枝
不当菜虚困14 小时前
windows下HSDB导出class文件报错【java.io.IOException : 系统找不到指定的路径。】
java·开发语言
小马爱打代码14 小时前
Spring Boot:第三方 API 调用的企业级容错设计
java·spring boot·后端