使用 gcloud sql instances restart 时,如何确保连接池在零停机窗口内完成重连?

解读

在国内互联网面试场景里,这道题考察的是“可控重启”而非“无脑重启”。
面试官真正想听的是:

  1. 你能否把 Cloud SQL 提供的原生高可用能力(HA 实例、故障转移)与 应用侧连接池行为 结合起来,做到“重启对业务透明”;
  2. 你能否给出 可落地的代码/脚本/配置片段,让 DBA 和研发都能按图施工;
  3. 你能否在 30 秒~2 分钟 的国内可接受中断 SLA 内,把 连接池重连事务重试 全部闭环。

一句话:不是“怎么重启”,而是“怎么让重启看起来像没重启”

知识点

  1. HA 实例架构:主实例与热备实例分处不同可用区,通过内部健康检查实现 <30 s 自动故障转移,重启命令默认先切到备节点再重启原主节点。
  2. 重启类型
    • gcloud sql instances restart 默认走 切换式重启(先 failover 再重启旧主),
    • --no-failover 则为 就地重启(直接断连),国内生产环境禁止直接使用。
  3. 连接池四大参数
    • initial-size / min-idle:保持预热连接,
    • max-lifetime:单条连接最大存活时间,建议 ≤ 5 min 防止 MySQL 的 wait_timeout 踢连接,
    • idle-timeout:空闲回收阈值,建议 30 s
    • connection-test-queryconnection-timeout:探活语句 SELECT 1验证间隔 ≤ 5 s
  4. 探活策略
    • testOnBorrow=false + testWhileIdle=true(HikariCP 默认)即可在后台异步探活,
    • Google 推荐配合 Cloud SQL Auth Proxy-max_connections-refresh_config_interval=30s 参数,让代理层先于池发现 IP 漂移。
  5. 事务重试
    • MySQL 引擎需捕获 SQLState=08xxx 与错误码 2013/2006
    • PostgreSQL 需捕获 SQLState=57P01/57P02/08003
    • 采用 ** exponential backoff + jitter**,最多 3 次,总耗时 < 15 s
  6. 国内网络合规
    • 若走 Private IP,需确保 VPC 对等Private Service Connect 已打通,
    • 若走 Public IP 必须加 SSL=requiredIP 白名单只放 NAT 出口,防止重启后新 IP 未同步白名单导致池永久拒绝。
  7. 观测指标
    • stackdriver metriccloudsql.googleapis.com/database/mysql/connections/failed 突增即代表池未重连成功,
    • 应用侧埋点:连接池 getConnection() 耗时 P99 超过 1 s 即触发告警。

答案

步骤一:强制使用 HA 实例并执行切换式重启

gcloud sql instances restart prod-mysql \
  --project=my-gcp-project \
  --async  # 立即返回,脚本可继续往下做健康检查

关键点绝不加 --no-failover,让 Google 先完成 <30 s 的跨区切换。

步骤二:连接池配置(以 Spring Boot 2.x + HikariCP 为例)

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      max-lifetime: 300000        # 5 min,防止重启后旧连接仍存活
      idle-timeout: 30000
      connection-test-query: SELECT 1
      validation-timeout: 5000
      leak-detection-threshold: 10000

解释max-lifetime < 云端 wait_timeout,保证重启后池会主动淘汰可能已失效的连接;validation-timeout=5 s 让探活失败快速抛错,触发池重建。

步骤三:事务层自动重试(基于 Spring Retry)

@Retryable(value = {SQLException.class, QueryTimeoutException.class},
           maxAttempts = 3, backoff = @Backoff(delay = 200, multiplier = 2, random = true))
public void bizOperation() {
    // 正常业务 SQL
}

注意:只重试 读事务幂等写;非幂等写需业务层去重或采用 PTC(Per-Transaction UUID) 防重框架。

步骤四:重启流程编排(国内常用 GitLab CI + Ansible)

  1. pre-restart:通过 MHA Manager自研脚本 把读写流量 禁写 3 s,等待活跃事务 ≤ 5;
  2. restart:执行上述 gcloud 命令;
  3. post-restart:轮询 Cloud SQL Admin API https://sqladmin.googleapis.com/v1/projects/{proj}/instances/{id}/connectSettings 直到 ipAddress 字段变更且 state=RUNNABLE
  4. traffic backAnsible 批量 reload Envoy/SLB,让新连接落到新 IP;
  5. 观测Grafanajetty_connections_activecloudsql_failed_connections2 min 内回归基线即判定成功。

结果

  • 业务侧感知到的中断TCP 重传超时 1~3 s
  • 连接池重连30 s 内全部完成;
  • 零人工干预,符合国内 SRE 1-5-10 要求(1 min 发现、5 min 定位、10 min 恢复)。

拓展思考

  1. 如果实例不是 HA,而是单节点开发库,能否做到“零停机”?
    不能。单节点重启必然 30~90 s 不可用,此时只能把连接池 max-lifetime 降到 60 s 并开启 熔断器(如 Sentinel),让业务快速失败并返回 “系统维护中”,重启完再自动闭合熔断,把“停机”转成“降级”

  2. 国内双活场景下,跨地域只读实例如何同步重连?
    采用 Cloud SQL 跨区域只读副本 时,重启主库会触发 VIP 漂移到只读副本。连接池需配置 读写分离插件(ShardingSphere / MyCat)读库列表动态刷新间隔 ≤ 10 s,否则重启后新只读 IP 未同步,读流量依旧打到旧 IP 导致 4040 错误

  3. Terraform 基础设施即代码如何固化上述流程?
    null_resource + local-exec 调用 gcloud 命令,并把 restart 后校验脚本 写成 Terraform provisioner,让 GitLab Pipeline 只跑 terraform apply 就能完成 “代码即重启”杜绝人工黑屏操作,满足国内金融云 “变更可审计” 合规要求。