Skip to content
清晨的一缕阳光
返回

Spring Boot 性能优化实战

前言

性能优化是提升用户体验和降低资源成本的关键。Spring Boot 应用性能优化涉及 JVM 调优、数据库优化、缓存策略等多个方面。本文将介绍 Spring Boot 性能优化的完整方案。

JVM 调优

1. 内存配置

# 生产环境推荐配置
java -Xms4g -Xmx4g \
     -XX:MetaspaceSize=256m \
     -XX:MaxMetaspaceSize=512m \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/heapdump.hprof \
     -jar app.jar

2. GC 选择

# G1 GC(推荐)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=45

# ZGC(JDK 15+,低延迟)
-XX:+UseZGC
-XX:ZCollectionInterval=5
-XX:ZAllocationSpikeTolerance=5

# Parallel GC(高吞吐)
-XX:+UseParallelGC
-XX:ParallelGCThreads=8
-XX:MaxGCPauseMillis=100

3. 虚拟线程配置

# JDK 21+ 虚拟线程
spring:
  threads:
    virtual:
      enabled: true
      pool:
        core-pool-size: 100
        max-pool-size: 1000
        keep-alive: 30s

4. 监控 GC

# 启用 GC 日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/var/log/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=10M

# 使用 GCViewer 分析
# https://github.com/chewiebug/GCViewer

数据库优化

1. 连接池配置

spring:
  datasource:
    hikari:
      maximum-pool-size: 20      # 最大连接数
      minimum-idle: 5            # 最小空闲
      connection-timeout: 30000  # 连接超时
      idle-timeout: 600000       # 空闲超时
      max-lifetime: 1800000      # 最大生命周期
      leak-detection-threshold: 60000  # 泄漏检测

2. SQL 优化

// ✅ 推荐 - 批量操作
@Service
public class UserService {
    
    public void batchInsert(List<User> users) {
        int batchSize = 100;
        for (int i = 0; i < users.size(); i += batchSize) {
            List<User> batch = users.subList(
                i, 
                Math.min(i + batchSize, users.size())
            );
            userRepository.saveAll(batch);
        }
    }
}

// ❌ 不推荐 - 循环插入
@Service
public class UserService {
    
    public void batchInsert(List<User> users) {
        for (User user : users) {
            userRepository.save(user); // N 次查询
        }
    }
}

3. 索引优化

-- 添加索引
CREATE INDEX idx_user_email ON user(email);
CREATE INDEX idx_user_status_created ON user(status, created_at);

-- 复合索引
CREATE INDEX idx_order_user_status ON orders(user_id, status);

-- 覆盖索引
CREATE INDEX idx_user_id_name ON user(id, name);

-- 查看慢查询
SHOW SLOW QUERIES;
EXPLAIN SELECT * FROM user WHERE email = 'test@example.com';

4. JPA 优化

// ✅ 推荐 - 使用@EntityGraph
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @EntityGraph(attributePaths = {"orders"})
    Optional<User> findById(Long id);
}

// ✅ 推荐 - 使用@Query
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);

// ❌ 不推荐 - N+1 问题
User user = userRepository.findById(id).get();
user.getOrders().size(); // 额外查询

缓存策略

1. 本地缓存

@Configuration
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(10000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .recordStats()
        );
        return cacheManager;
    }
}

2. Redis 缓存

@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    private final RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 缓存穿透防护
     */
    public UserDTO findById(Long id) {
        String key = "user:" + id;
        
        // 从缓存获取
        Object cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            if (cached.equals("__NULL__")) {
                return null; // 缓存空值
            }
            return (UserDTO) cached;
        }
        
        // 从数据库查询
        User user = userRepository.findById(id).orElse(null);
        
        if (user == null) {
            // 缓存空值,防止穿透
            redisTemplate.opsForValue().set(key, "__NULL__", 5, TimeUnit.MINUTES);
            return null;
        }
        
        UserDTO dto = convertToDTO(user);
        
        // 存入缓存
        redisTemplate.opsForValue().set(key, dto, 30, TimeUnit.MINUTES);
        
        return dto;
    }
    
    /**
     * 缓存击穿防护
     */
    public UserDTO findByIdWithLock(Long id) {
        String key = "user:" + id;
        
        Object cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return (UserDTO) cached;
        }
        
        // 分布式锁
        RLock lock = redissonClient.getLock("lock:" + key);
        try {
            if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
                // 双重检查
                cached = redisTemplate.opsForValue().get(key);
                if (cached != null) {
                    return (UserDTO) cached;
                }
                
                // 从数据库查询
                User user = userRepository.findById(id).orElse(null);
                if (user != null) {
                    UserDTO dto = convertToDTO(user);
                    redisTemplate.opsForValue().set(key, dto, 30, TimeUnit.MINUTES);
                    return dto;
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        
        return null;
    }
}

3. 多级缓存

@Service
public class MultiLevelCacheService {
    
    private final Cache localCache;      // L1
    private final RedisTemplate redis;   // L2
    private final UserRepository repo;   // DB
    
    public UserDTO findById(Long id) {
        // L1 缓存
        UserDTO cached = (UserDTO) localCache.getIfPresent(id);
        if (cached != null) {
            return cached;
        }
        
        // L2 缓存
        String key = "user:" + id;
        cached = (UserDTO) redis.opsForValue().get(key);
        if (cached != null) {
            localCache.put(id, cached); // 回写 L1
            return cached;
        }
        
        // 数据库
        User user = userRepository.findById(id).orElse(null);
        if (user != null) {
            UserDTO dto = convertToDTO(user);
            localCache.put(id, dto);
            redis.opsForValue().set(key, dto, 30, TimeUnit.MINUTES);
            return dto;
        }
        
        return null;
    }
}

异步处理

1. 异步任务

@Service
public class AsyncTaskService {
    
    @Async
    public CompletableFuture<Void> sendEmail(String to, String content) {
        // 异步发送邮件
        emailService.send(to, content);
        return CompletableFuture.completedFuture(null);
    }
    
    @Async
    public CompletableFuture<Void> sendSms(String phone, String message) {
        // 异步发短信
        smsService.send(phone, message);
        return CompletableFuture.completedFuture(null);
    }
    
    /**
     * 并行执行
     */
    public CompletableFuture<Void> notifyUser(
        String email, 
        String phone, 
        String content
    ) {
        return CompletableFuture.allOf(
            sendEmail(email, content),
            sendSms(phone, content)
        );
    }
}

2. 消息队列

@Service
public class MessageQueueService {
    
    private final RocketMQTemplate rocketMQTemplate;
    
    /**
     * 异步解耦
     */
    public void createOrder(Order order) {
        // 保存订单
        orderRepository.save(order);
        
        // 异步发送消息
        rocketMQTemplate.sendAsync("order-created", order);
    }
    
    /**
     * 削峰填谷
     */
    @RocketMQMessageListener(
        topic = "order-created",
        consumerGroup = "order-processor"
    )
    public class OrderProcessor implements RocketMQListener<Order> {
        
        @Override
        public void onMessage(Order order) {
            // 异步处理订单
            processOrder(order);
        }
    }
}

接口优化

1. 分页查询

@GetMapping("/api/users")
public R<PageResult<UserDTO>> getUsers(
    @RequestParam(defaultValue = "1") int page,
    @RequestParam(defaultValue = "10") int size
) {
    Pageable pageable = PageRequest.of(page - 1, size);
    Page<User> userPage = userRepository.findAll(pageable);
    
    return R.ok(PageResult.of(
        userPage.getContent().stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList()),
        userPage.getTotalElements(),
        page,
        size
    ));
}

2. 投影查询

// DTO 投影
public interface UserProjection {
    Long getId();
    String getUsername();
    String getEmail();
}

// 使用
@GetMapping("/api/users/simple")
public List<UserProjection> getUsersSimple() {
    return userRepository.findAllBy();
}

3. 压缩响应

server:
  compression:
    enabled: true
    min-response-size: 1024
    mime-types: application/json,text/html,text/css,application/javascript

监控分析

1. 性能监控

@Component
public class PerformanceMonitor {
    
    private final MeterRegistry meterRegistry;
    
    @Around("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = pjp.proceed();
            long cost = System.currentTimeMillis() - startTime;
            
            meterRegistry.timer("api.response.time",
                "method", pjp.getSignature().getName()
            ).record(cost, TimeUnit.MILLISECONDS);
            
            return result;
        } catch (Throwable e) {
            meterRegistry.counter("api.error.count",
                "method", pjp.getSignature().getName()
            ).increment();
            throw e;
        }
    }
}

2. 慢查询日志

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
    
logging:
  level:
    com.example.demo.mapper: DEBUG

3. APM 工具

最佳实践

1. 性能检查清单

- [ ] JVM 内存配置合理
- [ ] GC 参数优化
- [ ] 数据库连接池配置
- [ ] SQL 有索引
- [ ] 避免 N+1 查询
- [ ] 使用缓存
- [ ] 异步处理 IO
- [ ] 接口分页
- [ ] 响应压缩
- [ ] 监控告警

2. 性能测试

# JMeter 压测
jmeter -n -t test.jmx -l result.jtl

# wrk 压测
wrk -t12 -c400 -d30s http://localhost:8080/api/users

# 分析结果
cat result.jtl | jmeter-plugins.cmd --tool Reporter

3. 优化优先级

1. 数据库优化(影响最大)
2. 缓存策略(效果明显)
3. 异步处理(提升吞吐)
4. JVM 调优(最后优化)

总结

性能优化要点:

性能优化是一个持续的过程。


分享这篇文章到:

上一篇文章
OpenFeign 声明式调用
下一篇文章
OAuth2 认证基础