Nginx+Tomcat叢集的故障遷移實驗
經過前面的叢集實施,已經將Nginx+Tomcat的叢集環境給配置起來了,接著繼續進行叢集的故障轉移實驗.
這裡的故障轉移包括節點關閉情況和節點宕機情況的故障轉移.
首先對於節點關閉或加入的情況,比如某一Tomcat節點關閉或重啟的情況,在這種情況下,nginx可以快速識別到已停用或新加入的節點,基本上可以做到無延時的故障轉移.所以這裡主要實驗的是tomcat宕機的情況,比如tomcat執行過程中出現記憶體溢位或長時間不響應的情況.
為了實驗的需要,在tomcat7080的啟動引數中增加記憶體的配置,設定其最大可用記憶體為64m:
Java程式碼- JAVA_OPTS=
並在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內不再向故障節點發起新請求.(實際生產環境中可按需要適當進行調整)