1. 程式人生 > >Redis 一種主主複製解決方案及其實現

Redis 一種主主複製解決方案及其實現

問題的提出

redis(特指2.8.14及以下)replication僅支援主從複製。在實際生產環境中,這種單向主從複製,沒有辦法做高可用(當然,如果允許資料丟失的話,可以採用keepalived,採用其notify_master/notify_slave機制,強制實現主從的角色互換,這種方式對主從強行互換的過程中,如果存在未同步的資料,將會徹底丟失,是一種極其危險的方案,用於生產環境是不可取的)。
所謂他山之石,可以攻玉。mysql提供成熟的主主複製,結合keepalived動態IP,可以做到兩個結點同時準備(ready)提供服務,任何一臺掛掉的時候,另一臺立刻無縫接管。當掛掉的那臺啟動之後,未同步的資料還會繼續同步過來,最大限度保證資料不丟失。當然這種方案也並非100%一致,因為當掛掉結點起來之後可能存在操作續列的先後順序問題從而造成資料少量不一致,未同步的資料越多,不一致的可能性越大,但在生產應用中,這種方案已經可以最大限度保證高可用性,而且對一致性影響並非特別嚴重,因此該方案被廣泛採用。
借鑑MYSQL的經驗,要實現redis的高可用,首先要解決主主複製問題。Gleasy最初實現了一種基於代理機制的redis叢集方案,用於解決這個問題,可以參考這篇文章
《Gleasy的NOSQL資料庫叢集Cloudredis》
。這個叢集方案在生產環境中工作一段時間,就暴露出了一個問題,叢集內的結點,無法做到資料完全一致,不一致的地方存在於那些已經過期的key(設定了expire,並且到期)。redis的key失效機制分為主動失效和被動失效,而主動失效每次僅隨機失效很小一部分,當過期的KEY數量龐大時,在相當長的時間內,這些過期的KEY會一直存在,而叢集內所有結點都會隨機失效一部分,從而導致這些結點失效的KEY不一樣,最終反映出來的結果就是資料不一致,而這種不一致,會對運維工作造成極大的困惑,因為不知道到底是由於同步機制異常還是由於KEY失效引起,從而令到運維人員時時如履薄冰,坐立不安。最終決定,另外提供一種機制實現真正的redis主主複製。

實現原理

1. 概述
在每個redis結點上安裝一個模組(我們稱之為叢集模組),它可以獲取該redis結點所有的寫操作命令序列;
叢集模組獲取redis結點的所有寫操作序列,並將之寫入binlog檔案;
從結點的叢集模組定時向主結點的叢集模組請求binlog塊,並記錄上次請求的位置,下次請求的時候,接著上次的位置獲取;
從結點的叢集模組拿到binglog塊,分析出寫操作序列命令,在從結點redis中執行。
下面是單向複製的結構圖(雙向複製其實就是兩對單向複製a->b,b->a):

2. 具體實現
叢集模組以一個獨立的應用存在,取名為rediscluster(之所以做成獨立的程序,一是為了效能更好,避免加入redis的單執行緒事件機制中,二是避免過多修改redis原始碼從而導致升級不便);
rediscluster監聽獨立的埠,rediscluster之間通過訪問互相之間的獨立埠進行通訊;
每個redis結點對應一個rediscluster,rediscluster啟動後,作為redis的slave,實時接收所有寫操作命令序列;
rediscluster啟動一個獨立的執行緒,定期訪問主結點rediscluster的埠,獲取binlog資料;


一些技術關鍵點

1 一致性保證及技術實現
通過以下約束來保證一致性:
a. rediscluster未準備就緒的情況下(即未能正確接收操作命令並寫入binlog),redis不能接受寫操作.即binlog寫不成功,就不提供寫操作服務。
實現 :設定redis的min-slaves-to-write引數為1,將保證了至少有一個slave工作良好情況下才允許寫入
b.從結點的redis不開啟過期key的主動失效功能,只有主結點才開啟過期key的主動失效功能
實現:rediscluster接收到binlog,則關閉redis的主動失效功能;rediscluster接收到來自redis的寫命令,則開啟redis的主動失效功能;
c. 主結點和從結點不允許同時寫(一方寫,另一方自動變成只讀)
實現: rediscluster接收到binlog,則遮蔽redis的寫操作;
d. slave收到的主結點的寫命令,不再發送給rediscluter。
實現:rediscluster傳送binlog off命令給redis,之後接收到binlog,寫入redis,redis將不會發送給slave。
e. 所有命令加上伺服器唯一標識,避免死迴圈。
實現:rediscluster寫入binlog檔案時,加上本伺服器的唯一標識。當形成主從環時,根據該唯一標識,忽略自己生成的binlog,只消費別人生成的binlog.
2 對redis的改造

a. 開啟/關閉主動失效功能
引入命令 backup on/off來關閉/開啟該功能

b. 遮蔽寫操作
引入命令 lock on/off來遮蔽/解除遮蔽寫操作

c. 開啟/關閉該連線的binlog功能
引入命令binlog on/off來開啟/關閉當前連線的binlog功能(關閉後,由該連線發出的所有寫操作,將不會發給slave)

c. 特殊連線無視lock功能
引入命令cluster來開啟當前連線的無視lock功能(開啟後,由該連線發出的所有寫操作,將無視lock,即不管lock=on還是off,都可寫)

問題的解決

此方案真正實現了redis的主-主複製,配合keepalived,可以很好地實現高可用。
此方案由於採用binlog的方式進行資料同步,斷線後,或者重啟後,都可以從上次的位置繼續同步,完全規避了redis主從首次全量同步的方式,從而也規避了海量資料時,redis主從同步導致IO,CPU,頻寬狂升的問題。
經筆者實測,此方案開啟主主複製情況下,對redis效能無明顯影響(10次對比效能差異幾乎可以忽略不計【差異小於3%,考慮到測試的精確度,幾乎可以忽略】);
rediscluster為單獨應用,自主升級;原始碼暫不開放。
redis原始碼修改不超過20行,日後redis升級無障礙;原始碼在此redis-2.8.14; 也可以通過github下載,地址為redis-2.8.14 gleasy master-master

執行截圖

binlogs示例


rediscluster配置檔案


rediscluster執行時資訊

本文為Gleasy原創文章,轉載請指明引自Gleasy團隊部落格