您好,登錄后才能下訂單哦!
Redis深度歷險分為兩個部分,單機Redis和分布式Redis。
本文為分布式Redis深度歷險系列的第一篇,主要內容為Redis的復制功能。
Redis的復制功能的作用和大多數分布式存儲系統一樣,就是為了支持主從設計,主從設計的好處有以下幾點:
讀寫分離,提高讀寫性能
數據備份,減少數據丟失的風險
高可用,避免單點故障
Redis的復制主要分為同步和命令傳播兩個步驟:
同步可以理解為全量,是將主服務器某一時刻的所有數據全部同步到從服務器。
命令傳播可以理解為增量,當主服務器數據被修改時,主服務器向從服務器發送對應的數據修改命令。
同步分為以下幾個步驟:
1.從服務器向主服務器發送SYNC
命令(執行SLAVE OF
命令的第一步也會執行SYNC
)
2.主服務器在收到從服務器命令時,會執行BGSAVE
,也就是新開一個子進程將內存中的數據保存到RDB文件中。同時使用一個內存緩沖區記錄從現在開始執行的寫命令,該內存緩沖區的作用就是記錄RDB文件生成期間的增量。
3.向從服務器發送RDB文件
4.將緩沖區中的寫命令發送給從服務器
同步可以分為兩種情況,一種是從服務器第一次連接主服務器,另一種是從服務與主服務器的網絡鏈接斷開了,重新連上主服務器并重新同步。
命令傳播實現邏輯比較簡單,當主服務器執行了寫命令后,為了保證從服務器與主服務器數據的一致性,主服務器會將寫命令發送給從服務器,從服務器執行完收到的寫命令后其數據就能和主服務器保持一致了(當然會有延時),注意,從服務器對于客戶端來說是只讀的,因此從服務器的所有數據都是來自于主服務器的同步or命令傳播。
假設Redis主從服務器之間的網絡環境不太可靠,我們來看看上述復制方法會出現什么問題。假設有主服務器A和從服務器B,主服務器中目前存在1-10000共一萬條數據。
1.初始連接,從服務器第一次從主服務器同步數據,同步完成后,從服務器也有1-10000共一萬條數據。
2.主服務器新增10001,10002兩條數據
3.通過命令傳播,從服務器也新增10001,10002兩條數據
4.這時候主從服務器之間的網絡斷開
5.主服務器新增數據10003,因為網絡斷開,所以從服務器感受不到數據變化
6.網絡恢復,從服務器重新連接上主服務器,并發送SYNC命令,進行同步操作
7.主服務器將所有數據發送給從服務器(1-10003)
從上述步驟中可以看到,當從服務器重新連接上主服務器時,會重新進行全量同步,造成大量不必要的IO開銷,如果網絡環境不穩定時,會導致主服務器一直將內存中的數據寫到磁盤再發送給從服務器。
為了解決老版復制問題,Redis2.8對于復制功能進行了優化。實現如下:
1.主服務器會維護一個偏移量,每次向服務器傳播N個字節的數據時,該偏移量就會加上N,比如說一開始是0,接受到一條set key1 value1
后,其偏移量就為13(真實偏移可能不是13,只是舉個例子)。//這里可能要看下代碼確認
2.從服務器也維護一個偏移量,當從服務器收到到主服務器的N個字節數據時,該偏移量會加上N。
3.主服務器維護一個固定大小的緩沖區,每次接受到客戶端寫命令后,都會將對應命令
往這個緩沖區寫入。當寫入內容超出固定大小后,會覆蓋原來的數據。
4.主服務器有一個唯一id
5.從服務器連接上主服務時,會向主服務器發送上一次連接的主服務器的id以及偏移量,這里又分幾種情況:
如果從服務器沒傳id或者id與當前主服務器不匹配,那主服務器將傳送全量數據
如果從服務器的offset在緩沖區中不能找到(落后太多導致緩沖區已經被新數據覆蓋了),那也會進行全量同步
如果offset能在緩沖區找到,則主服務從offset開始,將緩沖區的數據依次發送給從服務器。(有做pipeline的優化嗎)
以上就是新版復制的大致思路,要注意的是,主服務器緩沖區的大小設置很關鍵,如果設置的太大會導致空間浪費,如果太小會導致網絡環境不好時,其退化為老版復制。
之前我就踩過這樣的坑:在上云時,redis集群在兩個不同機房,主從之前網絡環境不太穩定,而redis機器上存儲的value比較大,很容易就將緩沖區占滿導致每次全量同步,形成惡性循環,從服務器落后不可讀,主服務器不可寫(當從Redis落后太多時,主Redis將拒絕寫入,具體參數可以配置的,下文還會提到)
所以建議將緩沖區大小設置為平均重連間隔*每秒寫入數據量*2
從服務器默認會每秒一次的頻率向主服務器發送心跳:REPLCONF A?K <replication_offset>
,
replication_offset代表從服務器當前的復制偏移量。
心跳有三個作用:
1.檢測主從服務器的網絡連接
2.實現min-slaves功能
3.檢測命令丟失
主服務器會記錄從服務器上次發送心跳是什么時間,根據這個時間,我們能知道主從服務器之間的連接是不是出現了故障
Redis為了保證數據的安全性,可以配置當從服務器小于min-slaves-to-write
個或者min-slaves-to-write
個從服務器的延遲都大于等于min-slaves-max-lag
時,主服務器拒絕寫。
主從之間的復制,其實是以主服務器作為從服務器的客戶端來實現的(在Redis中,所有服務器之間的數據傳遞都是以該種方式)。假設主服務器向從服務器發送一條寫命令,但網絡出現異常,從服務器并沒有收到該命令。
這就會導致數據不一致的狀態(你可能想主服務器發送命令時,如果從沒返回失敗,進行重發不就好了嗎?如果說從成功執行了命令,但是再回復主的時候出現了問題,那主如果重發就會造成數據異常了)。所以主服務器會根據心跳信息來決定要發送的數據。看個例子:
初始,主服務器和從服務器偏移量都是100。
主服務器收到客戶端的寫命令,將偏移量改成110,同時向從服務器發送寫命令,但因網絡原因,從服務器并沒有收到,其偏移量仍然是100。主服務器根據心跳發現從服務器的偏移量是100落后于自己,所以會將100-110的數據進行重發。
?
看到這里,你可能對于上述方案的正確性感到質疑:在從服務器接收到100-110的數據前,它發送心跳包告訴主服務器自己當前偏移為100,然后接收到了100-110的數據。這時下個心跳還沒發出,主服務器認為從服務器落后于自己,再次發送100-110的數據,導致從服務器再次寫入100-110的數據,導致數據異常!
?
如果你有想到這個問題,說明你是有在認真思考了~
其實是不存在這種情況的,原因是redis是單線程的!記住單線程三個字,再回頭看一遍問題描述,相信你能想明白~
?
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。