Nginx upstream 失效轉移機制研究
結論
經過多次模拟線上的環境測試,Nginx 負載均衡技術預設情況下已經對于
connect refused
(狀态碼表現為
502
)和
time out
(狀态碼表現為
504
)做了失效轉移,使用的是
upstream
子產品的
proxy_next_upstream
指令(這個選項預設是啟動的)來實作。
對于
http GET
請求,當這個請求轉發到上遊伺服器發生斷路,或者讀取響應逾時則會将同樣的請求轉發到其他上遊伺服器來處理,如果所有伺服器都逾時或者斷路,則會傳回
502
或者
504
錯誤。
對于
http POST
請求,當這個請求轉發到上遊伺服器發生斷路,則會将請求轉發到其他上遊伺服器來處理,但是如果這個請求發生了讀取逾時,則不會做失效轉移,會傳回
504
錯誤,Nginx 之是以這麼做應該是為了防止同一個請求發送兩次,比如涉及到銀行的充值等操作就會發生很嚴重的 bug。以下是模拟線上的場景測試得出的結論:
- 上遊伺服器有兩台,一台處于 down 狀态,另一台處于正常服務狀态,那麼來自用戶端的
和GET
請求都會通過 Nginx 的失效轉移機制路由到正常狀态的機器,傳回POST
狀态碼,并不會傳回給用戶端200
錯誤;502
- 上遊伺服器有兩台,兩台都 down 了,那麼會不管是
還是GET
請求都會直接傳回給用戶端POST
錯誤;502
- 上遊伺服器有兩台,一台機器的
和http GET
POST
接口都正常 return,另一台相同的接口死循環,模拟逾時。
這種情況下如果用戶端的請求路由到了正常機器,那麼直接傳回
200
。
如果請求路由到了死循環的接口,并且是
GET
請求,那麼會等待 Nginx 設定的逾時時間過後,然後将請求轉發到另一台機器的正常接口。
如果請求路由到了死循環的接口,并且是
請求,那麼等待 nginx 設定的逾時時間過後直接傳回POST
,沒有進行失效轉移,防止請求的重複發送;504
- 上遊伺服器有兩台,兩台機器的
和http GET
接口都死循環,模拟逾時,那麼對于POST
請求會進行請求轉發到另一台嘗試,對于GET
請求直接傳回POST
,不會進行進一步嘗試;504
論證環境及工具
- 一台前端 Nginx 伺服器;
- 兩台上遊伺服器;
- Nginx 配置:
server {
listen ;
server_name ngxfailover.xxx.me;
ssl on;
ssl_certificate xxx/xxx.crt;
ssl_certificate_key xxx/xxx.key;
location / {
proxy_pass http://py_web_upstream;
}
}
upstream py_web_upstream{
server upstream_server1:;
server upstream_server2:;
}
- 第一台上遊伺服器正常代碼
import time
from flask import Flask
app = Flask(__name__)
@app.route('/a/<name>')
def failover_get_method(name):
print name
return '<h1>I am Server2, My name is %s </h1>' % name
@app.route('/b/<name>', methods=["POST"])
def failover_post_method(name):
print name
return '<h1>I am Server2, My name is %s </h1>' % name
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
- 第二台上遊伺服器逾時代碼
import time
from flask import Flask
app = Flask(__name__)
@app.route('/a/<name>')
def failover_get_method(name):
print name
while True:
time.sleep()
return '<h1>I am Server2, My name is %s </h1>' % name
@app.route('/b/<name>', methods=["POST"])
def failover_post_method(name):
print name
while True:
time.sleep()
return '<h1>I am Server2, my name is %s </h1>' % name
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
論證過程
以下為前面 4 種案例的論證過程:
案例 1
上遊伺服器有兩台,一台處于 down 狀态,另一台處于正常服務狀态。
在這種情況下,通過
curl
多次發送
GET
和
POST
請求,發現不管怎麼請求,傳回都是正常狀态,如果 Nginx 發生了失敗嘗試操作,那麼會在 Nginx access 日志中的 upstream 字段看到有兩個伺服器的位址。
發送 GET 和 POST 請求:
curl -XGET https://ngxfailover.xxx.me/a/hello
curl -XPOST https://ngxfailover.xxx.me/b/hello
觀察日志:
可以看出所有請求都成功了,紅框框圈起來的請求表示發生了失效轉移,并且請求成功。
案例 2
上遊伺服器有兩台,兩台伺服器都處于 down 狀态。
在這種情況下不管是
GET
還是
POST
請求都會直接傳回給用戶端
502
錯誤。
發送 GET 和 POST 請求:
curl -XGET https://ngxfailover.xxx.me/a/hello
curl -XPOST https://ngxfailover.xxx.me/b/hello
觀察日志:
可以看出所有請求全部傳回
502
錯誤,紅框框圈起來的請求表示發生了失效轉移,但是還是失敗了。
案例 3
上遊伺服器有兩台,一台機器的
http GET
和
POST
接口都正常 return,另一台相同的接口死循環,模拟逾時。
這種情況下如果用戶端的請求路由到了正常機器,那麼直接傳回
200
。
如果請求路由到了死循環的接口,并且是
GET
請求,那麼會等待 Nginx 設定的逾時時間過後,然後将請求轉發到另一台機器的正常接口。
如果請求路由到了死循環的接口,并且是
POST
請求,那麼等待 Nginx 設定的逾時時間過後直接傳回用戶端
504
錯誤,沒有進行失效轉移,防止請求的重複發送。
發送 GET 請求:
curl -XGET https://ngxfailover.xxx.me/a/hello
觀察日志:
可以看到對于
GET
請求全部成功,紅框框圈起來的表示發生了失效轉移,第一台逾時後會是繼續嘗試第二台,最終成功。
發送 POST 請求:
curl -XPOST https://ngxfailover.xxx.me/b/hello
觀察日志:
可以看到對于
POST
請求,如果 Nginx 等待上遊伺服器處理請求逾時,并不會發生失效轉移,直接傳回給用戶端
504
錯誤。
案例 4
上遊伺服器有兩台,兩台機器的
http GET
和
POST
接口都死循環,模拟逾時。
這種情況下對于
GET
請求會将請求轉發到另一台嘗試,對于
POST
請求直接傳回
504
錯誤,不會進行進一步嘗試。
發送 GET 請求:
curl -XGET https://ngxfailover.xxx.me/a/hello
觀察日志:
可以看出對于
GET
請求,Nginx 在等待逾時會繼續進行嘗試,兩台都嘗試失敗後傳回了
504
錯誤。
發送 POST 請求:
curl -XPOST https://ngxfailover.xxx.me/b/hello
觀察日志:
可以看出對于
POST
請求,Nginx 在等待逾時會不繼續進行嘗試其他上遊伺服器,直接傳回
504
錯誤。
總結
總體來看 Nginx 的失效轉移技術已經非常成熟,Nginx 預設情況下對于
connect refused
(狀态碼表現為
502
)和
time out
(狀态碼表現為
504
)已經做了失效轉移,并且 Nginx 根據請求的類型不同,對失效轉移的政策也不同。對于伺服器背景狀态沒有改變的請求(比如
GET
請求)會進行失效轉移,對于服務背景狀态有改變的請求(比如
POST
請求),有失效轉移機制,這也符合 Rest API 的冪等性标準。如果要強行加其他狀态碼的失效轉移,比如
500
、
503
等,需要考量下業務請求是否能容忍請求的重複發送。