资讯专栏INFORMATION COLUMN

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

edgardeng / 1875人阅读

摘要:再次使用方式请求,再分别查看和两个端口号对应的服务日志,可以发现只有一个服务收到请求。常见的请求方法中,是幂等的,而是非幂等的。而一般对应,如果执行多次后,可能会造成数据重复插入的问题。

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... 。

用一个最简单的例子来测试一下该特性,例如下面是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...

https://tools.ietf.org/html/r...

关注我

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/40603.html

相关文章

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

    摘要:再次使用方式请求,再分别查看和两个端口号对应的服务日志,可以发现只有一个服务收到请求。常见的请求方法中,是幂等的,而是非幂等的。而一般对应,如果执行多次后,可能会造成数据重复插入的问题。 Nginx通过反向代理做负载均衡时,如果被代理的其中一个服务发生错误或者超时的时候,通常希望Nginx自动重试其他的服务,从而实现服务的高可用性。实际上Nginx本身默认会有错误重试机制,并且可以通过...

    includecmath 评论0 收藏0
  • 我们如何在Linkerd 2.2里设计重试

    摘要:在这篇文章中,我们描述了我们如何在里设计重试,使能够在最小化风险的同时,自动提高系统可靠性。配置重试的最常用方法,是指定在放弃之前执行的最大重试次数。超时时,将取消请求并返回响应。但是在上面的服务配置文件中,我们将在服务器端指定重试政策。 showImg(https://segmentfault.com/img/bVbo113?w=4400&h=1007);作者:Alex Leong ...

    Mike617 评论0 收藏0
  • 使用 Resilience4j 框架实现重试机制

    摘要:重试会增加的响应时间。提供了辅助方法来为包含远程调用的函数式接口或表达式创建装饰器。如果我们想创建一个装饰器并在代码库的不同位置重用它,我们将使用。 在本文中,我们将从快速介绍 Resilience4j 开始,然后深入探讨其 Retry 模块。我们将了解何时、如何使用它,以及它提供的功能。在此过程中,我们还将学...

    番茄西红柿 评论0 收藏2637
  • 超时重试思考-非幂等请求

    摘要:如果要对幂等操作重试请求优先参考上面的回答,下面是的示例参考网站关于该参数的详细解释学习总结与模块三 转载请注明出处 http://www.paraller.com 原文排版地址 点击跳转 转载请注明出处 来源:parallers blog upstream www.paraller.com { server 10.29.209.14*:3810; ...

    Miracle 评论0 收藏0
  • Python重试机制是什么,下文给大家解答

      小编写这篇文章的主要目的,主要是给大家讲解一下,关于Python机制的一些问题,比如重新调试的机制是什么呢?应用到它的场景还是很多的,下面跟小编一块去学习吧。  介绍:  为了避免网络问题出现的错误,比如网络延迟或者是宕机,往往都会出现请求超时的问题。  这里要给大家介绍的是一个第三方库-Tenacity(标题中的重试机制并并不准确,它不是Python的内置模块,因此并不能称之为机制),它实现...

    89542767 评论0 收藏0

发表评论

0条评论

edgardeng

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<