前面的章节已经分别使用 JdbcClient 和 JPA 完成数据访问,通过 RestClient 与定时任务建立主动检查流程,并用 Testcontainers 和真实 HTTP 端口验证了整条应用链路。开发环境通常只有一个应用实例,数据库也在同一台电脑上;生产环境则可能同时运行多个实例,并经过防火墙、TLS、负载均衡和数据库连接上限。
连接池不能只看某一个实例的配置。本章把连接数量、超时、凭据、迁移权限、数据库监控与发布验证放进同一套生产边界,目标不是给出一组适合所有系统的固定数字,而是知道每个数字依据什么确定。
1. 连接在请求中的生命周期
HikariCP 是 Spring Boot JDBC 和 JPA 场景中的默认连接池。Repository 执行数据库操作时,实际过程可以简化为:
1请求进入 Service 事务2-> 从 HikariCP 借出 Connection3-> JdbcClient 或 Hibernate 执行 SQL4-> 提交或回滚事务5-> Connection 归还连接池6-> 后续请求复用底层数据库连接
代码调用 Connection.close() 时,池化连接通常是归还到池中,不是立即关闭底层 TCP 连接。Spring 事务和数据访问组件会处理这一步,Repository 不应该把连接保存在字段中,也不应该自己跨请求复用它。
一次事务会在执行期间占用连接。慢查询、等待锁、在事务中调用远程服务,或者忘记结束流式结果,都会延长占用时间。连接池耗尽往往是上游工作过慢的结果,直接扩大池只会让更多查询同时压到数据库。
可以用 Little's Law 粗略理解需求:并发占用量约等于每秒到达的数据库事务数乘以每个事务平均占用连接的秒数。例如每秒 50 个事务、平均占用 0.1 秒,稳定状态平均约占用 5 条连接;实际还要根据高峰分位耗时、突发流量和数据库能力通过压测留出余量。
2. 计算整个部署的连接预算
maximum-pool-size 是每个应用实例的上限,不是整个服务的上限。理论峰值需要按部署规模计算:
1应用连接峰值 = 同时运行的实例数 × 每实例 maximum-pool-size23数据库可分配给应用的连接4= max_connections5- PostgreSQL 保留连接6- 管理、监控、迁移和后台任务预留7- 其他服务使用的连接
假设数据库允许业务工作负载使用 60 条连接,常态有 4 个应用实例,每个实例上限 10 条,则应用峰值是 40。滚动发布期间可能短暂同时存在 5 个实例,峰值会变成 50,剩余容量才能供迁移和运维使用。
池大小还必须服从数据库吞吐能力。PostgreSQL 即使允许 200 条连接,也不代表 200 条并发查询会比 40 条更快;CPU 核数、内存、磁盘 I/O 和查询类型才决定有效并发。正确流程是先设定保守预算,再通过压测观察吞吐、延迟和数据库资源,而不是把 maximum-pool-size 调到数据库上限。
当短生命周期实例很多或连接总量难以控制时,可以评估 PgBouncer 等外部连接池。它会改变会话复用方式,使用事务池模式前必须核对临时表、会话级设置和预备语句等功能是否兼容。
3. 配置池大小与连接寿命
把本地默认值保留在 application.yaml,生产差异放进 application-prod.yaml。生产文件只保存非敏感的规则,具体数值由部署环境覆盖:
01spring:02datasource:03hikari:04pool-name: health-database05minimum-idle: ${DB_POOL_MIN_IDLE:2}06maximum-pool-size: ${DB_POOL_MAX_SIZE:10}07connection-timeout: ${DB_POOL_CONNECTION_TIMEOUT_MS:3000}08validation-timeout: ${DB_POOL_VALIDATION_TIMEOUT_MS:1000}09idle-timeout: ${DB_POOL_IDLE_TIMEOUT_MS:600000}10max-lifetime: ${DB_POOL_MAX_LIFETIME_MS:1800000}11keepalive-time: ${DB_POOL_KEEPALIVE_TIME_MS:120000}
application-prod.yaml 只有在 prod Profile 激活后才会参与配置。部署平台应设置 SPRING_PROFILES_ACTIVE=prod;没有激活时,上面这组生产连接池参数不会生效。
这些 Hikari 属性中的时间值使用毫秒:
| 属性 | 控制的问题 |
|---|---|
minimum-idle | 池希望保留的最少空闲连接 |
maximum-pool-size | 一个实例最多管理多少连接 |
connection-timeout | 池中无可用连接时,最多等待多久 |
validation-timeout | 检查连接有效性最多等待多久,应小于连接等待时间 |
idle-timeout | 超出最少空闲数量的连接闲置多久后可被回收 |
max-lifetime | 一条连接在池中的最长生命周期 |
keepalive-time | 空闲连接多久进行一次保活检测 |
max-lifetime 应略短于数据库、代理或网络设备强制关闭连接的时间,避免应用借到刚被外部回收的旧连接。keepalive-time 必须小于 max-lifetime,并且只对空闲连接生效。值设置过短会制造不必要的重连和探测流量。
minimum-idle 小于最大值时,池可以在低负载下减少空闲连接,但突发请求可能需要等待新连接建立。固定规模、延迟敏感的服务也可以不单独设置它,让池按 Hikari 的默认固定大小行为运行。选择弹性池还是固定池要结合实例生命周期、数据库连接成本和压测结果。
4. 为不同等待阶段设置超时
一个「数据库请求超时」实际上可能发生在多个阶段:
| 阶段 | 示例配置 | 超时后说明 |
|---|---|---|
| 建立网络连接 | PostgreSQL connectTimeout | 数据库地址不可达或握手过慢 |
| 从连接池借连接 | Hikari connection-timeout | 池中连接全部被占用 |
| 等待数据库锁 | PostgreSQL lock_timeout | SQL 无法及时取得所需锁 |
| 执行 SQL | PostgreSQL statement_timeout | 语句运行超过业务预算 |
| 事务开启但空闲 | idle_in_transaction_session_timeout | 应用持有事务却没有继续执行 SQL |
在部署环境提供的 JDBC URL 中配置连接建立超时,并启用完整的 TLS 校验:
1SPRING_PROFILES_ACTIVE=prod2DB_URL=jdbc:postgresql://db.internal:5432/spring?sslmode=verify-full&sslrootcert=/run/secrets/postgresql-ca.crt&connectTimeout=5
PostgreSQL JDBC 的 connectTimeout 使用秒,Hikari 的 connection-timeout 使用毫秒,两者不能混为一项。sslmode=verify-full 会校验证书链和数据库主机名,部署环境必须挂载正确的 CA 文件;生产配置不能为了绕过证书问题改成不验证身份的连接模式。
数据库管理员可以针对运行账号设置语句与事务保护:
1alter role health_runtime set statement_timeout = '10s';2alter role health_runtime set lock_timeout = '2s';3alter role health_runtime set idle_in_transaction_session_timeout = '30s';
这些值只是教学案例的起点。接口、批处理和迁移任务需要不同预算,因此应使用不同账号或在受控事务中采用不同设置。角色默认值只影响之后创建的连接,修改后要让连接池逐步重建连接。
在事务中出现语句超时后,当前 PostgreSQL 事务会进入失败状态,必须回滚,不能捕获异常后继续用同一事务执行下一条 SQL。应用层、数据库层和网关的时间预算也要留出回滚与错误响应时间,不能让客户端先放弃,而数据库继续长时间工作。
5. 隔离凭据与迁移权限
生产连接信息由部署平台的密钥管理能力注入,不给密码提供仓库内默认值:
1spring:2datasource:3url: ${DB_URL}4username: ${DB_USERNAME}5password: ${DB_PASSWORD}
环境变量名称可以出现在代码中,真实值不能进入 Git、镜像、构建日志或异常响应。数据库 URL 本身也可能包含敏感参数,记录配置时应脱敏,不要完整输出。
运行接口的账号只需要连接数据库、使用目标 schema,并对业务表执行必要的增删改查;它不应该拥有创建表、删除表或修改角色的权限。Flyway 迁移需要 DDL 权限,可以使用单独账号:
1spring:2flyway:3url: ${MIGRATION_DB_URL}4user: ${MIGRATION_DB_USERNAME}5password: ${MIGRATION_DB_PASSWORD}
在单实例或简单部署中,应用启动时可以使用这组独立凭据执行迁移,业务 DataSource 仍使用运行账号。多实例发布中,更稳妥的做法是由一次性的发布任务先执行并验证迁移,成功后再启动应用实例;运行进程随后关闭 Flyway 自动迁移,也不需要持有迁移凭据。
数据库结构发布还要考虑新旧应用同时运行的窗口。先增加兼容结构,再发布能够读写新结构的代码;数据回填和旧列删除分开执行。直接重命名或删除旧列,可能让滚动发布中尚未退出的旧实例立即失败。
6. 数据库监控与发布验证
连接池与 PostgreSQL 两侧都需要监控,单看某一侧容易误判:
| 观察位置 | 重点信号 | 常见含义 |
|---|---|---|
| HikariCP | 活跃、空闲、上限、等待连接数 | 应用是否在池前排队 |
| JDBC 调用 | 获取连接与 SQL 耗时 | 时间花在借连接还是执行查询 |
| PostgreSQL 会话 | 活跃连接、空闲事务、等待事件 | 连接是否被长期占用 |
| PostgreSQL 查询 | 慢查询、执行次数、返回行数 | 哪些 SQL 消耗数据库资源 |
| 锁 | 锁等待与阻塞链 | 请求是否被其他事务阻塞 |
如果等待连接数持续增加,应先结合慢查询、长事务、锁等待和请求流量定位占用来源。只调大池上限可能把排队从应用移到数据库,并让更多并发查询争抢相同 CPU、I/O 或锁。
后面的 Actuator 与可观测性文章会把 HikariCP 指标交给监控系统。数据库侧还应启用合适的慢查询记录和统计扩展,并控制访问权限;不要让应用运行账号拥有读取所有管理信息的高权限。
数据库健康查询必须快速、只读且有超时。它可以判断应用是否应该继续接收依赖数据库的流量,但不能代替实际 Repository 的异常处理,也不应该执行全表扫描或写入测试数据。
数据库变更发布前至少验证以下结果:
- 所有应用实例在滚动发布峰值下仍未超过连接预算;
- Testcontainers 和预发布数据库都能从空库完成全部 Flyway 迁移;
- 已有数据库可以从当前版本增量迁移,而不只是在空库建表成功;
- 新旧应用同时运行时,表结构保持向前和向后兼容;
- 生产配置缺少必要密钥时启动失败,日志不会输出真实凭据;
- TLS 主机名与 CA 校验通过;
- 慢查询、锁等待、连接池排队和数据库容量都有告警;
- 备份已经在隔离环境实际恢复并验证。
到这里,数据库连接、迁移、测试和生产配置已经形成完整工程边界。下一篇使用 Actuator 区分业务心跳接口与应用运行状态,让部署平台能够判断实例是否存活、是否已经准备接收流量。