Nginx失敗重試中的HTTP協議冪等問題: non_idempotent

Nginx經過反向代理作負載均衡時,若是被代理的其中一個服務發生錯誤或者超時的時候,一般但願Nginx自動重試其餘的服務,從而實現服務的高可用性。實際上Nginx自己默認會有錯誤重試機制,而且能夠經過proxy_next_upstream來自定義配置。html

若是不瞭解HTTP協議以及Nginx的機制,就可能在使用過程當中遇到各類各樣的坑。例如服務出現了錯誤或超時卻未重試,或者一些例如建立訂單或發送短信這類的HTTP接口,客戶端只發送一次請求,後臺卻因爲Nginx重試致使建立了多個訂單,或者收到多條短信,致使一些業務上的問題。java

proxy_next_upstream

在Nginx配置文件中,proxy_next_upstream用於指定在什麼狀況下Nginx會將請求轉移到其餘服務器上。其默認值是proxy_next_upstream error timeout,即發生網絡錯誤以及超時,纔會重試其餘服務器。默認狀況下服務返回500狀態碼是不會重試的,若是想在響應500狀態碼時也進行重試,能夠配置:nginx

proxy_next_upstream error timeout http_500;

固然還有http_502http_503http_404等能夠指定在出現哪些狀態碼的狀況下須要重試。具體配置項能夠參考官方文檔: http://nginx.org/en/docs/http...面試

用一個最簡單的例子來測試一下該特性,例以下面是Spring Boot寫了一個簡單的HTTP接口,返回500狀態碼:數據庫

@SpringBootApplication
public class NginxRetryApplication {

    public static void main(String[] args) {
        SpringApplication.run(NginxRetryApplication.class, args);
    }
}

@RestController
class TestController {

    @RequestMapping("/")
    public String test() {
        System.out.println("收到一個請求"); // 打印日誌
        throw new RuntimeException(); // 拋出異常, 返回500狀態碼
    }
}

分別使用9030和9031兩個端口號啓動該Spring Boot服務,而後Nginx配置好負載均衡:服務器

upstream nginxretry {
    server 127.0.0.1:9030 max_fails=0;
    server 127.0.0.1:9031 max_fails=0;
}
server {
    listen 9039;
    location / {
        proxy_pass http://nginxretry;
        proxy_next_upstream error timeout http_500;
    }
}

注意:以上配置中max_fails=0是爲了更方便的測試Nginx錯誤重試機制。max_fails默認值是1,用於指定一個server在一段時間內(默認10s)發生錯誤次數達到多少次,Nginx就會自動將該服務器下線。這裏設置爲0是禁用這個特性,防止在測試過程當中服務器被踢下線很差測試。線上環境下通常不會設置max_fails=0網絡

配置完成後重啓Nginx,使用GET方式請求 http://localhost:9039/ ,再分別查看9030和9031兩個端口號對應的服務日誌,能夠發現兩個服務都收到請求,也就是Nginx在訪問其中一個服務收到500錯誤狀態碼後,又嘗試去訪問另外一個服務。app

再次使用POST方式請求 http://localhost:9039/ ,再分別查看9030和9031兩個端口號對應的服務日誌,能夠發現只有一個服務收到請求。也就是當請求類型是POST時,Nginx默認不會失敗重試。若是想讓POST請求也會失敗重試,能夠繼續向下閱讀。負載均衡

non_idempotent

Nginx文檔中能夠看到proxy_next_upstream有一個選項non_idempotent:ide

normally, requests with a non-idempotent method (POST, LOCK, PATCH) are not passed to the next server if a request has been sent to an upstream server (1.9.13); enabling this option explicitly allows retrying such requests;

一般狀況下,若是請求使用非等冪方法(POST、LOCK、PATCH),請求失敗後不會再到其餘服務器進行重試。加上non_idempotent選項後,即便是非冪等請求類型(例如POST請求),發生錯誤後也會重試。

若是想讓POST請求也會失敗重試,須要配置non_idempotent

upstream nginxretry {
    server 127.0.0.1:9030 max_fails=0;
    server 127.0.0.1:9031 max_fails=0;
}
server {
    listen 9039;
    location / {
        proxy_pass http://nginxretry;
        proxy_next_upstream error timeout http_500 non_idempotent;
    }
}

重啓Nginx後再次使用POST請求訪問 http://localhost:9039/ ,再分別查看9030和9031兩個端口號對應的服務日誌,能夠看到兩個服務都收到請求,也就是POST請求也會重試了。不過實際上在生產環境中,不建議加上non_idempotent選項,具體緣由能夠繼續往下閱讀。

什麼是冪等方法

HTTP協議規範中,對冪等方法(Idempotent Method)作了如下定義:

A request method is considered "idempotent" if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request.

若是使用該方法的多個相同請求對服務器的預期效果與單個請求的效果相同,則認爲請求方法是冪等的。常見的HTTP請求方法中,GET是冪等的,而POST是非冪等的。若是在回答面試題"GET和POST區別"時能答出這一點,才能說明對HTTP協議有必定的理解。

在作業務開發是如何理解冪等性,舉個最簡單的例子:GET方法通常用於獲取數據,若是獲取的是數據庫數據,對應的是SELECT語句。一樣的SELECT語句執行一次仍是屢次,都不會影響數據。而POST通常對應INSERT,若是執行屢次後,可能會形成數據重複插入的問題。因此不要使用GET方法作一些INSERT操做,在業務開發時要遵循HTTP協議規範。

生產環境中爲何不建議加上non_idempotent選項?由於不管是發生500錯誤仍是timeout,服務器上的業務可能都已經執行過了,而重試會致使非冪等方法重複執行,從而致使業務問題,例如一個請求會建立了多個訂單,或者收到多條短信的問題。

參考文檔

關注我

圖片描述

相關文章
相關標籤/搜索