線上nginx的一次「no live upstreams while connecting to upstream 」分析

先描述一下環境,前段的負載均衡轉發給nginx,nginx再轉發給後端的應用服務器。前端

nginx配置文件以下:nginx

upstream ads {後端

        server ap1:8888 max_fails=1 fail_timeout=60s;服務器

        server ap2:8888 max_fails=1 fail_timeout=60s;網絡

}app

出現的現象是:負載均衡

日誌裏面每隔一兩分鐘就會記錄一條相似 *379803415 no live upstreams while connecting to upstream  的日誌,tcp

此外,還有大量的「upstream prematurely closed connection while reading response header from upstream」的日誌。ide


咱們先看「no live upstreams」的問題。函數

看字面意思是nginx發現沒有存活的後端了,可是很奇怪的事情是,這段時間一直訪問都正常,而且用wireshark看到的也是有進來的,也有返回的。


如今只能從nginx源碼的角度來看了。


由於是upstream有關的報錯,因此在ngx_http_upstream.c中查找「no live upstreams」的關鍵字,能夠找到以下代碼(其實,你會發現,若是在nginx全局代碼中找的話,也只有這個文件裏面有這個關鍵字): 


wKiom1XjE1HQ12ybAAQxHYe97qs267.jpg


在這裏能夠看出,當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監控的截圖也能看出來:


wKiom1XjFjmT6U62AAQz7MPocGQ650.jpg

wKioL1XjGFnjK_H_AAMiVUUNS8o299.jpg


初步的解決方法:


把max_fails從1改爲5,效果很明顯,「no live upstreams」出現的機率變少了不少,但卻沒有徹底消失。

另外,日誌裏面還會有大量的「upstream prematurely closed connection while reading response header from upstream」。

此次從源碼上看,在執行ngx_http_upstream_process_header這個函數的時候,會報這個錯,但具體是網絡緣由仍是其餘緣由不是很明顯,下面就tcpdump抓一下包。


wKioL1XjHBWzhnw9AALGNeKxURA038.jpg

其中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。

第二張圖:

wKioL1XjHe-x42gjAAIkp1PCews141.jpg


如圖,能夠看出,nginx跟ap2三次握手後,也發送了一個請求的數據包,一樣被直接關閉鏈接了。

隨後,nginx就把502返回給了負載均衡。


這裏的抓包又一次從側面支持了上面代碼的分析。


而後把問題反饋給作後端應用的同事了。

相關文章
相關標籤/搜索