1. 程式人生 > >高併發下redis的瓶頸分析

高併發下redis的瓶頸分析

背景

最近接到一個專案,負責架構設計與實現,原本單位做了很多給外邊的人使用的api,但是對外給別人用的時候,介面的連結是怎樣就給別人怎樣的,沒有加密也沒有做併發控制,介面程式所在的機器在哪兒,給別人的ip就是哪兒,而且沒有一個平臺來管理它們,我們很難知道這些介面的價值(哪個別人用得比較多,哪個比較少),這就是這個專案的由來

僅僅針對”監控“的這一需求,我們引入了redis作為中間層,首先我們完善了使用者使用介面的註冊流程,通過使用者資訊和地址,hash出一個key,這個key是對應著一個地址的,把這個(key - 地址)對存在了redis裡面。其次是nginx,nginx在我們的專案裡面的流程大概是這樣:

1、使用者註冊之後獲取到他的key,通過包含了key的跟原本的url完全不同的url來訪問

2、nginx捕獲到使用者特殊的key,然後程式根據這個key從redis中取出目標地址,再由nginx代替使用者訪問真正的地址,繼而返回。

(這個過程好處是很多的)

(1)、隱藏了真實的地址,程式可以在上游伺服器之外的地方干預使用者的訪問,提高安全性,干預過程可以很複雜

(2)、獲取使用者的資訊,並將其存回redis,上游伺服器通過定時程式將存在redis中的日誌持久化進oracle並刪除,然後進一步分析和視覺化

問題來了

這個專案還處於測試階段,資源是一臺window server 伺服器,和centos6.5伺服器,測試階段10秒內大概有10萬的併發量,剛部署上去的一兩天還是沒有問題的,接下來卻出現了redis連線不上的情況。檢視程序訪問,會出現下面的情況。(window server 下)


出現很多FiN_WAIT_2的TCP連結。

分析

一、redis是使用單執行緒處理連線的,意味著它絕對會出現下面二所說的情況。

二、很明顯這是由於nginx和redis之間有很多沒有釋放的資源造成的,檢視這個TCP的狀態FIN_WAIT_2,解釋一下:

在HTTP應用中,存在一個問題,SERVER由於某種原因關閉連線,如KEEPALIVE的超時,這樣,作為主動關閉的SERVER一方就會進入 FIN_WAIT2狀態,但TCP/IP協議棧有個問題,FIN_WAIT2狀態是沒有超時的(不象TIME_WAIT狀態),所以如果CLIENT不關閉,這個FIN_WAIT_2狀態將保持到系統重新啟動,越來越多的FIN_WAIT_2狀態會致使核心crash。 

好吧,大學沒有好好唸書,下面是http連線的狀態變化

客戶端狀態遷移

CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSEDb.

伺服器狀態遷移

CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED

有缺陷的客戶端與持久連線

有一些客戶端在處理持久連線(akakeepalives)時存在問題。當連線空閒下來伺服器關閉連線時(基於KeepAliveTimeout指令),

客戶端的程式編制使它不傳送FIN和ACK回伺服器。這樣就意味著這個連線 將停留在FIN_WAIT_2狀態直到以下之一發生:

客戶端為同一個或者不同的站點開啟新的連線,這樣會使它在該個套接字上完全關閉以前的連線。

使用者退出客戶端程式,這樣在一些(也許是大多數?)客戶端上會使作業系統完全關閉連線。

FIN_WAIT_2超時,在那些具有FIN_WAIT_2狀態超時設定的伺服器上。

如果你夠幸運,這樣意味著那些有缺陷的客戶端會完全關閉連線並釋放你伺服器的資源。

然而,有一些情況下套接字永遠不會完全關閉,比如一個撥號客戶端在關閉客戶端程式之前從ISP斷開。

此外,有的客戶端有可能空置好幾天不建立新連線,並且這樣在好幾天裡保持著套接字的有效即使已經不再使用。這是瀏覽器或者操作系統的TCP實現的Bug。


  產生原因有: 
1、長連線並且當連線一直處於IDLE狀態導致SERVERCLOSE時,CLIENT程式設計缺陷,沒有向SERVER 發出FIN和ACK包 
2、APACHE1.1和APACHE1.2增加了linger_close()函式,前面的帖子有介紹,這個函式可能引起了這個問題(為什麼我也不清楚)
  解決辦法: 
1。對FIN_WAIT_2狀態增加超時機制,這個特性在協議裡沒有體現,但在一些OS中已經實現 
如:LINUX、SOLARIS、FREEBSD、HP-UNIX、IRIX等 
2。不要用linger_close()編譯 
3。用SO_LINGER代替,這個在某些系統中還能很好地處理 
4。增加用於儲存網路連線狀態的記憶體mbuf,以防止核心crash 
5。DISABLE KEEPALIVE 

針對這種情況,我們做了幾次討論,有些結論,分別是:

1、設定nginx與redis的連線池,keepalive的時間,分別設為10秒,5秒,但是結果還是一樣

2、不用keepalive,即不使用連線池,即每次用完就close()掉,你可以看到連線少了,但是不使用連線池,意味著10秒內要開啟關閉10萬次,開銷太大

3、redis叢集,在原本叢集的體系上新增redis的叢集,這或許能解決問題,但是10秒內10萬實際上並不多,這樣做了或許是取巧,並沒有找到問題

4、設定redis的idle(空閒)時間限制,結果一樣。

解決方案:

實際上不算解決方案,因為放棄了redis的記憶體機制,而是使用nginx本身的記憶體技術。網上關於redis的優化大部分不適用,這個問題有待分析解決。