對系統的某個接口進行極限壓測,隨着並發量上升,nginx開始出現502 no live upstreams while connecting to upstream的報錯,維持最大並發量一段時間,發現調用接口一直返回502,即nginx已經發現不了存活的后端了。
通過跟蹤端口,發現nginx 跟后端創建了大量的連接。這很明顯是沒有使用http1.1長連接導致的。因此在upstream中添加keepalive配置。
upstream yyy.xxx.web{ server 36.10.xx.107:9001; server 36.10.xx.108:9001; keepalive 256; } server { ··· location /zzz/ { proxy_pass http://yyy.xxx.web; ··· } }
根據官方文檔的說明:該參數開啟與上游服務器之間的連接池,其數值為每個nginx worker可以保持的最大連接數,默認不設置,即nginx作為客戶端時keepalive未生效。
默認情況下 Nginx 訪問后端都是用的短連接(HTTP1.0),一個請求來了,Nginx 新開一個端口和后端建立連接,請求結束連接回收。如果配置了http 1.1長連接,那么Nginx會以長連接保持后端的連接,如果並發請求超過了 keepalive 指定的最大連接數,Nginx 會啟動新的連接來轉發請求,新連接在請求完畢后關閉,而且新建立的連接是長連接。
上圖是nginx upstream keepalive長連接的實現原理。
首先每個進程需要一個connection pool,里面都是長連接,多進程之間是不需要共享這個連接池的。 一旦與后端服務器建立連接,則在當前請求連接結束之后不會立即關閉連接,而是把用完的連接保存在一個keepalive connection pool里面,以后每次需要建立向后連接的時候,只需要從這個連接池里面找,如果找到合適的連接的話,就可以直接來用這個連接,不需要重新創建socket或者發起connect()。這樣既省下建立連接時在握手的時間消耗,又可以避免TCP連接的slow start。如果在keepalive連接池找不到合適的連接,那就按照原來的步驟重新建立連接。 我沒有看過nginx在連接池中查找可用連接的代碼,但是我自己寫過redis,mysqldb的連接池代碼,邏輯應該都是一樣的。誰用誰pop,用完了再push進去,這樣時間才O(1)。
需要注意的是:我在我的nginx1.12.0版本中新增該配置之后,再次壓測,502問題依然存在,升級到1.16.0版本之后,502問題解決。原因是nginx1.12.0版本不支持長連接配置。
另外,如果nginx所在服務器和建立連接后端服務所在服務器不在同一網段時(即兩台機器之間存在防火牆),還需要注意防火牆對長連接的影響。