Spring Boot: 性能优化

最后更新:2026-08-26

性能优化是系统工程——JVM、数据库、连接池三个维度,精准定位瓶颈才能对症下药。

1. 你将学到


2. 一个性能工程师的真实故事

(1) 痛点:API 慢如蜗牛

Charlie 报告 OrderFlow 订单列表 API 的 P99 延迟为 500ms,用户体验极差。Alice 分析发现:1)JVM 频繁 Full GC 暂停 200ms;2)JPA 查询订单列表时产生 N+1 问题(100 条订单 = 101 条 SQL);3)HikariCP 连接池大小不够,请求等连接超时。

(2) 系统优化的解法

逐层优化,精准打击瓶颈:

层级 瓶颈 优化方案
JVM Full GC 暂停 G1GC + 堆大小调优
连接池 连接不够用 HikariCP 参数优化
ORM N+1 查询 JOIN FETCH / EntityGraph
数据库 全表扫描 添加索引

(3) 收益

Alice 逐一优化后,P99 延迟从 500ms 降到 50ms,吞吐量提升 10 倍。


3. JVM 参数调优

(1) GC 算法对比

GC 算法 最大暂停时间 适用场景 JVM 参数
G1GC 10-200ms 通用(JDK 17 默认) -XX:+UseG1GC
ZGC < 1ms 低延迟(JDK 17+) -XX:+UseZGC
SerialGC 小堆(< 200MB) -XX:+UseSerialGC
ParallelGC 吞吐量优先 -XX:+UseParallelGC
100%
graph LR
    A["JVM Tuning"] --> B["Heap Size<br/>-Xms = -Xmx"]
    A --> C["GC Algorithm<br/>G1 / ZGC"]
    A --> D["GC Logging<br/>-Xlog:gc*"]
    B --> B1["Container: MaxRAMPercentage"]
    C --> C1["Default: G1GC"]
    C --> C2["Low latency: ZGC"]

▶ 示例: Docker 环境 JVM 配置

DOCKERFILE
ENTRYPOINT ["java", \
  "-XX:+UseG1GC", \
  "-XX:MaxRAMPercentage=75.0", \
  "-XX:InitialRAMPercentage=50.0", \
  "-XX:+UseStringDeduplication", \
  "-Xlog:gc*:file=/app/logs/gc.log:time,uptime,level,tags", \
  "-jar", "app.jar"]

输出:

TEXT 📖 仅展示
// 执行成功
参数 含义 推荐值
MaxRAMPercentage 最大堆占容器内存比例 75.0
InitialRAMPercentage 初始堆占容器内存比例 50.0
+UseG1GC 使用 G1 垃圾收集器 默认开启
+UseStringDeduplication 字符串去重节省内存 推荐
MaxGCPauseMillis GC 目标暂停时间 200(G1)/ 无(ZGC)

▶ 示例: ZGC 低延迟配置

BASH
java -XX:+UseZGC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:+ZGenerational \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

输出:

TEXT 📖 仅展示
# 命令执行成功
📌 重点: ZGC 在 JDK 17 中是实验性功能(JDK 21 正式可用)。暂停时间 < 1ms,适合对延迟极度敏感的场景。


4. HikariCP 连接池优化

(1) 连接池参数

参数 默认值 含义 推荐值
maximumPoolSize 10 最大连接数 CPU 核数 × 2 + 磁盘数
minimumIdle = maxPool 最小空闲连接 = maxPoolSize
connectionTimeout 30000ms 获取连接超时 3000ms
idleTimeout 600000ms 空闲连接超时 600000ms
maxLifetime 1800000ms 连接最大存活时间 1800000ms
leakDetectionThreshold 0(禁用) 连接泄漏检测 60000ms

▶ 示例: HikariCP 生产配置

YAML
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 20
      connection-timeout: 3000
      idle-timeout: 600000
      max-lifetime: 1800000
      leak-detection-threshold: 60000
      pool-name: OrderFlowHikariCP

输出:

TEXT 📖 仅展示
配置文件已生效

▶ 示例: 连接泄漏检测

YAML
# Enable leak detection (log warning if connection held > 60s)
spring:
  datasource:
    hikari:
      leak-detection-threshold: 60000
💻 输出(泄漏时):

TEXT 📖 仅展示
WARN  - Connection leak detection triggered for {conn-12345}
  Stack trace of the call site that acquired the connection:
    at com.orderflow.service.OrderService.getOrder(OrderService.java:45)
    ...

5. JPA 查询优化

(1) N+1 问题

▶ 示例: N+1 问题演示

JAVA
// BAD: N+1 problem
// 1 SQL to fetch 100 orders + 100 SQLs to fetch items for each order = 101 SQLs!
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
    order.getItems().size();  // Triggers lazy load for each order
}

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例: JOIN FETCH 解决 N+1

JAVA
// GOOD: Single query with JOIN FETCH
@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items i JOIN FETCH i.product WHERE o.status = :status")
List<Order> findByStatusWithItems(@Param("status") String status);

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例: @EntityGraph 声明式加载

JAVA
@Entity
@NamedEntityGraph(
    name = "Order.withItemsAndProduct",
    attributeNodes = {
        @NamedAttributeNode("items"),
        @NamedAttributeNode(value = "items", subgraph = "item-product")
    },
    subgraphs = {
        @NamedSubgraph(name = "item-product", attributeNodes = @NamedAttributeNode("product"))
    }
)
public class Order { /* ... */ }

// Usage in Repository
@EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
List<Order> findByStatus(String status);

输出:

TEXT 📖 仅展示
// 执行成功
方案 SQL 数量 适用场景 可维护性
默认懒加载 N+1 单个对象查询
JOIN FETCH 1 特定查询
@EntityGraph 1 可复用的加载计划
@BatchSize N/batchSize 批量加载

6. 数据库索引优化

(1) 索引策略

▶ 示例: 添加数据库索引

SQL
-- Index for order status queries
CREATE INDEX idx_order_status ON orders(status);

-- Composite index for common query pattern
CREATE INDEX idx_order_status_created ON orders(status, created_at DESC);

-- Index for product search
CREATE INDEX idx_product_name ON products(name);

-- Index for order item product lookup
CREATE INDEX idx_order_item_product ON order_items(product_id);

输出:

TEXT 📖 仅展示
CREATE TABLE
索引类型 适用场景 示例
单列索引 单条件查询 WHERE status = 'PENDING'
复合索引 多条件查询 WHERE status = ? AND created_at > ?
唯一索引 唯一约束 UNIQUE(sku)
覆盖索引 索引包含所有查询列 SELECT status FROM orders WHERE id = ?

▶ 示例: 慢查询分析

SQL
-- MySQL slow query log configuration
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- Log queries > 1 second
SET GLOBAL log_queries_not_using_indexes = ON;

-- Analyze query execution plan
EXPLAIN SELECT o.*, i.*
FROM orders o
JOIN order_items i ON o.id = i.order_id
WHERE o.status = 'PENDING'
  AND o.created_at > '2024-01-01'
ORDER BY o.created_at DESC
LIMIT 20;

输出:

TEXT 📖 仅展示
CREATE TABLE
EXPLAIN 字段 含义 关注点
type 访问类型 ALL(全表扫描)需优化
key 使用的索引 NULL 表示未使用索引
rows 扫描行数 越小越好
Extra 额外信息 Using filesort 需优化

7. 综合示例:OrderFlow P99 从 500ms 到 50ms

YAML
# application-prod.yml (optimized)
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 20
      connection-timeout: 3000
      leak-detection-threshold: 60000
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        default_batch_fetch_size: 100
        jdbc:
          batch_size: 50
        order_inserts: true
        order_updates: true
BASH
# JVM parameters (Docker ENTRYPOINT)
java -XX:+UseG1GC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:InitialRAMPercentage=50.0 \
     -XX:+UseStringDeduplication \
     -XX:MaxGCPauseMillis=100 \
     -Xlog:gc*:file=/app/logs/gc.log:time,uptime,level,tags \
     -jar app.jar
JAVA
// Optimized OrderRepository with EntityGraph and batch fetching
public interface OrderRepository extends JpaRepository<Order, Long> {

    @EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
    @Query("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.createdAt DESC")
    Page<Order> findByStatusPaged(@Param("status") String status, Pageable pageable);

    @EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
    Optional<Order> findById(Long id);
}

// Hibernate batch size configuration eliminates N+1 for collections
// application.yml: hibernate.default_batch_fetch_size=100
优化前 优化后 提升倍数
P99: 500ms P99: 50ms 10x
SQL/请求: 101 SQL/请求: 1 100x
GC 暂停: 200ms GC 暂停: 50ms 4x
连接等待: 100ms 连接等待: 2ms 50x

❓ 常见问题

Q G1GC 和 ZGC 该选哪个?
A G1GC 是 JDK 17 默认,适合绝大多数场景,暂停时间 10-200ms。ZGC 暂停 < 1ms,适合金融交易等低延迟场景,但吞吐量略低。从 G1GC 开始,有延迟问题再切 ZGC。
Q 连接池大小是不是越大越好?
A 不是。连接池太大导致:1)数据库压力增大;2)上下文切换开销;3)内存占用增加。推荐公式:connections = (CPU cores × 2) + effective_spindle_count。
Q @EntityGraph 和 JOIN FETCH 有什么区别?
A @EntityGraph 是声明式的,定义在 Entity 或 Repository 上可复用;JOIN FETCH 是命令式的,写在每个 @Query 里。复杂场景用 EntityGraph 更好维护。
Q Hibernate batch_size 有什么作用?
A 设置 hibernate.jdbc.batch_size=50 后,INSERT/UPDATE 语句批量执行(50 条一批),减少数据库往返次数。配合 order_inserts=true 效果更好。
Q 如何定位性能瓶颈?
A 1)Actuator /metrics 查看 HTTP 请求耗时;2)GC 日志分析 GC 暂停;3)HikariCP metrics 查看连接池状态;4)MySQL slow query log 查看慢查询;5)APM 工具(SkyWalking/Zipkin)全链路追踪。
Q 如何做性能压测?
A 使用 JMeter、Gatling 或 k6。逐步加压(ramp-up),观察响应时间和错误率的拐点。测试场景应覆盖:单接口、混合场景、峰值场景。

📖 小节


📝 作业

  1. 基础题(难度⭐):为 OrderFlow 配置 JVM G1GC 参数和 HikariCP 连接池参数,启用 GC 日志和连接泄漏检测。

  2. 进阶题(难度⭐⭐):使用 @EntityGraphJOIN FETCH 解决订单列表查询的 N+1 问题,对比优化前后的 SQL 数量和响应时间。

  3. 挑战题(难度⭐⭐⭐):使用 JMeter 对 OrderFlow 订单列表 API 进行压测,建立性能基线,逐步优化(JVM + 连接池 + N+1 + 索引),记录每步优化的 P50/P99 变化,最终将 P99 优化到 50ms 以下。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏