Redis系列1:深刻理解高效能Redis的本質
Redis系列2:資料持久化提高可用性
上一篇《Redis系列2:資料持久化提高可用性》中,我們介紹了Redis中的資料持久化技術,包括 RDB快照 和 AOF紀錄檔 。有了這兩個利器,我們再也不用擔心機器宕機,資料丟失了。
但是持久化技術只是解決了Redis服務故障之後,快速資料恢復的問題。並沒有從根本上提升Redis的可用性,我們需要的是保障Redis的高可用,減少甚至避免Redis服務發生宕機的可能。
目前實現Redis高可用的模式主要有三種: 主從模式、哨兵模式、叢集模式。這些我們後面會分成三篇介紹,今天我們先來聊一下主從模式。
Redis 提供的主從模式,是通過複製的方式,將主伺服器上的Redis的資料同步複製一份到從 Redis 伺服器,這種做法很常見,MySQL的主從也是這麼做的。
主節點的Redis我們稱之為master,從節點的Redis我們稱之為slave,主從複製為單向複製,只能由主到從,不能由從到主。可以有多個從節點,比如1主3從甚至n從,從節點的多少根據實際的業務需求來判斷。
為了保證主伺服器Redis的資料和從伺服器Redis的資料的一致性,也為了分擔存取壓力,均衡負載,應用層面一般採取讀寫分離的模式。
讀操作:主、從庫都可以執行,一般是在從庫上讀資料,對實時性和準確性有100%高真要求的部分業務,可以謹慎評估之後讀主庫;
寫操作:只在主庫上寫資料,寫完之後將寫操作指令同步到從庫。
如下圖:
讀寫分離模式的使用跟MySQL做讀寫分離的初衷是一樣的。因為我們已經劃分了主從庫,而且從庫的資料是由主庫單向複製的。如果主從庫都可以執行寫指令,那麼在高頻並行場景下對不同的副本資料做修改,操作會具有無序性,極易導致各副本產生資料不一致,這是分散式模式的弊病。 如果非要保證資料的強一致性,Redis 需要加鎖處理,或者使用佇列順序執行,這樣勢必降低Redis的效能,降低服務的吞吐能力,這就不是高效能Redis所能接受的。
主從複製的開啟,完全是在從節點設定和發起的,不需要我們在主節點做任何事情。
可以通過 replicaof(Redis 5.0 之前使用 slaveof)命令形成主庫和從庫的關係。在從節點開啟主從複製,有 3 種方式:
說明:masterip:主機IP,masterport:主機埠號
在從伺服器的組態檔中加入
replicaof <masterip> <masterport>
redis-server 啟動命令後面加入
--replicaof <masterip> <masterport>
啟動多個 Redis 範例後,直接通過使用者端執行命令:
replicaof <masterip> <masterport>
則該 Redis 範例成為從節點。
假設現在有主範例 (192.168.0.1:6379)、從範例 A(192.168.0.2:6380)和 從範例 B (192.168.0.3:6381),在從範例上分別執行以下命令,就成為了Slave,主範例成為 Master。
# redis 5.0之前
slaveof 192.168.0.1 6379
# redis 5.0之後
replicaof 192.168.0.1 6379
主從庫模式開啟之後,應用層面採用讀寫分離,所有資料的寫操作只會在主庫上進行,而讀操作基本會在從庫上面進行(特殊情況下部分讀業務允許走主庫)。
主從會保持最終一致性:主庫有了資料更新之後,會立即同步給從庫,來保證主從庫的資料的一致的。
那主從庫同步是如何完成的呢?一次性傳輸麼,那樣資料會不會太大?分批傳遞麼,那樣時效性會不會有問題?故障時候資料會不會丟失?重新連線之後中間產生的差額資料怎麼補充才能保證一致性?帶著這些疑問我們繼續來分析下。
綜合上面的問題來看同步,會有三種重要場景:
主從庫第一次複製過程大體可以分為 3 個階段:準備階段(即建立連線準備)、主庫同步資料到從庫階段、傳送同步期間增量指令到從庫的階段。
我們來看這張完整的流轉圖,從整體上有個認識。
這個階段的主要作用是建立主從之間的連線,連線成立之後,才能夠做資料全量同步。主要包含如下步驟:
第二階段
master 執行 bgsave命令生成 RDB 檔案,並將檔案傳送給從庫,從庫收到 RDB 檔案後儲存到磁碟,清空當前Redis庫中的資料,再將 RDB 檔案資料載入到記憶體中。
同時主庫為每一個 slave 開闢一塊 replication buffer 緩衝區記錄,用於記錄主庫生成 RDB 檔案後那段時間(那段時間的產生的寫命令沒有被記錄到RDB檔案中,但是主庫又會源源不斷的接收到新的請求指令,記錄緩衝區是為了保證資料不丟失)產生的所有寫指令。
第三階段
從第二階段我們可以知道,生成 RDB 檔案之後,後續的操作指令並沒有被記錄,為了保證Redis主從庫資料的一致性,主庫會在記憶體中建立 replication buffer ,記錄 RDB 檔案生成後的所有操作指令。
而從庫在接收完RDB主資料,先清空當前從庫資料,然後完成資料初始化。整個初始化工作完成之後,繼續執行從replication buffer 緩衝區傳送過來的資料,避免資料斷層。
★ 主資料同步到從庫的過程中,主庫不會被阻塞,可以正常處理其他任意操作,這也是Redis保證高效能的必備條件。
replication buffer
緩衝區建立在 master 主庫上,存放的資料是下面三個時間內 master 資料的所有寫操作。
三個步驟完成了Redis主從的全量複製。這邊需要注意的是,Redis中的通訊,無論是主庫跟從庫之間,還是與使用者端之間的資料互動。本質上都是通過分配記憶體buffer來進行的,Master 會先把資料寫到 buffer 中,再通過網路傳送出去,從而完成資料互動。
RDB 檔案作為二進位制檔案,無論是網路傳輸還是寫入時的磁碟IO,效率都要比 AOF 高很多。同樣的,從庫進行資料恢復的時候,效率也會高一些。所以我們會選擇RDB檔案做同步而不是AOF模式。
高版本的Redis,在網路斷開之後或者從範例服務故障恢復之後,主從庫會採用增量複製的方式繼續同步,而不是全量同步的模式,這樣會大大降低開銷,提升效率。
增量複製: 就是指網路中斷或者從庫重啟等情況後的複製,只將中斷期間主節點執行的寫命令傳送給從節點,與全量複製相比更加高效。
repl_backlog_buffer
主從庫重新連線之後可以實現增量複製。關鍵就在 repl_backlog_buffer 緩衝區 上面。
因為 master 會將寫指令操作記錄在 repl_backlog_buffer 緩衝區中,並使用 master_repl_offset 記錄master寫入的位置偏移量,slave 則使用 slave_repl_offset 記錄讀的偏移量。master 新增寫操作的時候,偏移量則會增加。從庫持續執行同步的寫指令後,slave_repl_offset 也會不斷增加。一般情況下,這兩個偏移量會保持同步,如下圖左。
但是網路斷開或者從庫故障期間,主範例Redis一般會收到新的寫操作命令,但從範例則暫停執行,所以 master_repl_offset 會大於 slave_repl_offset。如下圖右。
需要注意的是, repl_backlog_buffer 並不是如圖中顯示的貌似無限佇列的模式,而是一個類似環形陣列,如果陣列內容滿了,就會從頭開始覆蓋前面的內容,因為給到的記憶體空間是有限的。
在主從之間重新連線之後,slave 會先傳送 psync 命令給 master,同時將自己的 {runID,slave_repl_offset} 兩個引數傳送給 master。master 只需要把 master_repl_offset 與 slave_repl_offset 之間的命令同步給從庫即可。增量複製的流程類似如下:
在設定repl_backlog_buffer 的時候,需要綜合考慮各種因素,太大了會導致增量執行週期比較長,還不如RDB全量覆蓋。太小了,有可能從庫還沒讀取到就被 Master 的新寫操作覆蓋了,那樣也只能執行全量複製。
所以我們需要給出一個合理 緩衝區Size。一般有如下的計算公式共參考:
repl_backlog_buffer_size = seconds * write_size_per_second
seconds:正常情況下從庫斷開,到重連主庫所需的平均時間,秒為單位。
write_size_per_second:主庫平均每秒產生的寫命令資料量大小。
如主伺服器大約每秒產生 0.5 MB 的寫指令資料,而斷開到重連一般需要30s,那麼緩衝區的大小就是 0.5 * 30s = 15 MB。
但是我們一般會保留一點buffer,比如 預留 0.5 倍,那就是 : 1.5 * 15 MB = 22.5 MB 。
上面的工作都是為了完成完整複製,那在完成全量複製之後,主從開始進入正常有序的同步了,具體應該怎麼做呢?
主從完成全量複製之後,他們之間需要保持連線。當主庫收到操作指令的時候,通過這個連線同步給從庫,這個過程稱之為 基於長連線的命令傳播。
為了保證傳播的有效性和穩定性,從節點採用心跳機制進行偵測,傳送命令:PING 和 REPLCONF ACK。
每隔指定的時間(比如 1 分鐘,可設定),主節點會向從節點傳送 PING 命令,偵測從節點有無超時來判斷從節點的健康情況。
命令執行傳播的階段,從伺服器預設會以每秒一次的頻率,向主伺服器傳送命令,將複製的偏移量傳送過去。
REPLCONF ACK <replication_offset>
replication_offset 的屬性指的是當前從範例伺服器的複製偏移量。
從範例傳送 REPLCONF ACK 命令對於主要範例,主要有以下作用:
從節點可以傳送 psync 命令給主節點請求同步資料,主節點判斷從節點的當前狀態,看看具體同步是採用全量複製還是部分複製。核心的地方就是psync的引數,這個我們前面也已經提到過了:
下面我們來拆解下步驟:
psync <runID> <offset>
, runID 是主節點 runID,offset 複製偏移量。FULLRESYNC <runid> <offset>
,表示要進行全量複製,同時記下主節點的 runID 和offset。從上面的內容,我們得到以下兩點:
slave_repl_offset
,各自複製的進度也不相同。replication buffer 是主從庫在進行全量複製時,主庫上用於和從庫連線的使用者端的 buffer,而 repl_backlog_buffer 是為了支援從庫增量複製,主庫上用於持續儲存寫操作的一塊專用 buffer,所有從庫共用的。
主庫和從庫會各自記錄自己的複製進度,所以,不同的從庫在進行恢復時,需要將自己的複製進度(slave_repl_offset)發給主庫,主庫才可以按照偏移量取資料跟它同步。
如圖所示: