您好,登录后才能下订单哦!
线上nginx的no live upstreams while connecting to upstream 示例分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
先描述一下环境,前段的负载均衡转发给nginx,nginx再转发给后端的应用服务器。
nginx配置文件如下:
upstream ads {
server ap1:8888 max_fails=1 fail_timeout=60s;
server ap2:8888 max_fails=1 fail_timeout=60s;
}
出现的现象是:
日志里面每隔一两分钟就会记录一条类似 *379803415 no live upstreams while connecting to upstream 的日志,
此外,还有大量的“upstream prematurely closed connection while reading response header from upstream”的日志。
我们先看“no live upstreams”的问题。
看字面意思是nginx发现没有存活的后端了,但是很奇怪的事情是,这段时间一直访问都正常,并且用wireshark看到的也是有进来的,也有返回的。
现在只能从nginx源码的角度来看了。
因为是upstream有关的报错,所以在ngx_http_upstream.c中查找“no live upstreams”的关键字,可以找到如下代码(其实,你会发现,如果在nginx全局代码中找的话,也只有这个文件里面有这个关键字):
在这里可以看出,当rc等于NGX_BUSY的时候,就会记录“no live upstreams”的错误。
往上看1328行,可以发现rc的值又是ngx_event_connect_peer这个函数返回的。
ngx_event_connect_peer是在event/ngx_event_connect.c中实现的。这个函数中,只有这个地方会返回NGX_BUSY,其他地方都是NGX_OK或者NGX_ERROR或者NGX_AGAIN之类的。
rc = pc->get(pc, pc->data);
if (rc != NGX_OK) {
return rc;
}
这里的pc是指向ngx_peer_connection_t结构体的指针, get是个ngx_event_get_peer_pt的函数指针,具体指向哪里,一时无从得知。接着翻看ngx_http_upstream.c
在ngx_http_upstream_init_main_conf中看到了,如下代码:
uscfp = umcf->upstreams.elts;
for (i = 0; i < umcf->upstreams.nelts; i++) {
init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:
ngx_http_upstream_init_round_robin;
if (init(cf, uscfp[i]) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
这里可以看到,默认的配置为轮询(事实上负载均衡的各个模块组成了一个链表,每次从链表到头开始往后处理,从上面到配置文件可以看出,nginx不会在轮询前调用其他的模块),并且用ngx_http_upstream_init_round_robin初始化每个upstream。
再看ngx_http_upstream_init_round_robin函数,里面有如下行:
r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;
这里把get指针指向了ngx_http_upstream_get_round_robin_peer
在ngx_http_upstream_get_round_robin_peer中,可以看到:
if (peers->single) {
peer = &peers->peer[0];
if (peer->down) {
goto failed;
}
} else {
/* there are several peers */
peer = ngx_http_upstream_get_peer(rrp);
if (peer == NULL) {
goto failed;
}
再看看failed的部分:
failed:
if (peers->next) {
/* ngx_unlock_mutex(peers->mutex); */
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers");
rrp->peers = peers->next;
n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
/ (8 * sizeof(uintptr_t));
for (i = 0; i < n; i++) {
rrp->tried[i] = 0;
}
rc = ngx_http_upstream_get_round_robin_peer(pc, rrp);
if (rc != NGX_BUSY) {
return rc;
}
/* ngx_lock_mutex(peers->mutex); */
}
/* all peers failed, mark them as live for quick recovery */
for (i = 0; i < peers->number; i++) {
peers->peer[i].fails = 0;
}
/* ngx_unlock_mutex(peers->mutex); */
pc->name = peers->name;
return NGX_BUSY;
这里就真相大白了,如果连接失败了,就去尝试连下一个,如果所有的都失败了,就会进行quick recovery 把每个peer的失败次数都重置为0,然后再返回一个NGX_BUSY,然后nginx就会打印一条no live upstreams ,最后又回到原始状态,接着进行转发了。
这就解释了no live upstreams之后还能正常访问。
重新看配置文件,如果其中一台有一次失败,nginx就会认为它已经死掉,然后就会把以后的流量全都打到另一台上面,当另外一台也有一次失败的时候,就认为两个都死掉了,然后quick recovery,然后打印一条日志。
这样带来的另一个问题是,如果几台同时认定一台后端已经死掉的时候,会造成流量的不均衡,看zabbix监控的截图也能看出来:
初步的解决方法:
把max_fails从1改成5,效果很明显,“no live upstreams”出现的概率变少了很多,但却没有完全消失。
另外,日志里面还会有大量的“upstream prematurely closed connection while reading response header from upstream”。
这次从源码上看,在执行ngx_http_upstream_process_header这个函数的时候,会报这个错,但具体是网络原因还是其他原因不是很明显,下面就tcpdump抓一下包。
其中54是nginx前端的负载均衡的地址,171是nginx地址,32是ap1的地址,另外ap2的地址是201
如截图所示:
请求由负载均衡发到nginx上,nginx先是回应ack给负载均衡,然后跟ap1进行三次握手,随后发送了一个长度为614的数据包给ap1.然而却收到了一个ack和fin+ack,从Ack=615可以看出,这两个包都是针对长度为614的数据包的回应,后端app直接就把连接给关闭掉了!
再然后,nginx回应给后端的app一个ack和fin+ack,从Ack=2可以看出这是对fin+ack的回应。
再然后,nginx就向ap2发出了一个syn包,并且也收到了第一台返回的ack。
第二张图:
如图,可以看出,nginx跟ap2三次握手后,也发送了一个请求的数据包,同样被直接关闭连接了。
随后,nginx就把502返回给了负载均衡。
这里的抓包又一次从侧面支持了上面代码的分析。
然后把问题反馈给做后端应用的同事了。
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。