Skip to content

Spring Boot 配置与生产运行

应用能够启动只是工程闭环的中点。生产运行还要回答:配置最终从哪里来、实例何时可以接流量、故障是否可观察、发布时如何停止,以及外部依赖变慢时怎样保护线程和连接。

本页是生产运行总览,负责连接配置、探针、观测、资源边界和发布验证。配置发现与绑定的内部机制见 Config Data、配置覆盖与类型安全绑定;后续 Actuator、打包部署和升级排障会继续拆为独立文章。

先定义一个可运行实例

生产中的“应用正常”至少包含五层事实:

层次要回答的问题代表证据
制品运行的是哪次构建Git 提交、制品摘要、镜像 Digest
进程JVM 是否存活并完成启动进程状态、启动事件、错误日志
流量实例是否应该接收请求readiness、网关后端状态
依赖数据库、缓存、消息和远端服务是否满足当前操作分层健康、连接池、真实调用
业务订单创建等核心能力是否产生正确结果安全的代表性业务验证

只验证其中一层会产生错误结论。进程存在不代表端口开放,健康端点 200 不代表支付可用,首页能打开也不代表数据库写入成功。

配置不是一份 YAML 文件

配套视频:EP04:多个配置来源怎样决定最终值EP05:Config Data、Profile 和外部目录怎样进入配置栈

Boot 把多个 PropertySource 合并为 Environment。包内配置适合保存安全默认值,外部文件和环境变量提供环境差异,命令行参数适合临时覆盖。测试属性还有独立优先级。相同键的最终值由来源顺序决定。

结构化业务配置应使用 @ConfigurationProperties,在启动时完成类型转换与校验:

yaml
payment:
  base-url: https://pay.example.com
  connect-timeout: 1s
  read-timeout: 3s
java
@Validated
@ConfigurationProperties("payment")
public record PaymentProperties(
        @NotNull URI baseUrl,
        @NotNull Duration connectTimeout,
        @NotNull Duration readTimeout) {}

配置对象需要通过扫描或 EnableConfigurationProperties 注册。校验失败应阻止应用进入就绪,而不是等第一笔支付请求到来才发现地址缺失。

完整的配置位置、优先级、Profile、导入和失败实验见 Config Data 专文。生产排查时只保留四个关键动作:确认最终值、确认来源、确认绑定对象、确认消费者真正采用。敏感信息不能为了排错直接输出到日志或公开端点。

健康、存活与就绪不是同一个问题

存活检查判断进程是否已经失去自愈能力,需要重启;就绪检查判断实例当前能否接收流量。数据库短暂抖动如果直接导致 liveness 失败,所有实例可能同时重启并放大故障。更稳妥的做法是根据业务依赖关系设计 readiness,并让 liveness 只覆盖进程级不可恢复状态。

健康端点返回 UP 也不能证明核心业务成功。至少还要有真实请求、数据库读写或消息收发等分层验证,且验证动作不能产生不可控副作用。

依赖是否进入 readiness

并非所有依赖故障都要让实例摘流量。订单创建强依赖数据库,数据库不可用时实例可能无法服务;推荐列表失败可能只需要降级为空,不应让所有订单接口一起下线。

判断某依赖是否进入 readiness,需要回答:故障时实例还能否完成任何核心请求、其他实例是否有更好状态、摘除全部实例会不会比局部降级更糟。探针是流量决策,不是依赖清单。

liveness 要避免重启风暴

liveness 只应覆盖进程无法自行恢复的状态。把共享数据库短暂超时直接作为存活失败,会让所有副本同时重启,增加连接和初始化压力。外部依赖问题优先通过 readiness、熔断、告警和人工处置表达。

Actuator 应按问题开放

health 用于实例状态,metrics 用于趋势,mappings 用于路由,conditions 用于自动配置,envconfigprops 用于配置来源。生产环境不应为了排错方便暴露全部端点;内部结构和配置端点需要鉴权、网络隔离与脱敏。

一次配置问题的证据链应是:确认部署参数,查看 Environment 最终值及来源,检查配置绑定对象,再验证使用该配置的 Client。只看到 YAML 内容不能证明运行实例采用了该值。

管理面与业务面要分开

管理端点可以使用独立端口和网络策略,只允许平台与运维访问。envconfigpropsbeansmappings 和 heap dump 等端点可能暴露内部结构或敏感数据,不应因为排错方便全部开放到公网。

端点暴露、端点启用和安全授权是三件事。一个端点 Bean 存在,不代表必须通过 HTTP 暴露;通过 HTTP 暴露,也不代表匿名用户应能访问。

基础配置可以显式控制暴露面:

yaml
management:
  server:
    port: 9091
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      show-details: when-authorized

独立管理端口仍需要网络策略与鉴权。不能因为它不在业务端口上,就默认只有可信用户能访问。

日志、指标和 Trace 的职责

日志适合记录离散事件与上下文,指标适合观察趋势和容量,Trace 适合还原一次跨组件调用的时间分布。三者需要共享请求 ID、业务 ID或 Observation 上下文,但不能互相替代。

指标标签必须控制基数。把订单号、用户 ID 或完整 URL 放入指标标签,会快速制造大量时间序列;这些高基数信息更适合日志或受控 Trace。

外部调用必须有时间边界

HTTP、数据库、Redis 和消息系统都需要连接、读取或处理超时。没有超时的外部调用会长期占用请求线程;重试若没有总预算和退避,会在下游故障时制造更多流量。线程池和连接池的容量也必须结合上游并发、下游延迟和超时计算,而不是只提高最大值。

事务中应避免长时间远程调用。数据库连接在等待远端响应期间仍被占用,最终可能把外部服务变慢放大成连接池耗尽。

用预算而不是单点超时

一次入口请求的总预算假设为 2 秒,内部数据库、库存和支付调用不能各自配置 2 秒后再叠加三次重试。应该从入口预算反推每段连接、读取、重试和降级时间,并给响应序列化和网络留出余量。

重试只适合瞬时、可重复且满足幂等的失败。支付创建这类可能产生副作用的操作,必须使用业务幂等键并核对远端结果,不能因为客户端超时就盲目再次提交。

线程池与连接池要联动

请求线程数远大于数据库连接池并不自动提高吞吐。下游延迟升高时,大量请求会阻塞等待连接,队列增长并放大超时。容量设计应结合并发、平均/分位延迟、连接占用时间和拒绝策略,用压测与线上指标验证。

发布与停止过程

发布新版本时,实例应先从流量入口摘除,再等待在途请求完成,停止消费者和定时任务,最后关闭 Context 与连接资源。优雅停机需要网关、编排平台、服务器超时和应用配置协同;只打开一个开关不能保证所有请求都完成。

发布验证至少包括版本或提交标识、健康与就绪、一个代表性业务请求、关键依赖指标和错误日志。构建成功不能替代运行验证,进程存在也不能替代公网或真实调用链验证。

优雅停机是多方协议

应用收到停止信号后,需要先拒绝新流量或让平台摘除实例,再等待在途请求、消息消费和后台任务到安全点,最后关闭服务器、线程池与连接池。平台的 termination grace period 必须大于应用最大处理与关闭时间。

定时任务和消息消费者还要定义“停止取新任务”和“完成当前任务”的边界。只等待 HTTP 请求,不能保证批处理或消息确认安全结束。

回滚前先检查兼容性

应用制品可以回滚,不代表数据库结构、消息格式和缓存内容可以回滚。发布前应明确向前兼容窗口:新旧版本是否能同时读写同一数据、消费者是否理解新字段、配置是否保留旧键。

发布验收清单

一份可复用的发布检查至少包括:

  1. 制品摘要与计划提交一致,配置版本和数据库变更已记录。
  2. 新实例启动日志没有 FailureAnalyzer 报告或重复重试风暴。
  3. liveness、readiness 与平台后端状态符合预期。
  4. 通过正式入口执行代表性业务请求,确认状态码、响应体和数据副作用。
  5. 比较新旧实例的错误率、延迟分位数、线程池和连接池。
  6. 验证停止旧实例时没有新增 5xx、未确认消息或长事务中断。
  7. 在观察窗口内保留明确回滚条件,达到阈值立即执行预案。

这份清单必须使用实际环境证据。CI 构建日志不能替代正式入口请求,本地 health 也不能替代网关、证书和线上路由。

生产排错顺序

先确定影响范围和开始时间,再比较发布、配置和依赖事件;随后沿入口流量、线程池、数据库连接池、外部调用和 JVM 指标缩小范围。日志需要请求 ID 与业务 ID,指标需要延迟分位数和错误分类,Trace 用于还原跨服务时间分布。三者各自回答不同问题,不能只依赖一份启动日志。

可以使用下面的固定闭环:

  1. 记录开始时间、影响接口、租户或区域,确认是否所有实例一致。
  2. 对齐最近的制品、配置、数据库和平台变更,不先假定是代码问题。
  3. 从公网或真实入口复现一个安全请求,记录请求 ID 与响应分类。
  4. 沿网关、服务器线程、业务方法、连接池和外部依赖找到耗时或失败分界。
  5. 选择影响最小的止损动作,例如摘除异常实例、关闭非核心功能或回滚配置。
  6. 止损后再次执行代表性业务请求,并观察一段时间的错误率、延迟和资源恢复。
  7. 保留证据,修复根因,并为同类问题补充告警、测试或发布门禁。

一个“健康但不能下单”的案例

某版本启动成功,/actuator/health 返回 UP,首页也能访问,但创建订单持续 500。排查发现 DataSource 使用延迟连接,启动时没有验证数据库账户;基础 health 又因暴露策略没有包含数据库详情。

正确证据链是:确认请求进入 Controller,事务在获取连接时失败,连接池指标显示创建失败,数据库返回认证错误。修正 Secret 后滚动实例,再执行一笔可回滚或测试租户订单,验证数据库写入和响应。

这个案例说明健康检查应覆盖需要的依赖,但代表性业务验证仍不可省略。否则“端点为绿”只是某个管理接口成功。

版本与升级边界

Boot 4 的模块、Starter、Servlet 基线、Jackson 和部分测试能力与 Boot 3.5 不同。升级前先进入最新 3.5 维护版本,清理废弃 API,再对依赖树、配置属性、自动配置条件、序列化契约和探针行为做基线对比。

spring-boot-properties-migrator 可临时帮助发现重命名配置,但迁移完成后应移除。真正的升级闭环还包括编译测试、Context 测试、真实服务器测试、制品扫描、灰度发布和回滚演练。

继续阅读

别急,先让缓存热一下。