Nginx+Tomcat叢集故障遷移實現
前幾天在面試阿里的時候面試官問這麼一個問題:
在Nginx+Tomcat的負載均衡場景中,如果某臺伺服器意外宕機的時候,Nginx對於將要分發到這臺伺服器的處理策略是怎麼樣的?
筆者當時這個問題沒有回答後,面試介紹後馬上做了實驗並查詢了相關的Nginx的負載均衡的配置項。
先搭建出Nginx+Tomcat的環境
這個比較簡單,負載均衡演算法指定為輪循法,Tomcat為了啟動方便使用Spring Boot內嵌的Tomcat。
Tomcat一號機,埠server.port=8080
@SpringBootApplication
@RestController
public class App {
@RequestMapping("/set")
public String home(HttpServletRequest request) {
return "One!"+new Date();
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Tomcat二號機,埠server.port=9090
tomcat二號機需要模擬一個記憶體溢位的場景
啟動時指定JAVA_OPTS=”-Xms32M -Xmx64M”如果在intellij中則在editConfiguration中修改。
@SpringBootApplication
@RestController
public class App{
static HashMap<String,String> map = new HashMap<String,String>();
@RequestMapping("/set")
public String home(HttpServletRequest request) {
new Thread(){
@Override
public void run(){
StringBuilder s = new StringBuilder();
while (s.length()<50){
s.append(new Random().nextInt());
}
for (int i=0;i<500000;i++){
map.put(String.valueOf(1000000+i),s.toString());
}
}
}.start();
return "Two! Yes";
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Nginx的配置:
修改/etc/nginx/sites-enabled
upstream webservers{
server 192.168.0.101:8080 weight=1;
server 192.168.0.101:9090 weight=1;
}
location / {
proxy_pass http://webservers;
}
上述的Nginx配置完成了一個最簡單的配置,沒有任何附加引數。
通過Nginx輸出日誌配置
修改nginx.conf如下,對access.log修改輸出格式:
log_format main '$remote_addr - $remote_user [$time_local] '
'fwf[$http_x_forwarded_for] tip[$http_true_client_ip] '
'$upstream_addr $upstream_response_time $request_time '
'$geoip_country_code '
'$http_host $request '
'"$status" $body_bytes_sent "$http_referer" '
'"$http_accept_language" "$http_user_agent" ';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
關於Nginx日誌的配置簡單描述一下:
語法: log_format name string …;
預設值: log_format combined “…”;
配置段: http
name表示格式名稱,string表示等義的格式。log_format有一個預設的無需設定的combined日誌格式,相當於apache的combined日誌格式,如下所示:
log_format combined '$remote_addr - $remote_user [$time_local] '
' "$request" $status $body_bytes_sent'
' "$http_referer" "$http_user_agent" ';
- $
remote_addr
,$http_x_forwarded_for
記錄客戶端IP地址- $
remote_user
記錄客戶端使用者名稱稱- $
request
記錄請求的URL和HTTP協議- $
status
記錄請求狀態- $
body_bytes_sent
傳送給客戶端的位元組數,不包括響應頭的大小; 該變數與Apache模組mod_log_config
裡的“%B”引數相容。- $
bytes_sent
傳送給客戶端的總位元組數。- $
connection
連線的序列號。- $
connection_requests
當前通過一個連接獲得的請求數量。- $
msec
日誌寫入時間。單位為秒,精度是毫秒。- $
pipe
如果請求是通過HTTP流水線(pipelined)傳送,pipe值為“p”,否則為“.”。- $
http_referer
記錄從哪個頁面連結訪問過來的- $
http_user_agent
記錄客戶端瀏覽器相關資訊- $
request_length
請求的長度(包括請求行,請求頭和請求正文)。- $
request_time
請求處理時間,單位為秒,精度毫秒; 從讀入客戶端的第一個位元組開始,直到把最後一個字元傳送給客戶端後進行日誌寫入為止。- $
time_iso8601
ISO8601標準格式下的本地時間。- $
time_local
通用日誌格式下的本地時間。
如果未獲取到,則日誌輸出為-
傳送請求,導致Tomcat二號機記憶體溢位
瀏覽器上請求127.0.0.1/set。當返回為Two!Yes的時候,表示會造成記憶體溢位的執行緒已經開始執行。(如果返回的不是,則繼續請求,直到返回Two!Yes)
此時繼續請求127.0.0.1/set,你會發現最後請求都會返回One!Yes+時間,但你會發現有的請求傳送之後要整整1分鐘左右才響應One!Yes+時間。
這個1分鐘時間便是請求被nginx的負載均衡演算法分配到此時記憶體溢位的二號機上,而該機無法處理,nginx等待了1分鐘後便把請求轉發給了其他機器
從Nginx的日誌中也能看到這一點:
127.0.0.1 - - [08/Jul/2017:15:06:51 +0800] fwf[-] tip[-] 192.168.0.101:9090, 192.168.0.101:8080 60.003, 0.006 60.009 - 127.0.0.1 GET /set HTTP/1.1 "200" 67 "-" "en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36"
查閱nginx負載均衡修相關的引數
在nginx中,upstream中的server語法如下:
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在前一次嘗試連線9090埠失敗後,在10秒之後才會再次去嘗試。
其次在location中還有兩個重要的引數,proxy_connect_timeout和proxy_read_timeout設定.這兩個引數的含義如下:
proxy_connect_timeout
後端伺服器連線的超時時間_發起握手等候響應超時時間(預設為60s)
proxy_read_timeout
連線成功後等候後端伺服器響應時間其實已經進入後端的排隊之中等候處理(也可以說是後端伺服器處理請求的時間)(預設為60s)
而9090埠的tomcat在記憶體溢位的情況下,仍然能夠與nginx完成握手,但是卻不能處理結果,所以等待的一分鐘時間是耗費在proxy_read_timeout了.如果能設定一個合適的值,就可以在可接受的時間範圍內,完成叢集的故障遷移。
修改proxy_read_timeout為10s
location / {
proxy_pass http://webservers;
proxy_read_timeout 10s;
}
繼續重複上述的測試過程,發現次數延遲只有10s