第一章:Go数据库连接池耗尽的典型现象与根因定位
当 Go 应用中 database/sql 连接池耗尽时,最直观的表现是大量请求在 db.Query()、db.Exec() 或 db.Begin() 等调用处无响应阻塞,超时后抛出 context deadline exceeded 错误;同时 sql.DB.Stats().WaitCount 持续增长,sql.DB.Stats().MaxOpenConnections 达到上限但 InUse 接近 MaxOpen,而 Idle 接近 0。
常见诱因分类
- 连接泄漏(最常见):
rows未调用rows.Close(),或tx未调用tx.Commit()/tx.Rollback(),导致连接无法归还空闲队列 - 短连接滥用:高频创建新
*sql.DB实例(如每次 HTTP 请求 new 一个),绕过连接复用机制 - 超长事务阻塞:事务执行时间远超业务预期(如未加 LIMIT 的全表更新),占用连接不释放
- 配置失衡:
SetMaxOpenConns(5)过小,却承载每秒数十并发查询,或SetMaxIdleConns(0)禁用空闲连接缓存
快速诊断步骤
-
在应用启动后定期打印连接池状态:
go func() { ticker := time.NewTicker(10 * time.Second) for range ticker.C { stats := db.Stats() log.Printf("DB Stats: Open=%d, InUse=%d, Idle=%d, WaitCount=%d, WaitDuration=%v", stats.OpenConnections, stats.InUse, stats.Idle, stats.WaitCount, stats.WaitDuration) } }() -
启用
sql.DB的连接追踪(Go 1.19+):db.SetConnMaxLifetime(0) // 禁用自动清理,便于观察真实连接生命周期 db.SetConnMaxIdleTime(0) // 同上 // 配合 pprof 采集 goroutine stack:http://localhost:6060/debug/pprof/goroutine?debug=2 -
检查关键路径是否遗漏资源关闭:
rows, err := db.Query("SELECT id FROM users WHERE status = $1", "active") if err != nil { return err } defer rows.Close() // ⚠️ 必须存在!否则连接永久泄漏 for rows.Next() { var id int if err := rows.Scan(&id); err != nil { return err } } // rows.Err() 检查迭代错误(可选但推荐) return rows.Err()
| 指标 | 健康阈值 | 异常含义 |
|---|---|---|
WaitCount 增速 > 10/s |
需立即干预 | 连接获取严重排队 |
Idle / MaxOpen
| 可能存在泄漏或配置过紧 | 空闲连接严重不足 |
WaitDuration 持续 > 100ms |
存在性能瓶颈 | 连接复用效率低下或下游延迟突增 |
第二章:sql.DB连接池核心参数深度解析
2.1 SetMaxOpenConns机制:并发连接上限与阻塞行为的底层实现
SetMaxOpenConns 是 Go database/sql 包中控制连接池最大打开连接数的核心配置,直接影响高并发场景下的资源争用与请求阻塞行为。
连接获取阻塞逻辑
当活跃连接数已达 maxOpen 且无空闲连接时,后续 db.Query() 调用将同步阻塞,直至有连接被归还或超时(由 context 控制)。
底层状态流转
db.SetMaxOpenConns(5) // 限制最多5个已建立的物理连接
db.SetConnMaxLifetime(30 * time.Minute)
db.SetMaxIdleConns(2) // 最多2个空闲连接保留在池中
SetMaxOpenConns(5):硬性限制sql.DB实例可维护的最大已建立连接数(含正在使用 + 空闲);- 若当前已有 5 个活跃连接,新请求将排队等待
db.connChchannel; - 阻塞超时不由
SetConnMaxLifetime决定,而取决于调用方传入的context.Context。
关键状态对照表
| 状态 | 表现 | 触发条件 |
|---|---|---|
| 正常复用 | 从 idle list 快速返回连接 | len(idle) > 0 |
| 同步阻塞 | goroutine 挂起在 db.connCh |
open > maxOpen && idle == 0 |
| 连接新建失败 | 返回 sql.ErrConnDone |
底层驱动 Driver.Open panic |
graph TD
A[GetConn] --> B{idle list non-empty?}
B -->|Yes| C[Return idle conn]
B -->|No| D{open < maxOpen?}
D -->|Yes| E[Open new conn]
D -->|No| F[Block on connCh]
2.2 SetMaxIdleConns与SetConnMaxLifetime协同作用:空闲连接生命周期管理实践
数据库连接池中,SetMaxIdleConns 控制空闲连接上限,而 SetConnMaxLifetime 强制回收超时连接——二者需协同避免“僵尸连接”与资源浪费。
连接池参数协同逻辑
SetMaxIdleConns(10):最多保留10个空闲连接供复用SetConnMaxLifetime(30 * time.Minute):所有连接(含空闲)存活不超过30分钟
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(50)
db.SetConnMaxLifetime(30 * time.Minute) // ⚠️ 关键:防止后端连接超时被强制断开
此配置确保:空闲连接既不堆积(受10限制),又不会因长期空闲被DBMS静默关闭(30分钟内必重建)。若仅设
MaxIdleConns而忽略ConnMaxLifetime,空闲连接可能在DB侧已失效,导致首次复用时driver: bad connection错误。
协同失效场景对比
| 场景 | SetMaxIdleConns 单独启用 | + SetConnMaxLifetime |
|---|---|---|
| 空闲连接存活 > DB wait_timeout | ❌ 复用失败 | ✅ 定期刷新,规避超时 |
| 高并发突发后连接滞留 | ❌ 内存占用持续偏高 | ✅ 超时自动清理 |
graph TD
A[新请求] --> B{空闲连接池有可用?}
B -->|是| C[复用连接]
B -->|否| D[新建连接]
C & D --> E[执行SQL]
E --> F{连接空闲?}
F -->|是| G[加入idle队列]
G --> H{超时 or 数量超限?}
H -->|是| I[立即关闭]
2.3 ConnMaxIdleTime与ConnMaxLifetime的时序冲突案例复现与修复
冲突根源:两个“生命周期”的竞态窗口
当 ConnMaxIdleTime = 30s 且 ConnMaxLifetime = 60s,连接可能在第 45 秒被空闲驱逐器标记为可关闭,但尚未触发 lifetime 的强制回收——此时连接处于“双失效边缘”,驱动层行为不一致。
复现场景代码
db, _ := sql.Open("mysql", dsn)
db.SetConnMaxIdleTime(30 * time.Second) // 空闲超时:30s
db.SetConnMaxLifetime(60 * time.Second) // 总存活期:60s
// 持续每 35s 发起一次查询 → 连接永不空闲满30s,但总时长超60s后仍被复用
逻辑分析:
SetConnMaxIdleTime仅检查连接空闲时长(自上次归还后),而SetConnMaxLifetime检查的是连接创建至今的绝对时长。二者独立计时,无协调机制;若连接持续被复用(如高频短间隔查询),idle计时器始终重置,但lifetime持续累加,最终导致连接在61s时被driver强制关闭,而连接池仍认为其有效,引发io: read/write on closed connection。
推荐修复策略
- ✅ 统一设为
ConnMaxLifetime = 30s,禁用ConnMaxIdleTime(避免叠加) - ✅ 或启用
db.SetMaxOpenConns(0)配合健康检查探针
| 参数 | 作用域 | 是否受连接复用影响 |
|---|---|---|
ConnMaxIdleTime |
归还后空闲时长 | 是(每次归还重置) |
ConnMaxLifetime |
创建至今总时长 | 否(单调递增) |
graph TD
A[连接创建] --> B{30s空闲?}
B -- 是 --> C[空闲驱逐]
B -- 否 --> D{60s总存活?}
D -- 是 --> E[强制关闭]
D -- 否 --> F[继续复用]
2.4 连接泄漏检测:基于pprof+database/sql指标的实时诊断方法
Go 应用中 database/sql 连接池泄漏常表现为 sql.Open() 后未调用 db.Close(),或 rows.Close() 遗漏。此类问题难以复现,但可通过组合观测手段定位。
pprof 实时堆栈采样
启用 HTTP pprof 端点后,可抓取 goroutine 堆栈:
curl "http://localhost:6060/debug/pprof/goroutine?debug=2" | grep -A5 "database/sql"
该命令捕获所有活跃 goroutine,过滤含
database/sql调用链的协程,重点识别长期阻塞在(*DB).Conn或(*Rows).Next的实例——这往往指向未关闭的*sql.Rows或未释放的*sql.Conn。
database/sql 指标监控关键字段
| 指标名 | 含义 | 泄漏征兆 |
|---|---|---|
sql_db_open_connections |
当前打开连接数 | 持续增长且不回落 |
sql_db_idle_connections |
空闲连接数(应 ≥0) | 长期为 0 且总数持续上升 |
自动化诊断流程
graph TD
A[HTTP /debug/pprof/goroutine] --> B{筛选 database/sql 栈帧}
B --> C[定位未 Close 的 Rows/Conn]
C --> D[关联 SQL 执行上下文]
D --> E[标记可疑 handler 或 repo 方法]
核心防御:所有 db.QueryRow()/db.Query() 调用后必须 defer rows.Close();使用 sql.Tx 时确保 Commit()/Rollback() 后调用 tx.Close()(若使用 sqlx 等封装需验证其行为)。
2.5 压测验证:wrk + pgbench下不同参数组合的QPS/latency/conn_wait_time对比实验
为量化数据库连接池与查询并发的协同效应,我们采用双工具链交叉验证:wrk 模拟HTTP层高并发读请求(JSON API),pgbench 直连PostgreSQL执行TPC-B类事务。
测试变量组合
- 连接池:PgBouncer(transaction vs session 模式)
- 并发数:
wrk -t4 -c128 -d30s;pgbench -c64 -j4 -T30 - PostgreSQL配置:
max_connections=200,shared_buffers=4GB
关键观测指标对比(均值)
| 参数组合 | QPS | p95 Latency (ms) | conn_wait_time (ms) |
|---|---|---|---|
| PgBouncer transaction | 1842 | 68.3 | 12.1 |
| PgBouncer session | 1527 | 89.7 | 34.6 |
| 直连 pgbench | 1390 | 95.2 | — |
# wrk 基准命令(含连接复用与管线化)
wrk -t4 -c128 -d30s -H "Connection: keep-alive" \
--latency "http://api/db/users?id=1"
该命令启用 4 线程、128 持久连接,keep-alive 减少 TCP 握手开销;--latency 启用毫秒级延迟采样,确保 conn_wait_time 可从响应头 X-Conn-Wait 提取并聚合。
graph TD
A[HTTP Client] -->|Keep-Alive| B[PgBouncer]
B -->|Transaction Pool| C[PostgreSQL]
C -->|Shared Buffer Hit| D[Query Result]
第三章:高并发场景下的连接池配置黄金法则
3.1 基于业务TPS与平均查询延迟推导最优MaxOpenConns公式推演与校准
数据库连接池的 MaxOpenConns 并非经验调优值,而应由业务负载反向约束:
- 核心约束条件:
MaxOpenConns ≥ TPS × avg_query_latency_ms / 1000 - 物理含义:单位时间内并发活跃连接数下限,避免排队阻塞
公式推演逻辑
设每秒处理 TPS = 1200 请求,平均查询耗时 latency = 85ms,则瞬时需承载连接数至少为:
1200 × 0.085 = 102。考虑突发系数 1.5 与连接复用损耗,取整得 MaxOpenConns = 160。
// Go SQL driver 推荐配置(含安全冗余)
db.SetMaxOpenConns(int(math.Ceil(float64(tps)*latencySec*1.5))) // latencySec = avg_ms / 1000
db.SetMaxIdleConns(int(math.Ceil(float64(tps)*latencySec*1.2)))
逻辑说明:
SetMaxOpenConns需覆盖峰值并发连接需求;1.5为典型流量峰谷比,latencySec统一为秒级单位确保量纲一致。
校准验证表
| TPS | Avg Latency (ms) | 理论最小值 | 推荐值 | 实测P95排队延迟 |
|---|---|---|---|---|
| 800 | 60 | 48 | 80 | 2.1ms |
| 1500 | 110 | 165 | 250 | 3.7ms |
graph TD
A[业务TPS] --> B[乘以平均延迟秒数]
B --> C[得理论并发连接基线]
C --> D[×突发系数1.3~1.8]
D --> E[向上取整→MaxOpenConns]
3.2 Idle连接数动态裁剪策略:按流量峰谷自动调整SetMaxIdleConns的Go实现
核心设计思想
基于实时 QPS 与连接池空闲率双指标驱动,避免静态配置导致的资源浪费或连接争抢。
动态调整逻辑
func (m *ConnPoolScaler) adjustIdleConns(qps float64, idleRatio float64) {
base := int(math.Max(5, qps*1.5)) // 峰值预估基准
if idleRatio > 0.8 && base > 10 { // 空闲过多 → 缩容
m.httpClient.Transport.(*http.Transport).SetMaxIdleConns(base / 2)
} else if idleRatio < 0.3 && base < 200 { // 繁忙且未达上限 → 扩容
m.httpClient.Transport.(*http.Transport).SetMaxIdleConns(int(float64(base) * 1.3))
}
}
逻辑分析:
qps*1.5提供安全冗余;idleRatio(空闲连接数/总空闲+活跃)反映真实负载压力;缩容下限设为5防归零;扩容上限硬约束于200避免FD耗尽。
调整决策对照表
| QPS区间 | 空闲率 | 推荐 MaxIdleConns |
动作类型 |
|---|---|---|---|
| > 0.85 | 5 | 强裁剪 | |
| 10–50 | 0.4–0.7 | 10–30 | 保持 |
| > 50 | 65–200 | 渐进扩容 |
执行流程
graph TD
A[采集QPS & idleCount] --> B{计算idleRatio}
B --> C[查表+规则引擎]
C --> D[调用SetMaxIdleConns]
D --> E[平滑生效,无连接中断]
3.3 连接池健康度监控体系:自定义expvar指标+Prometheus告警规则设计
连接池健康度需从连接生命周期与资源饱和度双维度观测。Go 程序通过 expvar 注册自定义指标,暴露关键状态:
import "expvar"
var (
poolActive = expvar.NewInt("db_pool_active_connections")
poolIdle = expvar.NewInt("db_pool_idle_connections")
poolWait = expvar.NewInt("db_pool_wait_count") // 等待获取连接的总次数
)
// 在连接获取/释放时原子更新
func onConnAcquired() { poolActive.Add(1) }
func onConnReleased() { poolActive.Add(-1); poolIdle.Add(1) }
逻辑分析:
poolActive实时反映并发连接数,poolWait是连接争用核心信号;所有操作使用Add()保证并发安全,避免锁开销。
Prometheus 抓取 /debug/vars 后,可定义如下告警规则:
| 告警项 | PromQL 表达式 | 触发阈值 | 含义 |
|---|---|---|---|
| 连接池过载 | rate(db_pool_wait_count[5m]) > 10 |
每秒等待超10次 | 连接复用不足或池容量过小 |
| 空闲耗尽 | db_pool_idle_connections < 2 |
持续30s | 无可用空闲连接,新请求将阻塞 |
graph TD
A[应用启动] --> B[注册expvar指标]
B --> C[连接池事件钩子更新指标]
C --> D[Prometheus定时抓取/debug/vars]
D --> E[触发告警规则]
第四章:企业级连接池治理工程实践
4.1 中间件层封装:带熔断与降级能力的DBWrapper抽象与实战
在高并发微服务场景中,数据库成为关键单点瓶颈。直接调用JDBC或ORM易导致雪崩——一次慢查询可能拖垮整个服务线程池。
核心设计原则
- 统一入口:所有DB访问经
DBWrapper.execute()调度 - 熔断器内嵌:基于滑动窗口统计失败率(默认阈值60%,持续5秒触发)
- 降级策略可插拔:支持返回缓存快照、空对象或预设兜底SQL
熔断状态流转(mermaid)
graph TD
A[Closed] -->|错误率 > 阈值| B[Open]
B -->|休眠期结束| C[Half-Open]
C -->|试探成功| A
C -->|试探失败| B
关键代码片段
public <T> T execute(String sql, Class<T> resultType,
Supplier<T> fallback, Duration timeout) {
if (circuitBreaker.canExecute()) { // 检查熔断器状态
return timeoutExecutor.execute(sql, resultType, timeout); // 带超时的执行
}
return fallback.get(); // 触发降级
}
timeoutExecutor 内部封装 CompletableFuture.orTimeout(),确保单次DB操作不超 timeout;fallback 是函数式接口,解耦业务降级逻辑;circuitBreaker.canExecute() 返回布尔值,驱动状态机跳转。
| 组件 | 职责 | 可配置项 |
|---|---|---|
| CircuitBreaker | 熔断判定与状态管理 | 失败阈值、窗口大小、休眠时长 |
| TimeoutExecutor | 异步执行+超时中断 | 默认1s,支持动态覆盖 |
| FallbackRouter | 路由至缓存/静态数据/日志 | 降级链优先级与上下文感知 |
4.2 SQL执行链路埋点:从sql.Open到Rows.Close全程连接获取/释放追踪
为实现全链路可观测性,需在数据库驱动关键生命周期节点注入埋点逻辑。
核心埋点位置
sql.Open:记录连接池初始化与驱动注册耗时db.GetConn(隐式):捕获连接获取时刻、等待时间、是否复用空闲连接rows.Next()/rows.Scan():追踪结果集遍历行为与内存分配rows.Close():标记连接归还时机及实际释放延迟
典型埋点代码示例
// 使用 sql.Driver 接口包装器注入上下文追踪
type TracedDriver struct {
driver.Driver
}
func (d *TracedDriver) Open(name string) (driver.Conn, error) {
start := time.Now()
conn, err := d.Driver.Open(name)
trace.Record("sql.Open", map[string]any{
"dsn": redactDSN(name),
"elapsed": time.Since(start).Microseconds(),
"error": err,
})
return conn, err
}
该实现拦截驱动层 Open 调用,将 DSN 脱敏后上报毫秒级初始化耗时与错误状态,避免敏感信息泄露。
连接状态流转(简化版)
| 阶段 | 触发动作 | 埋点关键字段 |
|---|---|---|
| 获取连接 | db.Query() |
wait_time_us, is_new_conn |
| 执行查询 | rows.Next() |
rows_fetched, scan_time_us |
| 归还连接 | rows.Close() |
release_delay_us, conn_idle_us |
graph TD
A[sql.Open] --> B[db.Query]
B --> C{Get Conn from Pool?}
C -->|Yes| D[Execute & Stream]
C -->|No| E[Wait + New Conn]
D --> F[Rows.Close]
E --> D
F --> G[Conn returned to pool]
4.3 连接池热更新方案:运行时安全修改MaxOpenConns而不中断请求的原子切换机制
传统 sql.DB 的 SetMaxOpenConns() 会立即生效,但可能触发连接强制关闭,导致活跃事务失败。原子热更新需解耦配置变更与连接生命周期管理。
核心设计:双池影子切换
- 维护主池(active)与影子池(shadow),共享底层连接工厂
- 新连接按新配置创建,旧连接自然归还后不再复用
- 所有
Query/Exec调用路由到 active 池,无感知
// 原子切换逻辑(简化)
func (p *HotSwappablePool) UpdateMaxOpenConns(n int) {
p.mu.Lock()
defer p.mu.Unlock()
p.shadowCfg.MaxOpenConns = n
p.shadowPool.SetMaxOpenConns(n) // 预热影子池
}
该函数仅更新影子配置并预设参数,不改变当前活跃连接;真实切换由后台 goroutine 在连接空闲时渐进完成。
状态同步保障
| 状态项 | 主池 | 影子池 | 同步方式 |
|---|---|---|---|
| MaxOpenConns | 当前生效 | 待生效 | 写锁保护 |
| IdleCount | 实时统计 | 零(初始) | 池级独立计数 |
graph TD
A[收到 UpdateMaxOpenConns] --> B[锁定配置]
B --> C[更新 shadowCfg]
C --> D[预设 shadowPool 参数]
D --> E[启动平滑迁移协程]
E --> F[逐个回收超限 idle 连接]
4.4 多租户隔离优化:按业务域划分独立sql.DB实例并配额管控的落地模式
为规避租户间连接争用与慢查询扩散,系统将租户按核心业务域(如 payment、user、order)分组,每组独占一个 *sql.DB 实例,并绑定连接池与执行配额。
配额驱动的 DB 实例工厂
func NewDBForDomain(domain string) (*sql.DB, error) {
db := sql.Open("mysql", cfg[domain].DSN)
db.SetMaxOpenConns(cfg[domain].MaxOpen) // 硬性连接上限
db.SetMaxIdleConns(cfg[domain].MaxIdle) // 避免空闲连接耗尽资源
db.SetConnMaxLifetime(1 * time.Hour) // 主动轮换,防长连接僵死
return db, nil
}
MaxOpen 按 SLA 和 QPS 压测结果设定(如 payment: 80, user: 200),MaxIdle 设为 MaxOpen * 0.8 平衡复用与回收开销。
租户-域映射关系表
| TenantID | BusinessDomain | QuotaGroup |
|---|---|---|
| t_001 | payment | finance |
| t_012 | user | identity |
| t_045 | order | commerce |
运行时路由逻辑
graph TD
A[HTTP Request] --> B{TenantID → Domain}
B -->|payment| C[GetDBFromPool: finance]
B -->|user| D[GetDBFromPool: identity]
C & D --> E[Execute with context.WithTimeout]
第五章:连接池演进趋势与云原生适配展望
服务网格透明代理下的连接复用挑战
在 Istio 1.20+ 环境中,Sidecar(Envoy)默认启用 HTTP/1.1 连接复用,但 Java 应用若仍使用 Apache Commons Pool 2.x 配置 maxIdle=8 + minIdle=0,将导致连接被 Envoy 静默回收后应用层无法感知,引发 Connection reset 异常。某电商订单服务在灰度迁移至服务网格时,DB 连接错误率从 0.02% 飙升至 3.7%,最终通过启用 HikariCP 的 connection-test-query="SELECT 1" 并设置 validation-timeout=3000 解决。
多租户场景下的动态连接池分片
某 SaaS 平台采用 Kubernetes Namespace 级租户隔离,每个租户对应独立 PostgreSQL 实例。其连接池管理模块基于 Spring Boot Actuator 暴露 /actuator/pools/{tenant-id} 端点,并通过以下配置实现运行时伸缩:
spring:
datasource:
hikari:
pool-name: ${TENANT_ID}-pool
maximum-pool-size: ${MAX_POOL_SIZE:20}
minimum-idle: ${MIN_IDLE:5}
配合 Prometheus + Grafana 实时监控各租户池的 HikariPool-ActiveConnections 指标,当某租户并发请求持续 5 分钟 > 95% 阈值时,自动触发 kubectl patch cm tenant-config -p '{"data":{"MAX_POOL_SIZE":"35"}}' 更新配置。
Serverless 函数冷启动连接泄漏防控
AWS Lambda 运行时中,未显式关闭的 HikariCP 数据源会在容器复用时残留连接。某日志分析函数在启用 Provisioned Concurrency 后,出现连接数线性增长现象。根因是 @PostConstruct 初始化的连接池未绑定 Lambda 生命周期。修复方案采用静态 Holder 模式并注册 Shutdown Hook:
public class DBPoolHolder {
private static volatile HikariDataSource dataSource;
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (dataSource != null) dataSource.close();
}));
}
}
自适应容量调节机制
下表对比了三种自适应策略在高波动流量下的表现(测试环境:4c8g Pod,PostgreSQL 14,TPS 500→5000 阶跃变化):
| 策略 | 首次扩容延迟 | 连接建立失败率 | 资源峰值占用 |
|---|---|---|---|
| 固定大小(50) | — | 12.3% | 100% |
| 基于 CPU 触发扩容 | 42s | 4.1% | 89% |
| 基于连接等待队列长度 | 8.6s | 0.7% | 72% |
实际生产中采用后者,通过 Micrometer 定期采集 HikariPool-ThreadsAwaitingConnection 指标,当连续 3 个采样周期 > 阈值则调用 HikariConfig.setConnectionTimeout() 动态调整。
eBPF 辅助连接健康探测
在阿里云 ACK Pro 集群中,部署 eBPF 程序 tcp_health_probe.o 监听 connect() 系统调用返回值,当检测到 ECONNREFUSED 或 ETIMEDOUT 时,向用户态 agent 发送事件。该 agent 会立即标记对应连接为 INVALID 并触发 HikariCP 的 softEvictConnections(),避免传统 validationQuery 造成的 50ms+ 延迟。
flowchart LR
A[eBPF connect() hook] -->|error event| B[Userspace Agent]
B --> C{Is connection in pool?}
C -->|Yes| D[Soft evict & notify]
C -->|No| E[Log only]
D --> F[HikariCP internal cleanup]
弹性网络拓扑感知调度
某金融核心系统在混合云架构下,Kubernetes Scheduler 插件 TopoAwareScheduler 根据节点标签 topology.kubernetes.io/region=cn-shenzhen-az1 和数据库实例的 cloud.db.endpoint 地理位置,优先将 Pod 调度至同可用区。连接池初始化时自动注入 hikari.connection-init-sql=SET application_name = 'shenzhen-az1-order-service',便于 PG 统计各区域连接负载。
