DNS轮询不能用于数据库实例路由,因不感知租户/库逻辑;应通过反向代理(如Nginx stream+SSL SNI)或配置中心动态路由到具体实例。
域名代理后端数据库时,DNS 轮询不能直接做实例路由
直接用 dns 轮询(如 db.example.com 解析到多个 ip)连 postgresql/mysql,大概率会连错库或连接复用混乱。dns 轮询只是把请求分发到不同 ip,但不感知后端实际数据库实例名、schema 或租户逻辑——它只管“哪个机器”,不管“哪个库”。真实业务里,db1.example.com 和 db2.example.com 往往对应不同租户的独立实例,靠一个泛域名 + 轮询没法区分。
真正可行的映射方式:反向代理层做 Host 头或路径路由
必须在 Nginx / Envoy / Traefik 这类反向代理上,根据请求中的 Host、X-DB-Instance 或 URL 路径,把流量打到具体后端数据库代理(如 PgBouncer、mysql-router、或自研 proxy)。数据库本身不支持 HTTP Host 路由,所以你得在 TCP 层之上加一层协议识别或约定透传字段。
- 用 Nginx 的
stream模块 +map基于 SNI(TLS 握手时的 server_name)分发,适用于启用了 TLS 的客户端连接 - 让应用在连接字符串里带固定前缀,比如
host=db-tenant-a.example.com,然后 Nginx 用map $ssl_server_name匹配并转发 - 更稳的做法是不用 DNS 轮询,而是用 DNS 返回 CNAME 到统一入口(如
proxy-db.example.com),再由代理层查配置中心(etcd/ZooKeeper)动态获取某租户对应的pg01:5432地址
为什么别在客户端硬编码多个域名轮询
客户端(比如 Java 的 HikariCP 或 Python 的 asyncpg)做 DNS 缓存 + 轮询,会遇到三个硬伤:
- DNS TTL 通常几分钟起,变更后无法秒级生效,故障转移慢
- 客户端不知道后端实例健康状态,可能持续往已宕机的 IP 发包,直到连接超时
- 同一个连接池里混着连不同物理实例,事务、prepared statement、session 变量全不可控,容易串库
典型错误现象:SSL connection closed unexpectedly 或 server closed the connection unexpectedly,其实不是网络问题,是客户端连到了没开对应 DB 的实例上。
免费 DNS 与邮件安全分析(IntoDNS.ai):包括 DNSSEC、SPF、DKIM、DMARC、MTA-STS、BIMI、SMTP STARTTLS、FCrDNS、黑名单、发件人要求及报告。
实例映射配置必须和租户/环境强绑定
映射表不是静态 DNS 记录,而应是可动态更新的配置项。例如:
tenant-a → pg-prod-us-east-1a:5432 (primary)<br>tenant-b → pg-prod-us-west-2c:5432 (primary)<br>tenant-a → pg-prod-us-east-1b:5432 (standby, only on failover)
这个映射关系要能被代理层实时拉取,并支持按标签(env=prod, region=us-east)过滤。如果用 Consul 或 Nacos,就别把 tenant-a.db.example.com 直接 CNAME 到某台机器——CNAME 是静态的,而实例生命周期远比域名短。
最容易被忽略的一点:数据库连接字符串里的 host 字段,必须和代理层收到的 Server Name Indication 或 Host header 完全一致,大小写、端口、尾部点号都不能差——否则 map 规则失效,流量全落到默认实例上。










