震惊!一个http请求导致生产环境宕机100秒!—— Nginx 502 bad gateway的解决过程

       1. 背景:         

       中远海运生产部署采用HA部署CMP( 1个NGINX + 2个CMP 部署方案,自行脑补下架构图) ,分别部署在3台虚拟机上。

       2. 现象:

       生产上线前夜,测试人员反映,访问负载均衡的IP地址,有时会出现502错误Bad Gateway错误,过会页面又能正常访问的情况。在负载均衡502的时候,直接访问CMP的两台服务器IP,服务器一切正常。(图片非现场图!! 当是没截图!!内容是一致的!)

      

       补个现场图。。

      

扫描二维码关注公众号,回复: 9714277 查看本文章

       3. 排查过程:

        因为现象是直接访问CMP没什么问题,测试也没说有什么特殊情况,突然就502了,nginx没有开启记录access.log和error.log,无从下手,那硬着头皮就直接考虑是Nginx配置的问题。询问当时部署NGINX配置的人员,应该配置来源于wiki直接拿过来用了。 看了现场的配置(配置如下),Nginx配置不多,有一个proxy_next_upstream不认识,于是动手google了下,这篇文章说的很透彻 http://www.dczou.com/viemall/603.html

upstream 10.18.9.101 {

    ip_hash;

    server 10.18.9.102:80 fail_timeout=100s max_fails=1;

    server 10.18.9.103:80 fail_timeout=100s max_fails=1;

}

server {

    listen 80;

    location / {

        proxy_pass http://10.18.9.101;

        add_header X-Upstream  $upstream_addr;

        proxy_set_header X-Real-IP $remote_addr;

        # proxy_next_upstream配置当前定向到的后端,返回下列内容时重新分配新的后端

        proxy_next_upstream error timeout http_404 http_500 http_502 http_503;

    }

}

     proxy_next_upstream作用我这里简单描述一下:当nginx转发请求至upstream机器列表中的机器时,若机器返回如响应超时(这个超时时间nginx是有默认值),http请求返回500这些请求时,自动找到upstream机器列表中的下一台机器,再次执行该http request。

     可以看到配置的是 proxy_next_upstream error timeout http_404 http_500 http_502 http_503, 即error timeout http_404 http_500 http_502 http_503这6种情况,都会触发nginx切换操作(限于幂等的HTTP请求)。

     那么问题来了,500请求是我们前端debug调试中经常看到的请求。中远海开发过程中,代码中有使用如throw new Exception,F2CException.throwException("业务异常信息") ,返回业务校验失败情况。如图是我随便写了个onchange函数触发xhr,返回异常,可以看到是500,是很普遍的状况。

      对于如用户的非法输入,无论在任何服务器的校验都是无法通过的,如代码在此抛出异常,所有服务器都会因此,在这种情况下,NGINX无论切到upstream机器列表的任一机器都会返回500,所以NGINX认为upstream的所有机器都已经不可用了。对于upstream里无机器可用,于是返回了502错误。

      那为什么过了一段时间用户又能恢复访问呢,在回头看看我们nginx中upstream的配置,

   server 10.18.9.103:80 fail_timeout=100s max_fails=1;

      fail_timeout设置成了100s ,那么100s内nginx不会再请求这台机器机器,所以当发生proxy_next_stream时,upstream里所有机器都陷入100s不被请求的怪圈,所以就出现了文中一开始的现象,用户过会刷了就好,但在100s内,无论怎么搞都是502的问题。

      4. 结论:         

       1) 临时解决方案:proxy_next_upstream去掉http_500,fail_timeout减小,max_fails增大

upstream 10.18.9.101 {

    ip_hash;

    server 10.18.9.102:80 fail_timeout=10s max_fails=2;

    server 10.18.9.103:80 fail_timeout=10s max_fails=2;

}

server {

    listen 80;

    location / {

        proxy_pass http://10.18.9.101;

        add_header X-Upstream  $upstream_addr;

        proxy_set_header X-Real-IP $remote_addr;

        # proxy_next_upstream配置当前定向到的后端,返回下列内容时重新分配新的后端

        proxy_next_upstream error timeout http_404 http_502 http_503;

    }

}

       究根追地,本身nginx proxy_next_stream http_500是没有问题的,根本的问题还是在于异常的使用和http状态码的混用。

       终极解决方案:

       1)合理的抛出异常,如用户输入校验失败,应该  throw new IllegalArgumentException();

       2)  正确的异常处理机制,如controller抛出异常,Springboot全局catch,应当返回 400错误 Bad request,而非500错误;

        所以看起来是nginx配置有误,实质上开发不规范啊。。。

       下面有兴趣的可以验证下:

       于是动手做了个实验,

       1. 本地用docker跑了个Nginx : docker run --name some-nginx -d -P nginx:1.17.1

       2. 本地启动vm-service作为后端服务:

       3. 配置nginx,nginx负载本地的vm-service

        

        4. 本地随便做一个Http controller, 然后控制台随便个打印个时间戳什么的,打印完直接返回F2CException.throwException

        5. 界面触发请求,直接看效果图:

       

       

       6. 再请求

      

         结合图片还是非常符合我们的预期的,因为upstream配置了两台机器,所以同一接口执行了两次(max_fails = 1),符合我们认为的proxy_next_stream机制进行切换。

         再次请求的话,直接nginx返回错误页面(页面依据nginx配置而不同) , 502之后后端也收不到请求了

发布了28 篇原创文章 · 获赞 50 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_21873747/article/details/99574727
今日推荐