前言
在 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
常见原因:
- 依赖的 Bean 不存在
- 构造器注入失败
- 循环依赖
解决:
// ✅ 使用构造器注入
@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 未激活
- 配置层级错误
解决:
# 确保 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. 接口响应慢
排查步骤:
- 查看慢查询日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 使用 Actuator 监控
curl http://localhost:8080/actuator/metrics/http.server.requests
- 使用 APM 工具
- SkyWalking
- Pinpoint
- Arthas
解决:
// 添加索引
// 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>
分析工具:
- VisualVM
- MAT (Memory Analyzer Tool)
- JProfiler
解决:
# 调整 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
常见原因:
- 死循环
- 频繁 GC
- 线程阻塞
解决:
// 避免死循环
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. 总结复盘 → 避免复发
总结
问题排查要点:
- ✅ 启动问题 - 端口、数据库、Bean
- ✅ 配置问题 - 外部化、Profile
- ✅ 性能问题 - 慢查询、内存、CPU
- ✅ 内存泄漏 - ThreadLocal、静态集合
- ✅ 并发问题 - 死锁、竞态条件
- ✅ 最佳实践 - 日志、监控、流程
问题排查能力需要不断积累和实践。