在《Redis 核心篇:惟快不破的祕密》中,「碼哥」揭祕了 Redis 五大數據類型底層的數據結構、IO 模型、線程模型、漸進式 rehash 掌握了 Redis 快的本質緣由。redis
接着,在《Redis 日誌篇:無畏宕機與快速恢復的殺手鐗》中揭曉了當 Redis 發生宕機能夠經過從新讀取 RDB 快照和執行 AOF 日誌實現快速恢復的高可用手段。shell
高可用有兩個含義:一是數據儘可能不丟失,二是服務儘量提供服務。 AOF 和 RDB 保證了數據持久化儘可能不丟失,而主從複製就是增長副本,一份數據保存到多個實例上。即便有一個實例宕機,其餘實例依然能夠提供服務。數據庫
本篇主要帶你們全方位吃透 Redis 高可用技術解決方案之一主從複製架構。數組
本篇硬核,建議收藏慢慢品味,我相信讀者朋友會有一個質的提高。若有錯誤還望糾正,謝謝。關注「碼哥字節」設置「星標」第一時間接收優質文章,謝謝讀者的支持。安全
核心知識點服務器
問題 = 機會。遇到問題的時候,心裏實際上是開心的,越大的問題意味着越大的機會。網絡
任何事情都是有代價的,有得必有失,有失必有得,因此沒必要計較不少東西,咱們只要想清楚本身要作什麼,而且想清楚本身願意爲之付出什麼代價,而後就放手去作吧!數據結構
65 哥:有了 RDB 和 AOF 不再怕宕機丟失數據了,可是 Redis 實例宕機了怎麼實現高可用呢?架構
既然一臺宕機了沒法提供服務,那多臺呢?是否是就能夠解決了。Redis 提供了主從模式,經過主從複製,將數據冗餘一份複製到其餘 Redis 服務器。併發
前者稱爲主節點 (master),後者稱爲從節點 (slave);數據的複製是單向的,只能由主節點到從節點。
默認狀況下,每臺 Redis 服務器都是主節點;且一個主節點能夠有多個從節點 (或沒有從節點),但一個從節點只能有一個主節點。
65 哥:主從之間的數據如何保證一致性呢?
爲了保證副本數據的一致性,主從架構採用了讀寫分離的方式。
65 哥:爲什麼要採用讀寫分離的方式?
咱們能夠假設主從庫均可以執行寫指令,假如對同一份數據分別修改了屢次,每次修改發送到不一樣的主從實例上,就致使是實例的副本數據不一致了。
若是爲了保證數據一致,Redis 須要加鎖,協調多個實例的修改,Redis 天然不會這麼幹!
65 哥:主從複製還有其餘做用麼?
主從複製的開啓,徹底是在從節點發起的,不須要咱們在主節點作任何事情。
65 哥:怎麼搭建主從複製架構呀?
能夠經過 replicaof(Redis 5.0 以前使用 slaveof)命令造成主庫和從庫的關係。
在從節點開啓主從複製,有 3 種方式:
配置文件
在從服務器的配置文件中加入 replicaof <masterip> <masterport>
啓動命令
redis-server 啓動命令後面加入 --replicaof <masterip> <masterport>
客戶端命令
啓動多個 Redis 實例後,直接經過客戶端執行命令:replicaof <masterip> <masterport>
,則該 Redis 實例成爲從節點。
好比假設如今有實例 1(172.16.88.1)、實例 2(172.16.88.2)和實例 3 (172.16.88.3),在實例 2 和實例 3 上分別執行如下命令,實例 2 和 實例 3 就成爲了實例 1 的從庫,實例 1 成爲 Master。
replicaof 172.16.88.1 6379
主從庫模式一旦採用了讀寫分離,全部數據的寫操做只會在主庫上進行,不用協調三個實例。
主庫有了最新的數據後,會同步給從庫,這樣,主從庫的數據就是一致的。
65 哥:主從庫同步是如何完成的呢?主庫數據是一次性傳給從庫,仍是分批同步?正常運行中又怎麼同步呢?要是主從庫間的網絡斷連了,從新鏈接後數據還能保持一致嗎?
65 哥你問題咋這麼多,同步分爲三種狀況:
65 哥:我好暈啊,先從主從庫間第一次同步提及吧。
主從庫第一次複製過程大致能夠分爲 3 個階段:鏈接創建階段(即準備階段)、主庫同步數據到從庫階段、發送同步期間新寫命令到從庫階段;
直接上圖,從總體上有一個全局觀的感知,後面具體介紹。
該階段的主要做用是在主從節點之間創建鏈接,爲數據全量同步作好準備。從庫會和主庫創建鏈接,從庫執行 replicaof 併發送 psync 命令並告訴主庫即將進行同步,主庫確認回覆後,主從庫間就開始同步了。
65 哥:從庫怎麼知道主庫信息並創建鏈接的呢?
在從節點的配置文件中的 replicaof 配置項中配置了主節點的 IP 和 port 後,從節點就知道本身要和那個主節點進行鏈接了。
從節點內部維護了兩個字段,masterhost 和 masterport,用於存儲主節點的 IP 和 port 信息。
從庫執行 replicaof
併發送 psync
命令,表示要執行數據同步,主庫收到命令後根據參數啓動複製。命令包含了主庫的 runID 和 複製進度 offset 兩個參數。
主庫收到 psync 命令後,會用 FULLRESYNC 響應命令帶上兩個參數:主庫 runID 和主庫目前的複製進度 offset,返回給從庫。從庫收到響應後,會記錄下這兩個參數。
FULLRESYNC 響應表示第一次複製採用的全量複製,也就是說,主庫會把當前全部的數據都複製給從庫。
第二階段
master 執行 bgsave
命令生成 RDB 文件,並將文件發送給從庫,同時主庫爲每個 slave 開闢一塊 replication buffer 緩衝區記錄從生成 RDB 文件開始收到的全部寫命令。
從庫收到 RDB 文件後保存到磁盤,並清空當前數據庫的數據,再加載 RDB 文件數據到內存中。
第三階段
從節點加載 RDB 完成後,主節點將 replication buffer 緩衝區的數據發送到從節點,Slave 接收並執行,從節點同步至主節點相同的狀態。
65 哥:主庫將數據同步到從庫過程當中,能夠正常接受請求麼?
主庫不會被阻塞,Redis 做爲惟快不破的男人,怎麼會動不動就阻塞呢。
在生成 RDB 文件以後的寫操做並無記錄到剛剛的 RDB 文件中,爲了保證主從庫數據的一致性,因此主庫會在內存中使用一個叫 replication buffer 記錄 RDB 文件生成後的全部寫操做。
65 哥:爲啥從庫收到 RDB 文件後要清空當前數據庫?
由於從庫在經過 replcaof
命令開始和主庫同步前可能保存了其餘數據,防止主從數據之間的影響。
replication buffer 究竟是什麼玩意?
一個在 master 端上建立的緩衝區,存放的數據是下面三個時間內全部的 master 數據寫操做。
1)master 執行 bgsave 產生 RDB 的期間的寫操做;
2)master 發送 rdb 到 slave 網絡傳輸期間的寫操做;
3)slave load rdb 文件把數據恢復到內存的期間的寫操做。
Redis 和客戶端通訊也好,和從庫通訊也好,Redis 都分配一個內存 buffer 進行數據交互,客戶端就是一個 client,從庫也是一個 client,咱們每一個 client 連上 Redis 後,Redis 都會分配一個專有 client buffer,全部數據交互都是經過這個 buffer 進行的。
Master 先把數據寫到這個 buffer 中,而後再經過網絡發送出去,這樣就完成了數據交互。
無論是主從在增量同步仍是全量同步時,master 會爲其分配一個 buffer ,只不過這個 buffer 專門用來傳播寫命令到從庫,保證主從數據一致,咱們一般把它叫作 replication buffer。
replication buffer 過小會引起的問題:
replication buffer 由 client-output-buffer-limit slave 設置,當這個值過小會致使主從複製鏈接斷開。
1)當 master-slave 複製鏈接斷開,master 會釋放鏈接相關的數據。replication buffer 中的數據也就丟失了,此時主從之間從新開始複製過程。
2)還有個更嚴重的問題,主從複製鏈接斷開,致使主從上出現從新執行 bgsave 和 rdb 重傳操做無限循環。
當主節點數據量較大,或者主從節點之間網絡延遲較大時,可能致使該緩衝區的大小超過了限制,此時主節點會斷開與從節點之間的鏈接;
這種狀況可能引發全量複製 -> replication buffer 溢出致使鏈接中斷 -> 重連 -> 全量複製 -> replication buffer 緩衝區溢出致使鏈接中斷……的循環。
具體詳情:[top redis headaches for devops – replication buffer]
於是推薦把 replication buffer 的 hard/soft limit 設置成 512M。
config set client-output-buffer-limit "slave 536870912 536870912 0"
65 哥:主從庫複製爲什麼不使用 AOF 呢?相比 RDB 來講,丟失的數據更少。
這個問題問的好,緣由以下:
65 哥:主從庫間的網絡斷了咋辦?斷開後要從新全量複製麼?
在 Redis 2.8 以前,若是主從庫在命令傳播時出現了網絡閃斷,那麼,從庫就會和主庫從新進行一次全量複製,開銷很是大。
從 Redis 2.8 開始,網絡斷了以後,主從庫會採用增量複製的方式繼續同步。
增量複製:用於網絡中斷等狀況後的複製,只將中斷期間主節點執行的寫命令發送給從節點,與全量複製相比更加高效。
repl_backlog_buffer
斷開重連增量複製的實現奧祕就是 repl_backlog_buffer
緩衝區,無論在何時 master 都會將寫指令操做記錄在 repl_backlog_buffer
中,由於內存有限, repl_backlog_buffer
是一個定長的環形數組,若是數組內容滿了,就會從頭開始覆蓋前面的內容。
master 使用 master_repl_offset
記錄本身寫到的位置偏移量,slave 則使用 slave_repl_offset
記錄已經讀取到的偏移量。
master 收到寫操做,偏移量則會增長。從庫持續執行同步的寫指令後,在 repl_backlog_buffer
的已複製的偏移量 slave_repl_offset 也在不斷增長。
正常狀況下,這兩個偏移量基本相等。在網絡斷連階段,主庫可能會收到新的寫操做命令,因此 master_repl_offset
會大於 slave_repl_offset
。
當主從斷開重連後,slave 會先發送 psync 命令給 master,同時將本身的 runID
,slave_repl_offset
發送給 master。
master 只須要把 master_repl_offset
與 slave_repl_offset
之間的命令同步給從庫便可。
增量複製執行流程以下圖:
65 哥:repl_backlog_buffer 過小的話從庫還沒讀取到就被 Master 的新寫操做覆蓋了咋辦?
咱們要想辦法避免這個狀況,一旦被覆蓋就會執行全量複製。咱們能夠調整 repl_backlog_size 這個參數用於控制緩衝區大小。計算公式:
repl_backlog_buffer = second * write_size_per_second
例如,若是主服務器平均每秒產生 1 MB 的寫數據,而從服務器斷線以後平均要 5 秒才能從新鏈接上主服務器,那麼複製積壓緩衝區的大小就不能低於 5 MB。
爲了安全起見,能夠將複製積壓緩衝區的大小設爲2 * second * write_size_per_second
,這樣能夠保證絕大部分斷線狀況都能用部分重同步來處理。
65 哥:完成全量同步後,正常運行過程如何同步呢?
當主從庫完成了全量複製,它們之間就會一直維護一個網絡鏈接,主庫會經過這個鏈接將後續陸續收到的命令操做再同步給從庫,這個過程也稱爲基於長鏈接的命令傳播,使用長鏈接的目的就是避免頻繁創建鏈接致使的開銷。
在命令傳播階段,除了發送寫命令,主從節點還維持着心跳機制:PING 和 REPLCONF ACK。
每隔指定的時間,主節點會向從節點發送 PING 命令,這個 PING 命令的做用,主要是爲了讓從節點進行超時判斷。
在命令傳播階段,從服務器默認會以每秒一次的頻率,向主服務器發送命令:
REPLCONF ACK <replication_offset>
其中 replication_offset 是從服務器當前的複製偏移量。發送 REPLCONF ACK 命令對於主從服務器有三個做用:
repl_backlog_buffer
緩衝區中找到並推送缺失的數據。注意,offset 和 repl_backlog_buffer 緩衝區,不只能夠用於部分複製,也能夠用於處理命令丟失等情形;區別在於前者是在斷線重連後進行的,然後者是在主從節點沒有斷線的狀況下進行的。在 Redis 2.8 及之後,從節點能夠發送 psync 命令請求同步數據,此時根據主從節點當前狀態的不一樣,同步方式多是全量複製或部分複製。本文以 Redis 2.8 及以後的版本爲例。
關鍵就是 psync
的執行:
psync
命令給 master:
replicaof
,則從節點發送 psync ? -1
,向主節點發送全量複製請求;replicaof
則發送 psync <runID> <offset>
, runID 是上次複製保存的主節點 runID,offset 是上次複製截至時從節點保存的複製偏移量。psync
命令和當前服務器狀態,決定執行全量複製仍是部分複製:
slave_repl_offset
以後的數據在 repl_backlog_buffer
緩衝區中都存在,則回覆 CONTINUE
,表示將進行部分複製,從節點等待主節點發送其缺乏的數據便可;repl_backlog_buffer
緩衝區中 (在隊列中被擠出了),則回覆從節點 FULLRESYNC <runid> <offset>
,表示要進行全量複製,其中 runID 表示主節點當前的 runID,offset 表示主節點當前的 offset,從節點保存這兩個值,以備使用。一個從庫若是和主庫斷連時間過長,形成它在主庫 repl_backlog_buffer
的 slave_repl_offset 位置上的數據已經被覆蓋掉了,此時從庫和主庫間將進行全量複製。
總結下
每一個從庫會記錄本身的 slave_repl_offset
,每一個從庫的複製進度也不必定相同。
在和主庫重連進行恢復時,從庫會經過 psync 命令把本身記錄的 slave_repl_offset
發給主庫,主庫會根據從庫各自的複製進度,來決定這個從庫能夠進行增量複製,仍是全量複製。
replication buffer 和 repl_backlog
config set client-output-buffer-limit slave
設置。repl_backlog_buffer
是一個環形緩衝區,整個 master 進程中只會存在一個,全部的 slave 公用。repl_backlog 的大小經過 repl-backlog-size 參數設置,默認大小是 1M,其大小能夠根據每秒產生的命令、(master 執行 rdb bgsave) +( master 發送 rdb 到 slave) + (slave load rdb 文件)時間之和來估算積壓緩衝區的大小,repl-backlog-size 值不小於這二者的乘積。總的來講,replication buffer
是主從庫在進行全量複製時,主庫上用於和從庫鏈接的客戶端的 buffer,而 repl_backlog_buffer
是爲了支持從庫增量複製,主庫上用於持續保存寫操做的一塊專用 buffer。
repl_backlog_buffer
是一塊專用 buffer,在 Redis 服務器啓動後,開始一直接收寫操做命令,這是全部從庫共享的。主庫和從庫會各自記錄本身的複製進度,因此,不一樣的從庫在進行恢復時,會把本身的複製進度(slave_repl_offset
)發給主庫,主庫就能夠和它獨立同步。
如圖所示:
數據過時問題
65 哥:主從複製的場景下,從節點會刪除過時數據麼?
這個問題問得好,爲了主從節點的數據一致性,從節點不會主動刪除數據。咱們知道 Redis 有兩種刪除策略:
65 哥:那客戶端經過從節點讀取數據會不會讀取到過時數據?
Redis 3.2 開始,經過從節點讀取數據時,先判斷數據是否已過時。若是過時則不返回客戶端,而且刪除數據。
若是 Redis 單機內存達到 10GB,一個從節點的同步時間在幾分鐘的級別;若是從節點較多,恢復的速度會更慢。若是系統的讀負載很高,而這段時間從節點沒法提供服務,會對系統形成很大的壓力。
若是數據量過大,全量複製階段主節點 fork + 保存 RDB 文件耗時過大,從節點長時間接收不到數據觸發超時,主從節點的數據同步一樣可能陷入全量複製->超時致使複製中斷->重連->全量複製->超時致使複製中斷……的循環。
此外,主節點單機內存除了絕對量不能太大,其佔用主機內存的比例也不該過大:最好只使用 50% - 65% 的內存,留下 30%-45% 的內存用於執行 bgsave 命令和建立複製緩衝區等。
65 哥:碼哥你的圖畫的真好看,內容好,跟着你的文章我收穫了不少,我要收藏、點贊、在看和分享。讓更多的優秀開發者看到共同進步!
謝謝讀者支持,另外讀者技術羣也開通了,關注公衆號回覆「加羣」,羣裏 N 多大廠的大佬,跟我一塊交流,共同進步!
參考資料:
[1] redis 設計與實現(黃健宏)
[2] redis replication (http://redis.io/topics/replication)
[3] designing redis replication partial resync (http://antirez.com/news/31)
(4) Redis 核心技術與實戰(https://time.geekbang.org/column/intro/329)