一个nginx配置解决生产上的幽冥请求

nginx经常被用来做生产的代理和负载均衡,它高效的处理能力,得到了很多公司的青睐。可是,随着业务的不断发展,随之而来,可能会因为nginx产生一些意想不到的问题,比如说幽冥请求。

本文所说的幽冥请求泛指以下几种情况:

  • 一次客户端请求,前端能够收到返回的信息,但是后端位于不同VM的同个程序却被请求了多次,请求参数完全一致,并全部执行正常
  • 一次客户端请求,前端报异常返回,但是后端位于不同VM的同个程序被请求多次,从业务日志角度排查,程序返回正常
  • 客户端上传请求,出现一次上传了多个同样文件的情况

所谓的幽冥请求,其实就是本不该出现却又出现的请求。这里限定情况为明确客户端确实请求了一次的情况下。

在遇到过的幽冥请求案例中,大部分情况,程序上线后运行正常,经过了长时间未更改代码的运行后,突然就发生了幽冥请求情况。幽冥请求可能一直出现,也可能偶尔出现。通过nginx日志,可以确认来自客户端的请求只有一次,可是请求返回的时间较长。通过程序日志确定,请求正常可达,并且有正常的返回。事件发生后程序员都是没法从本地或者测试环境进行重现的。

问题究竟在哪呢?其实,问题出现在nginx的原生模块ngx_http_proxy_module配置上。对于ngx_http_proxy_module,其实大部分使用过的技术人员应该都不默认,它主要实现的是反向代理的功能,常用的配置如下:

 location / {
        expires -1;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host  $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://$UPSTREAM_NAME;
    }

可是,它有另外的几个配置却被忽略了:

proxy_send_timeout	360;//默认60,单位s
proxy_connect_timeout	360;//默认60,单位s
proxy_read_timeout	360;//默认60,单位s
proxy_next_upstream	error timeout;

以上几个配置,主要用于故障转移,当nginx将请求转发到对应服务器,但后期认为服务相应异常时,便会触发故障转移,其中各个配置解释如下:

Syntax:     proxy_read_timeout time;
Default:    proxy_read_timeout 60s;
Context:    http, server, location
定义从代理服务器读取响应的超时。

Syntax:     proxy_connect_timeout time;
Default:    proxy_connect_timeout 60s;
Context:    http, server, location
定义与代理服务器建立连接的超时。

Syntax:     proxy_send_timeout time;
Default:    proxy_send_timeout 60s;
Context:    http, server, location
设置将请求传输到代理服务器的超时。

Syntax:     proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...;
Default:    proxy_next_upstream error timeout;
Context:    http, server, location
指定在何种情况下一个失败的请求应该被发送到下一台后端服务器

其中:
error 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现错误
timeout 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现超时
invalid_header 后端服务器返回空响应或者非法响应头
http_500 后端服务器返回的响应状态码为500
http_502 后端服务器返回的响应状态码为502
http_503 后端服务器返回的响应状态码为503
http_504 后端服务器返回的响应状态码为504
http_404 后端服务器返回的响应状态码为404
off 停止将请求发送给下一台后端服务器

重新回到幽冥请求上,大部分的情况,nginx配置了全局超时时间,为了方便各个业务,可能会设置的比较大,如5分钟,然而却没有配置代理超时时间,默认使用为60s。

如图,客户端请求到达nginx后,nginx进行了第一次的转发,但是由于转发到的VM处理速度较慢导致超过默认设置的超时时间,因此nginx进行了第二次的转发,该转发耗时45s后正常返回。

这种情况在客户端看来,请求只请求了一次,除了反应慢一点没有任何异常。在nginx的请求日志看来,客户端的请求只有一次。而程序看来,虽然处理时间较长,可是前一次请求确实正常返回结果了,而第二次请求,却像是凭空产生的一般。

其实,在排查的过程中,不难发现,重复请求产生的时间是有规律的,由于nginx代理超时时间为60s,所以重复请求产生的时间,应该是在上一轮请求之后的60s后。根据这样的规律,便不难查出原因了。

总结,遇到上述问题的时候,为保生产,应该第一时间将代理过期时间设置为合理的区间,保证生产正常运行。但是,一个接口的请求时长过程,本身也算是一种潜在的问题,应该及时优化解决。将代理过期时间设长,应该事用在大文件上传或者一些实在无法避免的问题上。

Leave a Comment

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