Skip to content

请求在哪一跳分流,响应就走哪条路吗 ​

DNS 改了记录,客户端还在连接旧地址;入口改成 TCP 代理,后端看到的仍是代理地址。选后端的位置,会改变故障切换和身份传递的边界。

讨论负载均衡时,先画出请求和响应的双向路径,再讨论轮询或最少连接。客户端重新请求、解析地址、代理转发和内核转发,涉及不同的协议参与者。

本文用 Nginx 1.30.0、HAProxy 3.4.2、CoreDNS 1.14.7 和 CPython 3.14.8,在无宿主机端口映射的 Docker 网络里验证可见行为。NAT、直接路由、跨机器带宽和真实故障切换只作示意,没有运行 LVS 或网络性能测试。

一、把「选择」和「返回」分别画出来 ​

重定向路径客户端重定向入口返回 Location业务后端① 原请求② 302 / 307③ 客户端向业务后端另发请求④ 业务响应直接回到客户端全代理路径客户端HTTP / TCP 代理业务后端请求后端连接业务响应经过代理;全代理、NAT 与 DR 不能混为同一种路径
图 1 · 示意:重定向后客户端另发请求;全代理模式的业务响应仍经过代理。DNS 只参与地址发现

DNS 返回的是供客户端使用的地址,后续应用响应通常不经过 DNS 服务。HTTP 重定向则要求客户端处理另一个 URL。反向代理和 TCP 代理会建立通向后端的连接,响应仍需经过代理。

方式后端选择发生处响应经过分流组件吗主要约束
HTTP 重定向收到 HTTP 请求后返回新位置首次重定向响应经过;第二次请求的业务响应可直回客户端客户端跟随策略、方法语义、目标证书与鉴权
DNS地址解析阶段业务响应通常不经过 DNS缓存、解析器行为、已有连接
七层反向代理理解 HTTP 后选 upstream通常经过HTTP 语义、缓冲、重试、可信转发头
TCP 代理建立连接时选后端经过连接级分配、代理连接的源地址
内核 NAT 转发按网络与连接规则选目的地常见 NAT 拓扑要求回程匹配转换路径路由、连接跟踪与地址转换规则
直接路由(DR)入口转发到选中的后端设计上业务响应可由后端直回VIP、ARP、二层可达性与回程路由

后两行是拓扑示意,不是对任意四层产品的默认行为承诺。TCP 代理也不能代表所有四层转发:全代理、NAT 与 DR 的连接和源地址处理各不相同。

连接复用会延长某次选择的影响。DNS 更新之后,客户端仍可能复用原来的连接;连接级代理也未必对同一连接里的每个 HTTP 请求重新选后端。集群内的具体路径可对照一次请求在集群里的经过。

二、302 不适合承担「原样重放 POST」的保证 ​

实验入口把 POST 重定向到同源的另一路径,输入只是 demo=1。使用 CPython 3.14.8 的 urllib.request 自动跟随后:

text
302:目标收到 GET,请求体为空
307:客户端没有自动跟随 POST,向调用者返回 307

第二行值得单独看:307 的协议语义要求自动重定向时保持方法,但不强迫每个客户端替调用者重放请求。应用仍需处理库的限制、请求体能否重放、跨目标鉴权和重定向次数。

RFC 9110允许客户端在跟随 302 时把 POST 改为 GET;需要保留方法的临时重定向应考虑 307,永久场景对应 308。这里的具体自动跟随结果只代表该 Python 客户端,没有冒充浏览器实验。重定向到另一个域名时,还要检查 Cookie、认证头和 HTTPS 证书;不要把原请求的全部凭据直接交给新地址。

三、DNS TTL 没有检查后端是否健康 ​

实验由一个本地权威应答器返回 A 记录和 5 秒 TTL,CoreDNS 只负责转发与缓存,成功缓存的上限设为 5 秒、最小 TTL 设为 0。先查询缓存,再修改权威答案:

查询时间与对象地址
修改前,查询缓存203.0.113.10
修改后,直接查询权威端203.0.113.20
修改后立即查询缓存203.0.113.10
缓存过期后重新查询203.0.113.20

地址属于文档示例网段,实验没有连接它们。这验证的是旧答案在 TTL 内仍可被缓存返回,没有模拟地址对应节点宕机。CoreDNS 的缓存配置见插件文档。

健康检查修改了权威记录,不会主动清掉所有缓存。降低 TTL 也要等旧 TTL 结束,且应用自己的缓存和连接池还会增加下一层影响。没有新的解析请求,就没有获取新地址的机会。DNS 与 Java 客户端缓存进一步区分了这些层次。

四、后端看到的地址由连接和信任链决定 ​

同一个实验客户端依次直连后端、经过 Nginx、经过 HAProxy TCP 模式。后端分别记录 socket 的对端地址和 X-Forwarded-For:

路径socket 对端X-Forwarded-For
直连客户端容器无
Nginx HTTP 代理Nginx 容器实验入口写入的客户端容器地址
HAProxy TCP 代理HAProxy 容器无

Nginx 配置在本实验只有一个可信入口:

nginx
proxy_set_header X-Forwarded-For $remote_addr;

这行代码覆盖客户端提交的同名头,只传递该入口实际看到的对端地址。真实的多层代理需要明确哪些上游可信、如何解析地址链;从任意请求头直接读取「客户端 IP」不能作为身份或权限依据。

HAProxy 在实验中按 TCP 全代理转发字节,没有解析或补充 HTTP 头,也没有启用透明代理。若用 PROXY protocol 传递连接信息,两端必须同时配置并限制可信发送者;不能只打开一端。本文没有验证这种配置。这里的差异来自代理模式,与选择轮询还是最少连接无关;选择策略的故障边界另见慢实例与快速失败实例。

五、代理允许重试,会放大哪种不确定性 ​

实验让首选后端完整读取 POST 的请求体后返回 503,备用后端返回 200。两个路径都配置了 http_503 和最多两次尝试,只有第二个增加 non_idempotent:

nginx
proxy_next_upstream http_503 non_idempotent;
proxy_next_upstream_tries 2;

结果为:

配置首选后端收到 POST备用后端收到 POST客户端结果
没有 non_idempotent1 次0 次503
有 non_idempotent1 次1 次,包含相同请求体200

这是对实验配置的验证,没有写入订单或扣款。首选后端已经接收请求,503 并不能证明业务没有发生;启用重试需要业务级幂等或可查询的执行结果。Nginx 文档还要求能向客户端继续返回结果,已经发出响应的中途错误不能靠换 upstream 修复;能否重放请求体也受缓冲与转发进度影响。proxy_next_upstream 文档列出了这些条件。

确定入口方案时,验收对象应包括客户端实际使用的重定向库、DNS 缓存、连接池、请求体重放和来源地址链。重试预算与幂等约束见超时预算与重试放大,不能仅以「最后返回 200」判断请求只执行过一次。


配套实验

参考资料

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权