Redis常見問題和解決辦法梳理

散盡浮華發表於2016-06-08

 

=============Redis主從複製問題和解決辦法 =================

一、Redis主從複製讀寫分離問題

1)資料複製的延遲
讀寫分離時,master會非同步的將資料複製到slave,如果這是slave發生阻塞,則會延遲master資料的寫命令,造成資料不一致的情況。
解決方法:可以對slave的偏移量值進行監控,如果發現某臺slave的偏移量有問題,則將資料讀取操作切換到master,但本身這個監控開銷比較高,所以關於這個問題,大部分的情況是可以直接使用而不去考慮的。
2)讀到過期的資料
redis在刪除過期key的時候有兩種策略,第一種是懶惰型策略,即只有當redis操作這個key的時候,發現這個key過期,就會把這個key刪除。第二種是定期取樣一些key進行刪除。
針對上面說的兩種過期策略,會有個問題,即如果過期key的數量非常多,而取樣速度根本比不上過期key的生成速度時會造成很多過期資料沒有刪除,但在redis裡master和slave達成一種協議,slave是不能處理資料的(即不能刪除資料)而客戶端沒有及時讀到到過期資料同步給master將key刪除,就會導致slave讀到過期的資料(這個問題已經在redis3.2版本中解決)。
 
二、Redis主從配置不一致
這個問題一般很少見,但如果有,就會發生很多詭異的問題,例如:
1)max memory配置不一致:這個會導致資料的丟失。
原因:例如master配置4G,slave配置2G,這個時候主從複製可以成功,但如果在進行某一次全量複製的時候,slave拿到master的RDB載入資料時發現自身的2G記憶體不夠用,這時就會觸發slave的maxmemory策略,將資料進行淘汰。更可怕的是,在高可用的叢集環境下,如果將這臺slave升級成master的時候,就會發現資料已經丟失了。
2)資料結構優化引數不一致(例如hash-max-ziplist-entries):這個就會導致記憶體不一致。
原因:例如在master上對這個引數進行了優化,而在slave沒有配置,就會造成主從節點記憶體不一致的詭異問題。
 
三、規避全量複製
首先,redis複製有全量複製和部分複製兩種,而全量複製的開銷是很大的。那麼來看看,如何儘量去規避全量複製。
1)第一次全量複製
當某一臺slave第一次去掛到master上時,是不可避免要進行一次全量複製的,那麼如何去想辦法降低開銷呢?
方案1:小主節點,例如把redis分成2G一個節點,這樣一來會加速RDB的生成和同步,同時還可以降低fork子程式的開銷(master會fork一個子程式來生成同步需要的RDB檔案,而fork是要拷貝記憶體快的,如果主節點記憶體太大,fork的開銷就大)。
方案2:既然第一次不可以避免,那可以選在叢集低峰的時間(凌晨)進行slave的掛載。
2)節點RunID不匹配
例如主節點重啟(RunID發生變化),對於slave來說,它會儲存之前master節點的RunID,如果它發現了此時master的RunID發生變化,那它會認為這是master過來的資料可能是不安全的,就會採取一次全量複製。
解決辦法:對於這類問題,只有是做一些故障轉移的手段,例如master發生故障宕掉,選舉一臺slave晉升為master(哨兵或叢集)。
3)複製積壓緩衝區不足
在全量複製與部分複製那篇文章提到過,master生成RDB同步到slave,slave載入RDB這段時間裡,master的所有寫命令都會儲存到一個複製緩衝佇列裡(如果主從直接網路抖動,進行部分複製也是走這個邏輯),待slave載入完RDB後,拿offset的值到這個佇列裡判斷,如果在這個佇列中,則把這個佇列從offset到末尾全部同步過來,這個佇列的預設值為1M。而如果發現offset不在這個佇列,就會產生全量複製。
解決辦法:增大複製緩衝區的配置 rel_backlog_size 預設1M,我們可以設定大一些,從而來加大offset的命中率。這個值,可以假設,一般網路故障時間是分鐘級別,那可以根據當前的QPS來算一下每分鐘可以寫入多少位元組,再乘以可能發生故障的分鐘就可以得到我們這個理想的值。
 
四、規避複製風暴
什麼是複製風暴?舉例:master重啟,其master下的所有slave檢測到RunID發生變化,導致所有從節點向主節點做全量複製。儘管redis對這個問題做了優化,即只生成一份RDB檔案,但需要多次傳輸,仍然開銷很大。
1)單主節點複製風暴:主節點重啟,多從節點全量複製
解決辦法:更換複製拓撲,如下圖:
a)將原來master與slave中間加一個或多個slave,再在slave上加若干個slave,這樣可以分擔所有slave對master複製的壓力。(這種架構還是有問題:讀寫分離的時候,slave1也發生了故障,怎麼去處理?)
b)如果只是實現高可用,而不做讀寫分離,那當master當機,直接晉升一臺slave即可。
2)單機器複製風暴:機器當機後的大量全量複製,如下圖:
當machine-A這個機器當機重啟,會導致該機器所有master下的所有slave同時產生複製。(災難)
解決:
a)主節點分散多機器(將master分散到不同機器上部署)
b)還有我們可以採用高可用手段(slave晉升master)就不會有類似問題了。

=============Redis常見效能問題和解決辦法=================

1)Master寫記憶體快照
save命令排程rdbSave函式,會阻塞主執行緒的工作,當快照比較大時對效能影響是非常大的,會間斷性暫停服務,所以Master最好不要寫記憶體快照。
2)Master AOF持久化
如果不重寫AOF檔案,這個持久化方式對效能的影響是最小的,但是AOF檔案會不斷增大,AOF檔案過大會影響Master重啟的恢復速度。
3)Master呼叫BGREWRITEAOF
Master呼叫BGREWRITEAOF重寫AOF檔案,AOF在重寫的時候會佔大量的CPU和記憶體資源,導致服務load過高,出現短暫服務暫停現象。
下面是我的一個實際專案的情況,大概情況是這樣的:一個Master,4個Slave,沒有Sharding機制,僅是讀寫分離,Master負責 寫入操作和AOF日誌備份,AOF檔案大概5G,Slave負責讀操作,當Master呼叫BGREWRITEAOF時,Master和Slave負載會 突然陡增,Master的寫入請求基本上都不響應了,持續了大概5分鐘,Slave的讀請求過也半無法及時響應,Master和Slave的伺服器負載圖 如下:
Master Server load:
Slave server load:

上面的情況本來不會也不應該發生的,是因為以前Master的這個機器是Slave,在上面有一個shell定時任務在每天的上午10點呼叫 BGREWRITEAOF重寫AOF檔案,後來由於Master機器down了,就把備份的這個Slave切成Master了,但是這個定時任務忘記刪除 了,就導致了上面悲劇情況的發生,原因還是找了幾天才找到的。

將no-appendfsync-on-rewrite的配置設為yes可以緩解這個問題,設定為yes表示rewrite期間對新寫操作不fsync,暫時存在記憶體中,等rewrite完成後再寫入。最好是不開啟Master的AOF備份功能。

4)Redis主從複製的效能問題
第一次Slave向Master同步的實現是:Slave向Master發出同步請求,Master先dump出rdb檔案,然後將rdb檔案全量 傳輸給slave,然後Master把快取的命令轉發給Slave,初次同步完成。第二次以及以後的同步實現是:Master將變數的快照直接實時依次發 送給各個Slave。不管什麼原因導致Slave和Master斷開重連都會重複以上過程。Redis的主從複製是建立在記憶體快照的持久化基礎上,只要有 Slave就一定會有記憶體快照發生。雖然Redis宣稱主從複製無阻塞,但由於Redis使用單執行緒服務,如果Master快照檔案比較大,那麼第一次全 量傳輸會耗費比較長時間,且檔案傳輸過程中Master可能無法提供服務,也就是說服務會中斷,對於關鍵服務,這個後果也是很可怕的。
 
以上1.2.3.4根本問題的原因都離不開系統IO瓶頸問題,也就是硬碟讀寫速度不夠快,主程式 fsync()/write() 操作被阻塞。
 
5)單點故障問題
由於目前Redis的主從複製還不夠成熟,所以存在明顯的單點故障問題,這個目前只能自己做方案解決,如:主動複製,Proxy實現Slave對 Master的替換等,這個也是目前比較優先的任務之一。
 
簡單總結:
-  Master最好不要做任何持久化工作,包括記憶體快照和AOF日誌檔案,特別是不要啟用記憶體快照做持久化。
-  如果資料比較關鍵,某個Slave開啟AOF備份資料,策略為每秒同步一次。
-  為了主從複製的速度和連線的穩定性,Slave和Master最好在同一個區域網內。
-  儘量避免在壓力較大的主庫上增加從庫
-  為了Master的穩定性,主從複製不要用圖狀結構,用單向連結串列結構更穩定,即主從關係 為:Master<–Slave1<–Slave2<–Slave3…….,這樣的結構也方便解決單點故障問題,實現Slave對 Master的替換,也即,如果Master掛了,可以立馬啟用Slave1做Master,其他不變。

相關文章