Redis作為一個分布式的存儲服務(wù),集群的工作模式幾乎是標配,Redis目前的主從模式也是大多人使用的模型,那Redis的主機和從機是如何保證數(shù)據(jù)同步一致的呢?
RDB 與 AOF 文件
Redis本身,數(shù)據(jù)的持久化有兩種形式,一種是AOF,另一種就是RDB文件的格式,作為主從同步的模型,Redis采用的就是RDB的文件格式。這里簡單介紹一下RDB 和 AOF。
AOF
這種存儲模式非常高效,他所存儲的是Redis本身執(zhí)行的所有寫命令,比如說set key value, 所有的寫相關(guān)的命令,都會被加載到AOF里面。
但是有兩點點要注意一下:
- 寫入文件的過程不是實時的,畢竟寫入文件是有性能消耗的,所以會有一個AOF緩沖區(qū)來存儲最近的操作,然后定時寫入文件。
- 對于一些命令,比如說反復(fù)對list的操作,結(jié)果集會越來越大,極端的例子是我們for循環(huán)一組數(shù)據(jù),逐一添加到list里面,對于AOF來說,就是幾千條命令,所以AOF會有一個重寫的過程,具體的內(nèi)容在這里不介紹了,可以參閱官網(wǎng)和相關(guān)書籍。
RDB
相較于AOF的快照行為,RDB是對于內(nèi)存中的數(shù)據(jù),直接進行一次壓縮和文件寫入的過程,這種形式還原數(shù)據(jù)對于Redis來說,比AOF要快很多,但是對于生成來講,可能性能就不盡如人意了,他是將數(shù)據(jù),按照對應(yīng)的類型(string,list,set,map,zset)按照特定的格式,存入文件里面,添加一些版本號和唯一標示,來識別RDB文件。
生成的命令有兩種,一個是SAVE,一個是BGSAVE, 區(qū)別就是前者是整個Redis不工作來生成數(shù)據(jù),后者是開啟另一個線程來處理,不影響Redis的正常工作。但同樣的是RDB文件生成的時候,性能低下,注定他不能實時存儲,通常情況下都是設(shè)置一些條件觸發(fā)生成操作。這里篇幅有限,以后有機會再慢慢介紹。
兩者對比
首先第一點,AOF存儲的是操作,而RDB存儲的是實際的數(shù)據(jù)庫內(nèi)容,所以注定的是存儲上,AOF肯定更加便捷,RDB更加耗費性能。但相反的是,對于大量數(shù)據(jù),RDB還原的速度就快很多了,而AOF由于包含了大量的命令,需要逐一執(zhí)行,整個過程對于Redis而言,肯定不如RDB來得好。
數(shù)據(jù)丟失的問題上,RDB由于不是實時的,所以注定的在宕機的時候,會有一段時間空隙,數(shù)據(jù)會產(chǎn)生丟失的情況,而AOF采用的是緩沖區(qū)的形式,后臺線程進行AOF重寫,所以緩沖區(qū)也有可能因為宕機而存在丟失的問題,但是對比于RDB,肯定丟失的情況更加少。
主從同步的基本原理
聊了文件存儲之后,回到主題上面,Redis的主從模式采用的是RDB文件同步的方式,因為Redis的服務(wù)端,數(shù)據(jù)量有可能非常的大,所以從性能考慮,沒有采用AOF快照來同步。
過程大概如下:
- 從機上線,主動鏈接主機,發(fā)送
SYNC命令 - 主機接到命令后,執(zhí)行
BGSAVE命令,生成RDB文件 - 主機向從機發(fā)送RDB文件,開始同步數(shù)據(jù)
- 同步之后,主機將最近的更新,采用命令的形式同步到從機上面。
舊版復(fù)制的缺陷
上述的過程對于一個從機,新加入到集群里面的時候,比較合適,但是如果因為斷線重連,則需要重新復(fù)制主機上所有的內(nèi)容,主機也因此要進行一次全量級的RDB,比較耗費性能。
在2.8版本之后,采用了一個新的增量復(fù)制的過程,流程如下:
- 前面的
sync的過程還是一樣,沒有差異 - 當從機斷線重連之后,會發(fā)送
PSYNC,要求增量同步,并包括一個offset - 主機根據(jù)offset的位置,對之后的數(shù)據(jù)進行一次增量同步,到從機上面
這里面的offset是由主機和從機共同維護的,相當于一個樂觀鎖,描述了兩方的版本差異。
那決定能否增量同步的主要因素,包括如下三個:
- 是否具備偏移量,與主機進行對比
- 主服務(wù)器的復(fù)制積壓緩沖區(qū)(Replication backlog)
- 從服務(wù)器的ID
第一條不難理解,重點解釋一下第二和第三條
復(fù)制積壓緩沖區(qū)
Redis每分鐘都會處理很多的數(shù)據(jù),不可能一直把更新的操作存起來,等待從機上線在傳輸,AOF也不是都在內(nèi)存中一直保存,所以Redis有一個緩沖區(qū),采用隊列的模式來存儲這些寫操作。
所有的更新操作以隊列的形式放入里面,內(nèi)存大小默認是1MB,超過1MB之后,前面進入隊列的寫操作就會被移除。
所以當從機上線之后,如果offset與主機版本差距的內(nèi)容還在緩沖區(qū)內(nèi),則可以從緩沖區(qū)進行增量同步。否則依然還是全量同步(RDB),這里就好比你的機器宕機了一天,在上線,你不能要求我把這一天的數(shù)據(jù)都給你吧,你直接全量同步得了。
這里我就想到了一個問題,如果我們反復(fù)對Redis更新特別大的K-V的話,超過1M,會使得這個緩沖區(qū)失效,因為每次的更新都超過這個緩沖區(qū)大小了,所以同步操作對于從機來說,都是全量同步,如果太頻繁的話,則會產(chǎn)生很大的問題。
當然這個緩沖區(qū)的大小也是可以設(shè)置調(diào)整的餓,可以根據(jù)需求配置。
從服務(wù)器ID
這個不難理解,因為Redis有一個Slot的概念,每個機器負責一部分的Key,所以如果你之前不是我的從機,那你內(nèi)存內(nèi)的數(shù)據(jù)肯定都不是我的值,就不能只看offset了,還要看一下你之前是不是我的從機,這里主機會維護從機的唯一ID,來校驗。
整個復(fù)制流程
1. 設(shè)置端口號進行連接
SLAVEOF master_ip master_port 命令,或者通過配置文件配置均可。
2. 建立套接字連接
建立連接之后,從服務(wù)器會負責處理后續(xù)的RDB文件和寫命令,主服務(wù)器將從服務(wù)器作為一個客戶端進行響應(yīng)
3. 發(fā)送PING請求
連接之后,通過PING請求檢查相互的狀態(tài),類似心跳校測的內(nèi)容。
4. 身份驗證
這里需要主服務(wù)器和從服務(wù)器都設(shè)置校驗選項,然后校驗密碼。
- 如果兩方都沒有設(shè)置,則這一步可以忽略
- 如果從服務(wù)器設(shè)置了,主服務(wù)沒有設(shè)置,則會返回
no password is set - 如果都設(shè)置了,則會進行驗證
- 如果主服務(wù)器設(shè)置了,但是從服務(wù)器沒有設(shè)置,則會返回
NOAUTH
5. 發(fā)送端口
由于從服務(wù)類似于客戶端,等待接受信息,所以需要告知主服務(wù)器自己接受所用的端口號,方便未來接受RDB和寫命令。
6. 同步
這個時候就進入到我們上面描述的同步機制里面了,SYNC or PSYNC 等等的內(nèi)容。
7. 命令傳播
后續(xù)的寫命令,都會通過上面配置的端口號不停傳輸。
其他內(nèi)容
心跳檢測
進行了套接字連接,執(zhí)行過PING命令之后,雖然保證了連接,但是后續(xù)的寫同步操作,依然需要實時連接。所以從服務(wù)器會定期發(fā)送自己的offset到主服務(wù)器上,檢測是否需要同步寫操作。
檢測從服務(wù)狀態(tài)
主服務(wù)器在一個時間周期內(nèi)沒有收到從服務(wù)器上述的心跳檢測內(nèi)容,則察覺從服務(wù)器可能異常了,所以這個時候會需要查看從服務(wù)器的信息。
Redis會配置一些拒絕寫操作的內(nèi)容,在一些特定情況下會認為寫操作不安全,這里的不安全是通過配置決定的:
- min-slaves-to-write 3
- min-slaves-max-lag 10
以上的信息表示從節(jié)點不能少于3個,或者3個從服務(wù)延遲不能超過10秒。否則拒絕寫操作,只執(zhí)行讀操作。
命令丟失
由于定期的心跳檢測的存在,所以定期檢測版本,發(fā)現(xiàn)消息丟失這種內(nèi)容就自動實現(xiàn)了,可以通過offset準確的了解到從服務(wù)器哪些信息沒有接受到,及時的作出響應(yīng)。
感謝
以上內(nèi)容參考Redis官方網(wǎng)站,以及《Redis的設(shè)計與實現(xiàn)》一書,如有紕漏,歡迎指出。