標籤:style http ar io os 使用 sp strong on
本文會討論一下Redis的複製功能以及Redis複製機制本身的優缺點以及叢集搭建問題。
Redis複製流程概述
Redis的複製功能是完全建立在之前我們討論過的基於記憶體快照的持久化策略基礎上的,也就是說無論你的持久化策略選擇的是什麼,只要用到了 Redis的複製功能,就一定會有記憶體快照發生,那麼首先要注意你的系統記憶體容量規劃,原因可以參考我上一篇文章中提到的Redis磁碟IO問題。
Redis複製流程在Slave和Master端各自是一套狀態機器流轉,涉及的狀態資訊是:
Slave 端:
REDIS_REPL_NONEREDIS_REPL_CONNECTREDIS_REPL_CONNECTED
Master端:
REDIS_REPL_WAIT_BGSAVE_STARTREDIS_REPL_WAIT_BGSAVE_ENDREDIS_REPL_SEND_BULKREDIS_REPL_ONLINE
整個狀態機器流程過程如下:
Slave端在設定檔中添加了slave of指令,於是Slave啟動時讀取設定檔,初始狀態為REDIS_REPL_CONNECT。
Slave端在定時任務serverCron(Redis內部的定時器觸發事件)中串連Master,發送sync命令,然後阻塞等待master發送回其記憶體快照檔案(最新版的Redis已經不需要讓Slave阻塞)。
Master端收到sync命令簡單判斷是否有進行中的記憶體快照子進程,沒有則立即開始記憶體快照,有則等待其結束,當快照完成後會將該檔案發送給Slave端。
Slave端接收Master發來的記憶體快照檔案,儲存到本地,待接收完成後,清空記憶體表,重新讀取Master發來的記憶體快照檔案,重建整個記憶體表資料結構,並最終狀態置位為 REDIS_REPL_CONNECTED狀態,Slave狀態機器流轉完成。
Master端在發送快照檔案過程中,接收的任何會改變資料集的命令都會暫時先儲存在Slave網路連接的發送緩衝隊列裡(list資料結構),待快照完成後,依次發給Slave,之後收到的命令相同處理,並將狀態置位為 REDIS_REPL_ONLINE。
整個複製過程完成,流程如所示:
Redis複製機制的缺陷
從上面的流程可以看出,Slave從庫在串連Master主庫時,Master會進行記憶體快照,然後把整個快照檔案發給Slave,也就是沒有象MySQL那樣有複製位置的概念,即無差異複寫,這會給整個叢集搭建帶來非常多的問題。
比如一台線上正在啟動並執行Master主庫配置了一台從庫進行簡單讀寫分離,這時Slave由於網路或者其它原因與Master斷開了串連,那麼當 Slave進行重新串連時,需要重新擷取整個Master的記憶體快照,Slave所有資料跟著全部清除,然後重建立立整個記憶體表,一方面Slave恢複的 時間會非常慢,另一方面也會給主庫帶來壓力。
所以基於上述原因,如果你的Redis叢集需要主從複製,那麼最好事先配置好所有的從庫,避免中途再去增加從庫。
Cache還是Storage
在我們分析過了Redis的複製與持久化功能後,我們不難得出一個結論,實際上Redis目前發布的版本還都是一個單機版的思路,主要的問題集中在,持久化方式不夠成熟,複製機制存在比較大的缺陷,這時我們又開始重新思考Redis的定位:Cache還是Storage?
如果作為Cache的話,似乎除了有些非常特殊的業務情境,必須要使用Redis的某種資料結構之外,我們使用Memcached可能更合適,畢竟Memcached無論用戶端包和伺服器本身更久經考驗。
如果是作為儲存Storage的話,我們面臨的最大的問題是無論是持久化還是複製都沒有辦法解決Redis單點問題,即一台Redis掛掉了,沒有太好的辦法能夠快速的恢複,通常幾十G的持久化資料,Redis重啟載入需要幾個小時的時間,而複製又有缺陷,如何解決呢?
Redis可擴充叢集搭建 1. 主動複製避開Redis複製缺陷。
既然Redis的複製功能有缺陷,那麼我們不妨放棄Redis本身提供的複製功能,我們可以採用主動複製的方式來搭建我們的叢集環境。
所謂主動複製是指由業務端或者通過代理中介軟體對Redis儲存的資料進行雙寫或多寫,通過資料的多份儲存來達到與複製相同的目的,主動複製不僅限於 用在Redis叢集上,目前很多公司採用主動複製的技術來解決MySQL主從之間複製的延遲問題,比如Twitter還專門開發了用於複製和分區的中介軟體 gizzard(https://github.com/twitter/gizzard) 。
主動複製雖然解決了被動複製的延遲問題,但也帶來了新的問題,就是資料的一致性問題,資料寫2次或多次,如何保證多份資料的一致性呢?如果你的應用 對資料一致性要求不高,允許最終一致性的話,那麼通常簡單的解決方案是可以通過時間戳記或者vector clock等方式,讓用戶端同時取到多份資料並進行校正,如果你的應用對資料一致性要求非常高,那麼就需要引入一些複雜的一致性演算法比如Paxos來保證 資料的一致性,但是寫入效能也會相應下降很多。
通過主動複製,資料多份儲存我們也就不再擔心Redis單點故障的問題了,如果一組Redis叢集掛掉,我們可以讓業務快速切換到另一組Redis上,降低業務風險。
2. 通過presharding進行Redis線上擴容。
通過主動複製我們解決了Redis單點故障問題,那麼還有一個重要的問題需要解決:容量規劃與線上擴容問題。
我們前面分析過Redis的適用情境是全部資料存放區在記憶體中,而記憶體容量有限,那麼首先需要根據業務資料量進行初步的容量規劃,比如你的業務資料需 要100G儲存空間,假設伺服器記憶體是48G,那麼根據上一篇我們討論的Redis磁碟IO的問題,我們大約需要3~4台伺服器來儲存。這個實際是對現有 業務情況所做的一個容量規劃,假如業務增長很快,很快就會發現當前的容量已經不夠了,Redis裡面儲存的資料很快就會超過實體記憶體大小,那麼如何進行 Redis的線上擴容呢?
Redis的作者提出了一種叫做presharding的方案來解決動態擴容和資料分區的問題,實際就是在同一台機器上部署多個Redis執行個體的方式,當容量不夠時將多個執行個體拆分到不同的機器上,這樣實際就達到了擴容的效果。
拆分過程如下:
在新機器上啟動好對應連接埠的Redis執行個體。
配置新連接埠為待遷移連接埠的從庫。
待覆制完成,與主庫完成同步後,切換所有用戶端配置到新的從庫的連接埠。
配置從庫為新的主庫。
移除老的連接埠執行個體。
重複上述過程遷移好所有的連接埠到指定伺服器上。
以上拆分流程是Redis作者提出的一個平滑遷移的過程,不過該拆分方法還是很依賴Redis本身的複製功能的,如果主庫快照資料檔案過大,這個複製的過程也會很久,同時會給主庫帶來壓力。所以做這個拆分的過程最好選擇為業務訪問低峰時段進行。
Redis複製的改進思路
我們線上的系統使用了我們自己改進版的Redis,主要解決了Redis沒有差異複寫的缺陷,能夠完成類似Mysql Binlog那樣可以通過從程式庫要求日誌位置進行差異複寫。
我們的持久化方案是首先寫Redis的AOF檔案,並對這個AOF檔案按檔案大小進行自動分割滾動,同時關閉Redis的Rewrite命令,然後 會在業務低峰時間進行記憶體快照儲存,並把當前的AOF檔案位置一起寫入到快照檔案中,這樣我們可以使快照檔案與AOF檔案的位置保持一致性,這樣我們得到 了系統某一時刻的記憶體快照,並且同時也能知道這一時刻對應的AOF檔案的位置,那麼當從庫發送同步命令時,我們首先會把快照檔案發送給從庫,然後從庫會取 出該快照檔案中儲存的AOF檔案位置,並將該位置發給主庫,主庫會隨後發送該位置之後的所有命令,以後的複製就都是這個位置之後的增量資訊了。
Redis與MySQL的結合
目前大部分互連網公司使用MySQL作為資料的主要持久化儲存,那麼如何讓Redis與MySQL很好的結合在一起呢?我們主要使用了一種基於MySQL作為主庫,Redis作為高速資料查詢從庫的異構讀寫分離的方案。
為此我們專門開發了自己的MySQL複製工具,可以方便的即時同步MySQL中的資料到Redis上。
(MySQL-Redis 異構讀寫分離)
總結:
Redis的複製功能沒有差異複寫,每次重連都會把主庫整個記憶體快照發給從庫,所以需要避免向線上服務的壓力較大的主庫上增加從庫。
Redis的複製由於會使用快照持久化方式,所以如果你的Redis持久化方式選擇的是日誌追加方式(aof),那麼系統有可能在同一時 刻既做aof記錄檔的同步刷寫磁碟,又做快照寫磁碟操作,這個時候Redis的響應能力會受到影響。所以如果選用aof持久化,則加從庫需要更加謹慎。
可以使用主動複製和presharding方法進行Redis叢集搭建與線上擴容。
Redis複製與可擴充叢集搭建