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

Spring Boot 常见问题排查

前言

在 Spring Boot 应用开发和运维过程中,会遇到各种问题。快速定位和解决问题是开发者必备的技能。本文将介绍 Spring Boot 常见问题的排查方法。

启动问题

1. 端口被占用

现象:

Error: Port 8080 was already in use.

排查:

# 查看端口占用
netstat -ano | findstr :8080

# Linux
lsof -i :8080

解决:

# 修改端口
server:
  port: 8081

2. 数据库连接失败

现象:

com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

排查:

# 测试数据库连接
telnet db-host 3306

# 查看数据库状态
mysql -h db-host -u root -p

解决:

# 检查配置
spring:
  datasource:
    url: jdbc:mysql://correct-host:3306/demo
    username: correct-user
    password: correct-password
    driver-class-name: com.mysql.cj.jdbc.Driver
    
  # 连接池配置
  hikari:
    connection-timeout: 30000
    maximum-pool-size: 10

3. Bean 创建失败

现象:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService'

排查:

# 启用调试日志
logging:
  level:
    org.springframework: DEBUG

常见原因:

解决:

// ✅ 使用构造器注入
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
}

// 解决循环依赖
@Lazy
@Autowired
private OtherService otherService;

4. 自动配置失败

现象:

Error starting ApplicationContext. Some AutoConfiguration could not be applied.

排查:

# 查看自动配置报告
--debug

# 查看条件评估报告
logging:
  level:
    org.springframework.boot.autoconfigure: DEBUG

解决:

// 排除不需要的自动配置
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    RedisAutoConfiguration.class
})

配置问题

1. 配置不生效

现象: 配置修改后没有效果

排查:

# 查看加载的配置
curl http://localhost:8080/actuator/env

# 查看特定配置
curl http://localhost:8080/actuator/env/spring.datasource.url

常见原因:

解决:

# 确保 Profile 正确
spring:
  profiles:
    active: dev

# 配置层级正确
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo

2. 外部化配置失败

现象: 环境变量未生效

排查:

# 查看环境变量
echo $SPRING_DATASOURCE_URL

# Windows
echo %SPRING_DATASOURCE_URL%

解决:

# 正确设置环境变量
export SPRING_DATASOURCE_URL="jdbc:mysql://localhost:3306/demo"

# 或在命令行指定
java -jar demo.jar --spring.datasource.url=jdbc:mysql://localhost:3306/demo

性能问题

1. 接口响应慢

排查步骤:

  1. 查看慢查询日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  1. 使用 Actuator 监控
curl http://localhost:8080/actuator/metrics/http.server.requests
  1. 使用 APM 工具

解决:

// 添加索引
// CREATE INDEX idx_user_email ON user(email);

// 优化查询
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);

// 添加缓存
@Cacheable(value = "users", key = "#id")
public UserDTO getUserById(Long id);

2. 内存占用高

排查:

# 查看堆内存
jstat -gc <pid> 1000

# 生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>

分析工具:

解决:

# 调整 JVM 参数
-Xms2g
-Xmx2g
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
// 避免内存泄漏
// 及时关闭资源
try (InputStream is = file.getInputStream()) {
    // 处理
}

// 使用弱引用
WeakReference<User> weakRef = new WeakReference<>(user);

3. CPU 占用高

排查:

# 查看线程 CPU 使用
top -H -p <pid>

# 线程堆栈
jstack <pid> > thread_dump.txt

常见原因:

解决:

// 避免死循环
while (condition) {
    // 确保 condition 会变为 false
}

// 优化集合操作
// ❌ 不推荐
for (User user : userList) {
    if (user.getStatus() == 1) {
        // 处理
    }
}

// ✅ 推荐
userList.stream()
    .filter(user -> user.getStatus() == 1)
    .forEach(this::process);

内存泄漏

1. ThreadLocal 泄漏

现象: 内存持续增长

排查:

# 查看线程
jstack <pid> | grep -A 20 "ThreadLocal"

解决:

// ✅ 推荐
private static final ThreadLocal<User> userContext = new ThreadLocal<>();

public void process() {
    try {
        userContext.set(user);
        // 业务逻辑
    } finally {
        userContext.remove(); // 必须清理
    }
}

// ❌ 不推荐
userContext.set(user);
// 忘记清理

2. 静态集合泄漏

现象: 静态集合持续增长

排查:

# 查看对象分布
jmap -histo <pid> | head -20

解决:

// ❌ 不推荐
private static List<User> cache = new ArrayList<>();

public void addUser(User user) {
    cache.add(user); // 只增不减
}

// ✅ 推荐
private static Map<String, User> cache = new ConcurrentHashMap<>();

public void addUser(User user) {
    cache.put(user.getId(), user);
    
    // 限制大小
    if (cache.size() > 1000) {
        // 清理策略
    }
}

3. 未关闭资源

现象: 文件句柄、连接泄漏

排查:

# 查看打开的文件
lsof -p <pid> | wc -l

解决:

// ✅ 使用 try-with-resources
try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql)) {
    // 处理
}

// ✅ 使用 Closeable
class MyResource implements Closeable {
    @Override
    public void close() {
        // 清理资源
    }
}

并发问题

1. 死锁

排查:

jstack <pid> | grep -A 50 "deadlock"

解决:

// 避免嵌套锁
// ❌ 不推荐
synchronized (lock1) {
    synchronized (lock2) {
        // 处理
    }
}

// ✅ 使用 ReentrantLock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 处理
} finally {
    lock.unlock();
}

2. 竞态条件

现象: 数据不一致

解决:

// ✅ 使用原子类
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();

// ✅ 使用锁
private final ReentrantLock lock = new ReentrantLock();

public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

// ✅ 使用并发集合
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();

数据库问题

1. 连接池耗尽

现象:

Cannot get a connection from pool

排查:

# 查看连接池状态
curl http://localhost:8080/actuator/metrics/hikaricp.connections.active

解决:

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

2. 慢查询

排查:

-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

-- 查看慢查询
SHOW SLOW QUERIES;

解决:

-- 添加索引
CREATE INDEX idx_order_user_id ON orders(user_id);

-- 优化查询
EXPLAIN SELECT * FROM orders WHERE user_id = 1;

最佳实践

1. 日志规范

// ✅ 推荐
log.info("订单创建成功:orderId={}", order.getId());
log.warn("库存不足:productId={}", product.getId());
log.error("订单处理失败:orderId={}", order.getId(), e);

// ❌ 不推荐
log.info("订单创建成功");  // 缺少关键信息
System.out.println("调试");  // 使用 System.out

2. 监控告警

# Prometheus 告警规则
groups:
  - name: demo
    rules:
      - alert: ServiceDown
        expr: up{job="demo"} == 0
        for: 1m
      
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
        for: 5m

3. 健康检查

@Component
public class DatabaseHealthIndicator implements HealthIndicator {
    
    @Autowired
    private DataSource dataSource;
    
    @Override
    public Health health() {
        try {
            Connection conn = dataSource.getConnection();
            conn.close();
            return Health.up().build();
        } catch (SQLException e) {
            return Health.down(e).build();
        }
    }
}

4. 问题排查流程

1. 查看日志 → 定位错误信息
2. 查看监控 → 分析指标
3. 复现问题 → 最小化测试
4. 分析原因 → 查找根因
5. 解决问题 → 验证修复
6. 总结复盘 → 避免复发

总结

问题排查要点:

问题排查能力需要不断积累和实践。


分享这篇文章到:

上一篇文章
结构化 Prompt 设计
下一篇文章
Sentinel 流量控制