Nginx失败重试中的HTTP协议幂等问题: non_idempotent

Nginx失败重试中的HTTP协议幂等问题: non_idempotentNginx 通过反向代理做负载均衡时 如果被代理的其中一个服务发生错误或者超时的时候 通常希望 Nginx 自动重试其他的服务 从而实现服务的高可用性 实际上 Nginx 本身默认会有错误重试机制 并且可以通过 proxy next upstream 来自定义配置 如果不了解 HTTP 协议以及 Nginx 的机制 就可能在使用过程中遇到各种各样的坑 例如服务出现了错误或超时却未重试 或者一些例如创建订单或发送短信这

Nginx通过反向代理做负载均衡时,如果被代理的其中一个服务发生错误或者超时的时候,通常希望Nginx自动重试其他的服务,从而实现服务的高可用性。实际上Nginx本身默认会有错误重试机制,并且可以通过proxy_next_upstream来自定义配置。

如果不了解HTTP协议以及Nginx的机制,就可能在使用过程中遇到各种各样的坑。例如服务出现了错误或超时却未重试,或者一些例如创建订单或发送短信这类的HTTP接口,客户端只发送一次请求,后台却由于Nginx重试导致创建了多个订单,或者收到多条短信,导致一些业务上的问题。

proxy_next_upstream

在Nginx配置文件中,proxy_next_upstream用于指定在什么情况下Nginx会将请求转移到其他服务器上。其默认值是proxy_next_upstream error timeout,即发生网络错误以及超时,才会重试其他服务器。默认情况下服务返回500状态码是不会重试的,如果想在响应500状态码时也进行重试,可以配置:

proxy_next_upstream error timeout http_500; 

当然还有http_502http_503http_404等可以指定在出现哪些状态码的情况下需要重试。具体配置项可以参考官方文档: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream 。

用一个最简单的例子来测试一下该特性,例如下面是Spring Boot写了一个简单的HTTP接口,返回500状态码:

@SpringBootApplication public class NginxRetryApplication { 
    public static void main(String[] args) { 
    SpringApplication.run(NginxRetryApplication.class, args); } } @RestController class TestController { 
    @RequestMapping("/") public String test() { 
    System.out.println("收到一个请求"); // 打印日志 throw new RuntimeException(); // 抛出异常, 返回500状态码 } } 

分别使用9030和9031两个端口号启动该Spring Boot服务,然后Nginx配置好负载均衡:

upstream nginxretry { server 127.0.0.1:9030 max_fails=0; server 127.0.0.1:9031 max_fails=0; } server { listen 9039; location / { proxy_pass http://nginxretry; proxy_next_upstream error timeout http_500; } } 

注意:以上配置中max_fails=0是为了更方便的测试Nginx错误重试机制。max_fails默认值是1,用于指定一个server在一段时间内(默认10s)发生错误次数达到多少次,Nginx就会自动将该服务器下线。这里设置为0是禁用这个特性,防止在测试过程中服务器被踢下线不好测试。线上环境下一般不会设置max_fails=0

配置完成后重启Nginx,使用GET方式请求 http://localhost:9039/ ,再分别查看9030和9031两个端口号对应的服务日志,可以发现两个服务都收到请求,也就是Nginx在访问其中一个服务收到500错误状态码后,又尝试去访问另一个服务。

再次使用POST方式请求 http://localhost:9039/ ,再分别查看9030和9031两个端口号对应的服务日志,可以发现只有一个服务收到请求。也就是当请求类型是POST时,Nginx默认不会失败重试。如果想让POST请求也会失败重试,可以继续向下阅读。

non_idempotent

在Nginx文档中可以看到proxy_next_upstream有一个选项non_idempotent:

normally, requests with a non-idempotent method (POST, LOCK, PATCH) are not passed to the next server if a request has been sent to an upstream server (1.9.13); enabling this option explicitly allows retrying such requests;

通常情况下,如果请求使用非等幂方法(POST、LOCK、PATCH),请求失败后不会再到其他服务器进行重试。加上non_idempotent选项后,即使是非幂等请求类型(例如POST请求),发生错误后也会重试。

如果想让POST请求也会失败重试,需要配置non_idempotent

upstream nginxretry { server 127.0.0.1:9030 max_fails=0; server 127.0.0.1:9031 max_fails=0; } server { listen 9039; location / { proxy_pass http://nginxretry; proxy_next_upstream error timeout http_500 non_idempotent; } } 

重启Nginx后再次使用POST请求访问 http://localhost:9039/ ,再分别查看9030和9031两个端口号对应的服务日志,可以看到两个服务都收到请求,也就是POST请求也会重试了。不过实际上在生产环境中,不建议加上non_idempotent选项,具体原因可以继续往下阅读。

什么是幂等方法

在HTTP协议规范中,对幂等方法(Idempotent Method)做了以下定义:

A request method is considered “idempotent” if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request.

如果使用该方法的多个相同请求对服务器的预期效果与单个请求的效果相同,则认为请求方法是幂等的。常见的HTTP请求方法中,GET是幂等的,而POST是非幂等的。如果在回答面试题”GET和POST区别”时能答出这一点,才能说明对HTTP协议有一定的理解。

在做业务开发是如何理解幂等性,举个最简单的例子:GET方法一般用于获取数据,如果获取的是数据库数据,对应的是SELECT语句。同样的SELECT语句执行一次还是多次,都不会影响数据。而POST一般对应INSERT,如果执行多次后,可能会造成数据重复插入的问题。所以不要使用GET方法做一些INSERT操作,在业务开发时要遵循HTTP协议规范。

生产环境中为什么不建议加上non_idempotent选项?因为无论是发生500错误还是timeout,服务器上的业务可能都已经执行过了,而重试会导致非幂等方法重复执行,从而导致业务问题,例如一个请求会创建了多个订单,或者收到多条短信的问题。

参考文档

  • http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream
  • https://tools.ietf.org/html/rfc7231

关注我

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/230711.html原文链接:https://javaforall.net

(0)
上一篇 2026年2月7日 上午8:01
下一篇 2026年2月7日 上午8:22


相关推荐

  • 浅谈SPU和SKU

    浅谈SPU和SKUSPU amp SKU

    2026年3月26日
    1
  • lunix常用命令「建议收藏」

    lunix常用命令「建议收藏」文件管理命令ls      显示文件或目录   -l     列出文件详细信息l(list)   -a     列出当前目录下所有文件及目录,包括隐藏的a(all)mkdir    创建目录   -p     创建目录,若无父目录,则创建p(parent)cd   

    2022年10月3日
    5
  • python中多重if语句用法_python中if语句用法

    python中多重if语句用法_python中if语句用法python 中 if 语句用法以下实例通过使用 if elif else 语句判断数字是正数 负数或零 推荐 python 教程 实例 Python3 0 Filename test py authorby www php cn 用户输入数字 num float input 输入一个数字 ifnum gt 0 print 正数 elifnum

    2026年3月18日
    2
  • php和asp网络验证码,Verifycode 1个简单的网页图片验证码的示例程序,基本上现有 字和字母都可以识别。 WEB(ASP,PHP,…) 238万源代码下载- www.pudn.com…

    php和asp网络验证码,Verifycode 1个简单的网页图片验证码的示例程序,基本上现有 字和字母都可以识别。 WEB(ASP,PHP,…) 238万源代码下载- www.pudn.com…文件名称:Verifycode下载收藏√[54321]开发工具:C#文件大小:3201KB上传时间:2014-06-12下载次数:4详细说明:1个简单的网页图片验证码的示例程序,基本上现有的数字和字母都可以识别。-asimplewebverifycodesampleprojectwithnumberandalphabetrecognit…

    2022年7月15日
    16
  • 史上最全面的JTAG和SWD接口的定义/STM32/STM8工程师的福音/JTAG转SWD接口仿真/告别杂乱的仿真线/终于讲清楚了JTAG/SWD

    史上最全面的JTAG和SWD接口的定义/STM32/STM8工程师的福音/JTAG转SWD接口仿真/告别杂乱的仿真线/终于讲清楚了JTAG/SWD一、前言作为一名嵌入式工程师,相信大家都十分清楚MCU开发或者ARM开发都避免不了关键的研发过程,产品研发过程中的程序调试更是举足轻重般的存在。从8051内核到ARM内核,自己也接触了很多的调试工具和调试手段;今天在此给大家分享一下使用ST-LINK仿真调试器的一些基础知识和好物推荐。二、ST-LINK仿真器说明ST-Link是用于STM8和STM32微控制器在线调试器和编程器,ST-Link本身具有SWIM、JTAG/SWD通信接口,适用于STM8和STM32微控制器的软件调试仿真。

    2022年4月25日
    53
  • Java–链表ListNode

    今天我们来介绍一下Java中的链表,作者依旧尽量用白话解释,希望能帮到大家链表链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。上面是链表的定义,那么我们用通俗点的语言…

    2022年4月6日
    162

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号