开发环境跑起来了,为什么生产还要调
很多团队的 Spring Boot 应用在开发环境跑得好好的,一上生产就出问题:数据库连接池打满、日志刷屏看不到错误、内存慢慢涨到 OOM、重启时正在处理的请求被中断。这些不是代码 bug,而是缺少生产级配置和监控。
Spring Boot 的自动装配在开发环境帮你做了很多合理的默认值,但生产环境的流量、并发、故障模式完全不同。你需要主动配置多环境隔离、暴露监控端点、规范日志输出、处理优雅停机——这些是让应用从"能跑"变成"能扛"的关键步骤。
开发环境的目标是快速迭代,生产环境的目标是稳定运行——两者的配置优先级完全不同。
- 多环境application-dev/test/prod.yml 分离配置,启动时指定 profile。
- ActuatorSpring Boot 自带的监控端点,暴露健康、指标、环境信息。
- 日志生产日志要分级、脱敏、可检索,不要打印用户敏感数据。
- 优雅停机收到关闭信号后先停接新请求,处理完存量请求再退出。
多环境配置:dev、test、prod 一套代码
不要把数据库密码、API 密钥硬编码在代码里。Spring Boot 的多环境配置通过 profile 机制实现:不同环境用不同的配置文件,启动时指定用哪一套。
# application.yml(公共配置)
spring:
application:
name: order-service
profiles:
active: dev # 默认开发环境
---
# application-dev.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_dev
username: root
password: dev123
---
# application-prod.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://10.0.0.5:3306/order_prod
username: order_app
password: ${DB_PASSWORD} # 从环境变量读取
生产环境的密码通过环境变量传入,不写在配置文件里——这样配置文件可以安全地提交到 Git。启动生产应用时指定 profile:java -jar app.jar --spring.profiles.active=prod。
Actuator:开箱即用的监控端点
Spring Boot Actuator 是生产监控的利器。引入依赖后,它自动暴露一组 HTTP 端点,告诉你应用的健康状态、内存使用、线程情况、配置信息。
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application-prod.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics # 只暴露必要的端点
endpoint:
health:
show-details: when-authorized # 详情只给授权用户看
启动后访问 http://localhost:8080/actuator/health 就能看到健康状态。include 白名单很重要——不要把所有端点都暴露在公网上,env 端点会泄露所有配置信息,包括数据库密码。
健康检查与自定义指标
Actuator 自带的健康检查会检测数据库连接、磁盘空间、Redis 连接等。你也可以写自定义健康指标,比如检查外部 API 是否可用:
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;
@Component
public class ExternalApiHealthIndicator extends AbstractHealthIndicator {
@Override
protected void healthCheck(Health.Builder builder) {
try {
// 检查外部支付 API 是否可达
boolean ok = checkPaymentApi();
if (ok) {
builder.up().withDetail("payment-api", "可用");
} else {
builder.down().withDetail("payment-api", "不可达");
}
} catch (Exception e) {
builder.down(e);
}
}
private boolean checkPaymentApi() {
// 实际发一个 HTTP 请求检查
return true;
}
}
K8s 的 livenessProbe 和 readinessProbe 通常就是调用 /actuator/health。健康检查返回 DOWN 时,K8s 会把这个实例从负载均衡里摘下来。
日志规范:分级、脱敏、聚合
生产出问题时,日志是你唯一的线索。不规范的日志等于没有日志——要么刷屏看不到错误,要么关键信息缺失无法定位。
| 级别 | 用途 | 示例 |
|---|---|---|
| ERROR | 业务失败、外部依赖不可用 | 支付失败、数据库连不上 |
| WARN | 异常但可恢复的情况 | 重试成功、降级触发 |
| INFO | 关键业务节点 | 订单创建、用户登录 |
| DEBUG | 开发调试细节 | SQL 参数、接口入参 |
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@PostMapping("/order")
public Result createOrder(@RequestBody OrderRequest req) {
log.info("创建订单: userId={}, amount={}", req.getUserId(), req.getAmount());
try {
orderService.create(req);
return Result.ok();
} catch (Exception e) {
log.error("创建订单失败: userId={}", req.getUserId(), e);
return Result.fail("系统繁忙");
}
}
}
关键规范:日志里不要打印用户手机号、身份证号、密码等敏感信息;异常日志一定要把异常对象传进去(第二个参数),否则看不到堆栈。
优雅停机与连接池调优
直接 kill 进程会导致正在处理的请求被中断、数据库连接没正确关闭。Spring Boot 2.3+ 支持优雅停机:
# application-prod.yml
server:
shutdown: graceful # 优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 最多等 30 秒
# HikariCP 连接池调优
spring:
datasource:
hikari:
maximum-pool-size: 20 # 最大连接数
minimum-idle: 5 # 最小空闲连接
connection-timeout: 3000 # 获取连接超时 3 秒
max-lifetime: 1800000 # 连接最大寿命 30 分钟
优雅停机的流程是:收到关闭信号 → 停止接收新请求 → 等待正在处理的请求完成(最多 30 秒)→ 关闭数据库连接池 → 退出。这样重启时不会丢请求。
数据库连接池大小不是越大越好——HikariCP 官方建议 CPU 核心数 * 2 + 磁盘数,连接过多反而增加数据库上下文切换开销。
生产部署检查清单
每次上生产前,对照这张清单过一遍:
- profile 正确:启动参数确认是 prod,不是默认的 dev。
- 密钥不入代码:数据库密码、API Key 全部走环境变量或配置中心。
- Actuator 白名单:只暴露 health 和必要端点,不暴露 env 和 beans。
- 日志级别:生产默认 INFO,DEBUG 只在排查时临时开。
- 优雅停机:开启 graceful shutdown,超时时间合理。
- 连接池:HikariCP 参数按流量调优,不要用默认值。
- 健康检查:确认 /actuator/health 在负载均衡器上可访问。
生产级不是某个高级特性,而是一组细节的集合——每一个单独看都不起眼,缺了哪一个都可能在凌晨三点出问题。