默认开启重试会导致非幂等post请求(如订单插入)重复执行;proxy_next_upstream对超时也触发重试,需显式限制tries和timeout,并禁用timeout选项或关闭重试以避免双写。

默认开启重试,但多数人根本没意识到它正在悄悄重发 POST 请求——尤其是插入操作重复写库,就是这么来的。
proxy_next_upstream 是重试开关,不是“失败才重试”
很多人以为 proxy_next_upstream 只在后端挂了才触发重试,其实它对“超时”也敏感。只要配置里包含 timeout(默认就带),Nginx 就会在任意一个超时环节(连接、发送、读取)立刻换台服务器重发当前请求。
-
proxy_connect_timeout 2s:2 秒连不上后端,就换下一台——哪怕后端只是慢,还没挂 -
proxy_read_timeout 5s:后端已连上、开始处理,但 5 秒内没返回响应头,也换台重发 - POST /order 提交订单耗时 8 秒?只要
proxy_read_timeout设成 5,大概率打到两台 Tomcat 上
重试次数和总耗时必须显式限制
不设限的重试等于放大问题:proxy_next_upstream_tries 和 proxy_next_upstream_timeout 必须配,否则可能重试到天荒地老或所有节点都试一遍。
-
proxy_next_upstream_tries 2:最多尝试 2 次(含首次),即最多转发 1 次 -
proxy_next_upstream_timeout 10s:从第一次请求发出起,10 秒内所有重试必须结束,超时直接返回 502 - 注意:
proxy_next_upstream_tries默认是 0(不限),proxy_next_upstream_timeout默认也是 0(不限)
非幂等请求要禁用重试,别信 non_idempotent
non_idempotent 这个选项名字很迷惑,但它不是“禁止重试非幂等请求”,而是“允许重试非幂等请求”。想让 POST/PUT 不重试,得关掉 timeout 和 error,只留安全项,或者直接关掉。
- 安全写法(推荐):
proxy_next_upstream error invalid_header http_500 http_502 http_503 http_504—— 去掉timeout,避免因慢而重发 - 更彻底:
proxy_next_upstream off—— 整个重试机制关闭,适合已知会长耗时的接口 - 别用
non_idempotent:它会让 Nginx 在 POST 已发出去后还继续重试,数据库双写风险更高
真正影响重试行为的还有 upstream 的 max_fails
upstream 块里的 max_fails 和 fail_timeout 不控制单次重试,但决定“哪些后端可被选中重试”。如果某台机器刚被标记为不可用,它就不会出现在本次重试候选列表里。
-
server 192.168.1.10:8080 max_fails=2 fail_timeout=30s:连续 2 次失败后,30 秒内不往这台发新请求 - 但它不影响当前正在重试的请求——当前请求仍可能被发过去,直到判定失败才切走
- 所以不能靠
max_fails防止重复执行,它只是被动健康检查,不是重试策略本身
最易被忽略的一点:重试发生在 Nginx 层,后端完全感知不到这是“重试请求”还是“新请求”,Header、Body、URL 全都原样复制。如果你没做请求幂等校验(比如用 Idempotency-Key 或业务唯一键),那重试 = 数据污染。











