1. 背景:
中远海运生产部署采用HA部署CMP( 1个NGINX + 2个CMP 部署方案,自行脑补下架构图) ,分别部署在3台虚拟机上。
2. 现象:
生产上线前夜,测试人员反映,访问负载均衡的IP地址,有时会出现502错误Bad Gateway错误,过会页面又能正常访问的情况。在负载均衡502的时候,直接访问CMP的两台服务器IP,服务器一切正常。(图片非现场图!! 当是没截图!!内容是一致的!)
补个现场图。。
3. 排查过程:
因为现象是直接访问CMP没什么问题,测试也没说有什么特殊情况,突然就502了,nginx没有开启记录access.log和error.log,无从下手,那硬着头皮就直接考虑是Nginx配置的问题。询问当时部署NGINX配置的人员,应该配置来源于wiki直接拿过来用了。 看了现场的配置(配置如下),Nginx配置不多,有一个proxy_next_upstream不认识,于是动手google了下,这篇文章说的很透彻 http://www.dczou.com/viemall/603.html
|
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增大
|
究根追地,本身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之后后端也收不到请求了