redis 主從復制常見的一些坑 轉


出處: redis主從復制常見的一些坑

 

讀寫分離的問題

1.數據復制的延遲

  讀寫分離時,master會異步的將數據復制到slave,如果這是slave發生阻塞,則會延遲master數據的寫命令,造成數據不一致的情況

  解決方法:可以對slave的偏移量值進行監控,如果發現某台slave的偏移量有問題,則將數據讀取操作切換到master,但本身這個監控開銷比較高,所以關於這個問題,大部分的情況是可以直接使用而不去考慮的。

 

 

2.讀到過期的數據 

產生原因:

  redis的從庫是無法主動的刪除已經過期的key的,所以如果做了讀寫分離,就很有可能在從庫讀到臟數據

例子重現:

主Redis

setex test 20 1
+OK
get test
$1
1
ttl test
:18

從Redis

get test
$1
1
ttl test
:7

以上都沒問題,然而過幾秒再看從Redis

ttl test
:-1
get test
$1
1

test這個key已經過期了,然而還是可以獲取到test的值。

在使用Redis做鎖的時候,如果直接取讀從庫的值,這就有大問題了。

 

為什么從庫不刪除數據?

redis刪除過期數據有以下幾個策略:

  1.惰性刪除:當讀/寫一個已經過期的key時,會觸發惰性刪除策略,直接刪除掉這個過期key,很明顯,這是被動的!

  2.定期刪除:由於惰性刪除策略無法保證冷數據被及時刪掉,所以 redis 會定期主動淘汰一批已過期的key。(在第二節中會具體說明)

  3.主動刪除:當前已用內存超過maxMemory限定時,觸發主動清理策略。主動設置的前提是設置了maxMemory的值

int expireIfNeeded(redisDb *db, robj *key) { 
 time_t when = getExpire(db,key); 
 
 if (when < 0) return 0; /* No expire for this key */ 
 
 /* Don't expire anything while loading. It will be done later. */ 
 if (server.loading) return 0; 
 
 /* If we are running in the context of a slave, return ASAP: 
 * the slave key expiration is controlled by the master that will 
 * send us synthesized DEL operations for expired keys. 
 * 
 * Still we try to return the right information to the caller, 
 * that is, 0 if we think the key should be still valid, 1 if 
 * we think the key is expired at this time. */ 
 if (server.masterhost != NULL) { 
 return time(NULL) > when; 
 } 
 
 /* Return when this key has not expired */ 
 if (time(NULL) <= when) return 0; 
 
 /* Delete the key */ 
 server.stat_expiredkeys++; 
 propagateExpire(db,key); 
 return dbDelete(db,key); 
}
通過以上源碼發現,4行:沒有設置超時時間,則不刪;7行:在”loading”時不刪; 16行:非主庫不刪;21行未到期不刪。25行同步從庫和文件。
所以說,在從庫執行主動刪除操作,或者通過惰性刪除的方式觸發刪除key的操作,最終都不會執行成功。原因就在上面的第16行代碼。

 

解決方法:

  1)通過一個程序循環便利所有的key,例如scan

  2)通過ttl判斷

  3)升級到reidis3.2

 

主從配置不一致

這個問題一般很少見,但如果有,就會發生很多詭異的問題

例如:

1. maxmemory配置不一致:這個會導致數據的丟失

  原因:例如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.單主節點復制風暴:主節點重啟,多從節點全量復制

  解決:更換復制拓撲如下圖:

  1.我們將原來master與slave中間加一個或多個slave,再在slave上加若干個slave,這樣可以分擔所有slave對master復制的壓力。(這種架構還是有問題:讀寫分離的時候,slave1也發生了故障,怎么去處理?)

  2.如果只是實現高可用,而不做讀寫分離,那當master宕機,直接晉升一台slave即可。

 

2.單機器復制風暴:機器宕機后的大量全量復制,如下圖:

當machine-A這個機器宕機重啟,會導致該機器所有master下的所有slave同時產生復制。(災難)

解決:

  1.主節點分散多機器(將master分散到不同機器上部署)

  2.還有我們可以采用高可用手段(slave晉升master)就不會有類似問題了。

 

 


附錄一份文章關於數據庫主從數據不一致問題

數據庫主從不一致問題解決方案

 

問:常見的數據庫集群架構如何?

答: 一主多從,主從同步,讀寫分離。

 

如上圖:

  (1)一個主庫提供寫服務

  (2)多個從庫提供讀服務,可以增加從庫提升讀性能

  (3)主從之間同步數據

畫外音:任何方案不要忘了本心,加從庫的本心,是提升讀性能。

 

問:為什么會出現不一致?

答:主從同步有時延,這個時延期間讀從庫,可能讀到不一致的數據。

 

如上圖:

  (1)服務發起了一個寫請求

  (2)服務又發起了一個讀請求,此時同步未完成,讀到一個不一致的臟數據

  (3)數據庫主從同步最后才完成

畫外音:任何數據冗余,必將引發一致性問題。

 

問:如何避免這種主從延時導致的不一致?

答:常見的方法有這么幾種。

方案一:忽略

任何脫離業務的架構設計都是耍流氓,絕大部分業務,例如:百度搜索,淘寶訂單,QQ消息,58帖子都允許短時間不一致。

畫外音:如果業務能接受,最推崇此法。

 

如果業務能夠接受,別把系統架構搞得太復雜。

 

方案二:強制讀主

 

如上圖:

  (1)使用一個高可用主庫提供數據庫服務

  (2)讀和寫都落到主庫上

  (3)采用緩存來提升系統讀性能

這是很常見的微服務架構,可以避免數據庫主從一致性問題。

 

方案三:選擇性讀主

強制讀主過於粗暴,畢竟只有少量寫請求,很短時間,可能讀取到臟數據。

 

有沒有可能實現,只有這一段時間,可能讀到從庫臟數據的讀請求讀主,平時讀從呢?

 

可以利用一個緩存記錄必須讀主的數據。

 

如上圖,當寫請求發生時:

(1)寫主庫

(2)將哪個庫,哪個表,哪個主鍵三個信息拼裝一個key設置到cache里,這條記錄的超時時間,設置為“主從同步時延”

畫外音:key的格式為“db:table:PK”,假設主從延時為1s,這個key的cache超時時間也為1s。

 

 

 

如上圖,當讀請求發生時:

這是要讀哪個庫,哪個表,哪個主鍵的數據呢,也將這三個信息拼裝一個key,到cache里去查詢,如果,

  (1)cache里有這個key,說明1s內剛發生過寫請求,數據庫主從同步可能還沒有完成,此時就應該去主庫查詢

  (2)cache里沒有這個key,說明最近沒有發生過寫請求,此時就可以去從庫查詢

以此,保證讀到的一定不是不一致的臟數據。

 

總結

數據庫主庫和從庫不一致,常見有這么幾種優化方案:

(1)業務可以接受,系統不優化

(2)強制讀主,高可用主庫,用緩存提高讀性能

(3)在cache里記錄哪些記錄發生過寫請求,來路由讀主還是讀從

 

出處: redis 數據庫主從不一致問題解決方案

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM