1. 程式人生 > >Nginx+Tomcat叢集的故障遷移實驗

Nginx+Tomcat叢集的故障遷移實驗

        經過前面的叢集實施,已經將Nginx+Tomcat的叢集環境給配置起來了,接著繼續進行叢集的故障轉移實驗.

  這裡的故障轉移包括節點關閉情況和節點宕機情況的故障轉移.

  首先對於節點關閉或加入的情況,比如某一Tomcat節點關閉或重啟的情況,在這種情況下,nginx可以快速識別到已停用或新加入的節點,基本上可以做到無延時的故障轉移.所以這裡主要實驗的是tomcat宕機的情況,比如tomcat執行過程中出現記憶體溢位或長時間不響應的情況.

  為了實驗的需要,在tomcat7080的啟動引數中增加記憶體的配置,設定其最大可用記憶體為64m:

Java程式碼  收藏程式碼
  1. JAVA_OPTS=
    "-Xms32m -Xmx64m"

  並在tomcat7080中,使用一段程式不斷地往記憶體中寫入資料,以使它出現記憶體溢位錯誤,不再處理新的訪問請求.

Java程式碼  收藏程式碼
<body>  
    <%!  
    static HashMap<String,String> map = new HashMap<String,String>();  
    public String generateStr(){  
        StringBuilder s = new StringBuilder();  
        while(s.length()<50){  
            s.append(new Random().nextInt());  
        }     
        return s.toString();  
    }  
    %>  
    <%  
    int c = 500000;  
    try {  
        c=Integer.parseInt(request.getParameter("c"));  
    } catch(Exception e){  
         c = 500000;  
    }  
    for(int i = 0; i < c ;i++){  
        map.put(String.valueOf(1000000+i),generateStr());  
    }  
    %>  
</body> 

  在tomcat7080啟動之後,訪問這段程式所在的jsp檔案,tomcat很快便出現記憶體溢位錯誤,成功宕機.

  此時通過程式來模擬一個單併發,每秒發出一次請求的客戶端:

Java程式碼  收藏程式碼
public static void main(String[] args) {  
            for (int i = 0; i < 90; i++) {//測試90次  
                try {  
                    doGet();  
                    Thread.sleep(1000);  
                } catch (Exception e) {  
                    // e.printStackTrace();  
                }  
            }  
        }  
      
        public static void doGet() throws Exception {  
            URL url = new URL("http://localhost/");  
            HttpURLConnection conn;  
            BufferedReader reader = null;  
            conn = (HttpURLConnection) url.openConnection();  
            String s;  
            int rc = conn.getResponseCode();  
            if (rc != 200) {  
                System.out.println("WARN: response code:" + rc);  
            }  
            reader = new BufferedReader(new InputStreamReader(  
                    conn.getInputStream(), "UTF-8"), 512);  
            String line;  
            while ((line = reader.readLine()) != null) {  
            }  
            if (reader != null)  
                reader.close();  
        }  

  日誌檔案中的輸出結果為:



 

  從日誌輸出中可以看到,nginx仍然會嘗試去請求已經宕機的7080埠,但在等待60秒之後將請求轉發給了6080,然後在大約13秒左右的時間內都只會請求6080埠,然後再去嘗試請求7080埠,依次迴圈.

  要解釋出現這個現象的原因,需要來看一下叢集中server的引數以及proxy_connect_timeout, proxy_read_timeout等引數的設定

  在nginx中,upstream中的server語法如下:

  (參考http://nginx.org/en/docs/http/ngx_http_upstream_module.html)

Java程式碼  收藏程式碼
server address [weight=number] [max_fails=number] [fail_timeout=time] [slow_start=time] [backup] [down];  


  其中max_fails和fail_timeout的預設值分別為1和10s,這兩個引數配置起來使用.含義是:在fail_timeout的時間內,nignx與upstream中某個server的連線嘗試失敗了max_fails次,則nginx會認為該server已經失效。在接下來的 fail_timeout時間內,nginx不再將請求分發給失效的server。所以在預設的情況下,nginx在前一次嘗試連線7080埠失敗後,在10秒之後才會再次去嘗試(這裡的實際是大約是13秒,考慮請求轉發的原因,基本上可以認為是一個正常值).

  然後是location中的proxy_connect_timeout和proxy_read_timeout設定.這兩個引數的含義如下:

  proxy_connect_timeout 

  後端伺服器連線的超時時間_發起握手等候響應超時時間(預設為60s,不建議超過75s)

  proxy_read_timeout 

  連線成功後_等候後端伺服器響應時間_其實已經進入後端的排隊之中等候處理(也可以說是後端伺服器處理請求的時間)(預設為60s)

  分析我們前面的實驗,tomcat7080在記憶體溢位的情況下,仍然能夠與nginx完成握手,但是卻不能處理結果,所以等待的一分鐘時間是耗費在proxy_read_timeout了.如果能設定一個合適的值,就可以在可接受的時間範圍內,完成叢集的故障遷移.

  在測試過程中,最終的故障遷移時間配置如下:

Java程式碼  收藏程式碼
 upstream cluster {  
        server localhost:6080 weight=10 fail_timeout=1m;  
        server localhost:7080 weight=10 fail_timeout=1m;  
  }  
    
    
location / {  
  proxy_pass http://cluster;  
  proxy_set_header Host $host;  
     proxy_set_header X-Real-IP $remote_addr;  
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  
  proxy_connect_timeout       2s;  
  proxy_read_timeout          5s;  
  proxy_send_timeout          5s;  
}  


 即可承受的請求響應時間為5s,在故障被檢測到之後,1m內不再向故障節點發起新請求.(實際生產環境中可按需要適當進行調整)