引言
Redis的應用還得是在分布式系統當中。在分布式系統中,涉及到一個非常關鍵的問題,就是單點問題。例如,如果某個服務器程序,只有一個節點(只搞了一個物理服務器,來部署這個服務器程序),這樣會帶來幾個缺點,第一個就是可用性問題,如果這個機器掛了,就會意味著服務就中斷了;第二個就是性能問題,一個服務器程序支持的并發量是有限的。所以為了解決上述問題。就引入了分布式系統。
在分布式系統當中,往往希望有多個服務器來部署redis服務,從而構成一個redis集群,此時的就可以讓這個集群給整個分布式系統中其他的服務,提供更穩定/高效的數據存儲功能。
在分布式系統中,希望使用多個服務器來部署redis,存在以下幾種redis的部署方式:
- 主從復制模式。
- 主從+哨兵模式
- 集群模式
這篇博客就來講解主從模式。
主從復制模式
在若干個redis節點中,有的是“主”節點,有的是“從”節點。如下圖所示,有三個物理服務器(稱為是三個節點)分別部署了一個redis-server
進程,此時就可以把其中的一個節點,作為“主”節點,另外兩個作為“從”節點。
從節點得聽從主節點的(從節點上的數據要跟隨主節點變化;從節點的數據要和主節點保持一致)
本來,在主節點上保存一堆數據,引入從節點之后,就是要把主節點上面的數據,復制出來,放到從節點中。后續,主節點這邊對于數據有任何修改,都會把這樣的修改同步到從節點上。簡單來說,從節點就是主節點的副本。另外需要注意的是,主從模式中,從節點上的數據,不允許修改,只能讀取數據。
由于從節點的數據時刻和主節點保持一致,因此其他的客戶端從從節點這里讀取數據,和從主節點這里讀取數據,是沒有區別的。后續如果有客戶端來讀取數據了,就可以從上述節點中,隨機挑一個節點,給客戶端提供讀取數據的服務。
如果掛掉了某個從節點,對整體來說是沒有影響到,此時繼續從主節點或者其它從節點讀取數據,得到的效果完全相同。但是主節點掛掉的話,還是有一定影響的,從節點只能讀數據,如果需要寫數據,就沒法寫了。
配置主從模式
大部分人的手中只有一臺主機或者云服務器,要想打造一個分布式系統需要一點技巧。我們只需要保證每個進程的端口號不同,那么就可以存在多個redis-server
。
我們先創建一個目錄,來存放從節點的配置文件。
然后分別對拷貝過來的配置文件作修改
找到port
選項,然后就行修改,由于主節點端口號是6379
,因此為了不和主節點發生沖突,可以改成其他的值,我這個slave1.conf
修改成了6380
,另一個改成了6381
。還需要把daemonize
修改成yes
,這樣redis才能在后臺啟動。
修改完成后,通過以下命令進行啟動:
redis-server 配置文件地址
啟動后通過ps
查看,可以看到同時有三個Redis
在運行:
如果想要啟動不同的客戶端,只需要通過redis-cli -p 端口號
進行啟動。
修改完之后,這三個節點并沒有構成主從結構,想要成為主從結構,還需要進一步的配置。配置從主從結構的方式有三種,需要使用slaveof
。
- 在配置文件中加入
slaveof{masterHost}{masterPort}
隨Redis啟動生效。 - 在
redis-server
啟動命令時加入--slaveof{masterHost}{masterPort}
生效。 - 直接使用redis命令:
slaveof{masterHost}{masterPort}
生效。
修改配置文件是永久生效的,其它兩種方式需要手動生效。我們通過修改配置文件來實現主從結構。兩個文件都要修改。
# 配置主從復制
slaveof 127.0.0.1 6379
修改完成之后,需要重新啟動才能生效。
先使用kill -9 pid
殺掉兩個從節點。
接著重新啟動兩個從節點。
redis-server ./slave1.conf
redis-server ./slave1.conf
最后使用netstat
查看網絡情況。
可以發現,除了三個redis-server
,還有很多其它的redis
網絡連接,這是因為主從之間,要進行數據傳輸,所以要創建額外的網絡連接。其中一個redis
從節點和主節點之間的tcp連接。從節點啟動之后就會和主節點建立tcp連接。主節點相當于服務器,從節點相當于客戶端。
現在來進行測試。
左邊是主節點,右邊是從節點。在主節點寫入數據之后,從節點可以收到。但是在從節點上不能寫入數據,只能讀取數據。
info replication
我們可以通過info replication
來查看主從節點相關的信息。
左邊是主節點,右邊是從節點。
role
:表示當前節點是主節點還是從節點。master
為主節點,slave
為從節點。connected_salves
:表示有幾個從節點連接。slave0
:第一個從節點的相關信息。offset
:同步數據的進度,多個節點的該值不同,因為同步需要時間。lag
:當前主從節點之間數據傳輸的延遲
master_replid
:主節點的傳輸ID。offset
:主節點的數據進度,與從節點的offset
匹配,如果從節點的offset
小于主節點,說明從節點沒有同步完成,數據版本是落后的。repl_backlog_xxx
:積壓緩沖區。slave_priority
:一個優先級,如果主節點崩潰了,會重新選主節點,與該優先級有關。
斷開主從復制
slaveof
命令不但可以建立復制,還可以在從節點執行slaveof no one
來斷開與主節點的復制關系,例如在6390節點上執行slaveof no one
來斷開復制。斷開復制的主要流程是:
- 斷開與主節點復制關系。
- 從節點上升為主節點。
從節點斷開復制后并不會拋棄原有數據,只是無法再獲取主節點上的數據變化。
通過 slaveof
命令還可以實現切主操作,將當前從節點的數據源切換到另?個主節點。執行
slaveof {newMasterIp} {newMasterPort}
命令即可。
切主操作主要流程:
1)斷開與舊主節點復制關系。
2)與新主節點建立復制關系。
3)刪除從節點當前所有數據。
4)從新主節點進行復制操作。
不管是salveof
還是slaveof no one
,都是臨時進行修改,一旦服務器進行重啟,那么依然會按照配置文件的主從關系執行。
只讀
默認情況下,從節點使用slave-read-only=yes
配置為只讀模式。由于復制只能從主節點到從節點,對于從節點的任何修改,主節點都沒有變化,修改從節點會造成主從數據不一致。所以建議線上不要修改從節點的只讀模式。
傳輸延遲
主從節點?般部署在不同機器上,復制時的網絡延遲就成為需要考慮的問題,Redis 為我們提供
了 repl-disable-tcp-nodelay
參數用于控制是否關閉 TCP_NODELAY
,默認為 no,即開啟 tcp-nodelay
功能,說明如下:
- 當關閉時,主節點產生的命令數據?論大小都會及時地發送給從節點,這樣主從之間延遲會變小,但增加了網絡帶寬的消耗。適用于主從之間的網絡環境良好的場景,如同機房部署。
- 當開啟時,主節點會合并較小的 TCP 數據包從而節省帶寬。默認發送時間間隔取決于 Linux 的內核,?般默認為 40 毫秒。這種配置節省了帶寬但增大主從之間的延遲。適用于主從網絡環境復雜的場景,如跨機房部署。
拓撲
一主一從結構
一主一從結構式最簡單的復制拓撲結構,用于主節點出現故障時從節點提供故障轉移支持。當應用寫命令并發量較高且需要持久化時,可以通過關閉主節點的AOF,只在從節點上開啟AOF,這樣既可以保證數據安全性的同時也避免了持久化對主節點的性能干擾。但是這種設定方法有一個嚴重的缺陷,主節點一旦掛了,不能讓他自動重啟,如果自動重啟,此時沒有AOF文件,就會丟失數據,進一步的主從同步,會把從節點的數據也給刪了。
改進辦法就是當主節點掛了之后,就需要讓主節點從從節點這里獲取到AOF文件,再啟動。
一主多從結構
一主多從結構使得應用端可以利用多個從節點實現讀寫分離。對于讀場景比較多的場景,可以把讀命令負載均衡到不同的從節點上分擔壓力。同時一些耗時的讀命令可以指定一臺專門的從節點執行,避免破環整體的穩定性。對于寫并發量較高的場景,多個從節點會導致主節點寫命令的多次發送從而加重主節點的負載,
樹形主從結構
樹形主從結構(分層結構)使得從節點不但可以復制主節點數據,同時可以作為其他從節點的主節點繼續向下層復制。通過引?復制中間層,可以有效降低住系欸按負載和需要傳送給從節點的數據量。數據寫?主節點之后會同步給 A 和 C 節點,A 節點進?步把數據同步給 B 和 D 節點。當主節點需要掛載等多個從節點時為了避免對主節點的性能干擾,可以采用這種拓撲結構。
主從復制原理
復制過程?致分為 6 個過程:
- 保存主節點的信息
開始配置主從同步關系之后,從節點只保存主節點的地址信息,即主節點的IP和端口信息。 - 主從建立連接
TCP的三次握手。從節點內部通過每秒運行的定時任務和維護復制相關邏輯,當定時任務發現存在新的主節點,會嘗試與主節點建立基于TCP的網絡連接。如果從節點無法建立連接,定時任務會無限重試直到連接成功或者用戶停止主從復制。 - 發送ping命令
連接建立成功之后,從節點通過 ping 命令確認主節點在應?層上是工作良好的。如果 ping 命令的結果 pong 回復超時,從節點會斷開 TCP 連接,等待定時任務下次重新建?連接。 - 權限驗證
如果主節點設置了requirepass
參數,則需要密碼驗證,從節點通過配置masterauth
參數來設置密碼。如果驗證失敗,則從節點的復制將會停止。 - 同步數據集
對于首次建立復制的場景,主節點會把當前持有的所有數據全部發送給從節點,這步操作基本是耗時最長的,所以又劃分稱兩種情況:全量同步和部分同步。 - 命令持續復制
當從節點復制了主節點的所有數據之后,針對之后的修改命令,主節點會持續的把命令發送給從節點,從節點執行修改命令,保證主從數據的?致性。
數據同步
redis
提供了psync
命令,完成數據同步的過程。psync
不需要我們手動去執行,redis服務器會在建立好主從同步關系之后,自動執行psync
。語法格式如下:
PSYNC replicationid offset
如果 replicationid 設為 ?
并且 offset 設為 -1
此時就是在嘗試進行全量復制。
如果 replicationid offset 設為了具體的數值, 則是嘗試進?部分復制。
replication/replid
(復制ID)
主節點的復制id。主節點重新啟動,或者從節點晉升為主節點,都會生成一個replicationid
。(同一個節點,每次重啟,生成的replicationid
也會變化)。從節點在和主節點建立連接之后,就會獲取到主節點的replicationid
。
通過 info replication
即可看到 replicationid
。
master_replid:d5221004c144d5521a5ec2cf9efa60e6e5a2b0f5
master_replid2:0000000000000000000000000000000000000000
一般情況下replid2
是用不上的。比如說有一個主節點A,還有一個從節點B。如果A和B通信過程中出現了一些網絡抖動,B可能認為A掛了,B就會自己成為主節點(給自己生成了一個replid
),此時,B也會記得之前舊的replid
,就是通過replid2
,后續網絡穩定了,B還可以根據replid2
重新回到A。
- offset(偏移量)
參與復制的主從節點都會維護自身復制偏移量。主節點(master)在處理完寫?命令后,會把命令的字節長度做累加記錄,統計信息在 info replication
中的 master_repl_offset
指標中。
127.0.0.1:6379> info replication
# Replication
role:master
...
master_repl_offset:1055130
從節點(slave)每秒鐘上報自身的復制偏移量給主節點,因此主節點也會保存從節點的復制偏移量,統計指標如下:
127.0.0.1:6379> info replication
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=1055214,lag=1
...
從節點在接受到主節點發送的命令后,也會累加記錄自身的偏移量。統計信息在 info replication
中的slave_repl_offset
指標中:
127.0.0.1:6380> info replication
# Replication
role:slave
...
slave_repl_offset:1055214
psync運行流程
從節點發送psync
命令給主節點,replid
和offset
的默認值分別是?
和-1
。主節點根據psync
參數和自身數據情況決定響應結果:
- 如果回復
+FULLRESYNC replid offset
,則從節點需要進行全量復制流程。 - 如果回復
+CONTINEU
,從節點進行部分復制流程。 - 如果回復
-ERR
,說明Redis主節點版本過低,不支持psync
命令。從節點可以使用sync
命令進行全量復制。
全量復制
全量復制是 Redis 最早支持的復制方式,也是主從第?次建立復制時必須經歷的階段。全量復制的運行流程如圖所示。
- 從節點發送
psync
命令給主節點進行數據同步,由于是第一次進行復制,從節點沒有主節點的運行ID和復制偏移量,所以發送psync ? -1
。 - 主節點根據命令,解析出要進行全量復制,回復
+FULLPESYNC
響應。 - 從節點接收主節點的運行信息進行保存。
- 主節點執行
bgsave
進行RDB文件的持久化。 - 從節點發送RDB文件給從節點,從節點保存RDB數據到本地硬盤。
- 主節點將從生成RDB到接收完成期間執行的寫命令,寫入緩沖區中,等從節點保存RDB文件后,主節點再將緩沖區的數據補發給從節點,補發的數據仍然按照RDB的二進制格式追加寫入到收到的RDb文件中,保持主從一致性。
- 從節點清空自身原有舊數據。
- 從節點加載RDB文件得到與主節點一致的數據。
- 如果從節點加載RDB完成之后,并且開啟了AOF持久化功能,它會進行
bgrewrite
操作,得到最近的AOF文件。
全量復制的場景發生在首次和主節點進行數據同步以及主節點不方便進行部分復制的時候。
全量復制是?件高成本的操作:主節點 bgsave 的時間,RDB 在網絡傳輸的時間,從節點清空舊數據的時間,從節點加載 RDB 的時間等。所以?般應該盡可能避免對已經有?量數據集的 Redis 進?全量復制。
無硬盤模式
默認情況下, 進行全量復制需要主節點?成 RDB ?件到主節點的磁盤中, 再把磁盤上的 RDB文件通過發送給從節點。Redis 從 2.8.18 版本開始支持無磁盤復制。 主節點在執行 RDB ?成流程時, 不會?成 RDB 文件到磁盤中了, 而是直接把生成的 RDB 數據通過網絡發送給從節點. 這樣就節省了?系列的寫硬盤和讀硬盤的操作開銷。
即使引入了無硬盤模式,仍然整個操作比較重,比較耗時的。因為網絡傳輸時無法省略的,相比于網絡傳輸來說,讀寫硬盤是小頭。
部分復制
- 當主節點之間出現網絡中斷時,如果超過
repl-timeout
時間,主節點會認為從節點故障并終端復制連接。 - 主從連接中斷期間主節點依然響應命令,但這些復制命令都因網絡中斷無法及時發送給從節點,所以暫時將這些命令滯留在復制積壓緩沖區中。
- 當主從節點網絡恢復后,從節點再次連上主節點。
- 從節點將之前保存的
replicationid
和復制偏移量作為psync
的參數發送給主節點,請求進行部分復制。 - 主節點接到
psync
請求后,進行必要的驗證。隨后根據offset
去復制積壓緩沖區查找合適的數據,并響應+CONTINUE
給從節點。 - 主節點將需要從節點同步的數據發送給從節點,最終完成一致性。
復制積壓緩沖區
復制積壓緩沖區是保存在主節點上的?個固定長度的隊列,默認大小為 1MB,當主節點有連接的從節點(slave)時被創建,這時主節點(master)響應寫命令時,不但會把命令發送給從節點,還會寫入復制積壓緩沖區,如圖所示:
由于緩沖區本質上是先進先出的定?隊列,所以能實現保存最近已復制數據的功能,?于部分復制和復制命令丟失的數據補救。復制緩沖區相關統計信息可以通過主節點的 info replication
中:
127.0.0.1:6379> info replication
# Replication
role:master
...
repl_backlog_active:1 // 開啟復制緩沖區
repl_backlog_size:1048576 // 緩沖區最??度
repl_backlog_first_byte_offset:7479 // 起始偏移量,計算當前緩沖區可?范圍
repl_backlog_histlen:1048576 // 已保存數據的有效?度
實時復制
從節點已經和主節點同步好了數據,但是之后,主節點這邊會源源不斷的收到新的修改數據的請求,主節點上的數據就會隨之改變,也需要能夠同步給從節點。從節點和主節點之間會建立TCP的長連接,然后主節點把自己收到的修改數據的請求,通過上述連接,發送給主節點,從節點再根據這些修改請求,修改內存中的數據。
再進行實時復制的時候,需要保證連接處于可用狀態,即通過過心跳包的方式來維護連接狀態。
- 主從節點彼此都有心跳檢測機制,各?模擬成對方的客?端進行通信。
- 主節點默認每隔 10 秒對從節點發送 ping 命令,判斷從節點的存活性和連接狀態。
- 從節點默認每隔 1 秒向主節點發送 replconf ack {offset} 命令,給主節點上報自身當前的復制偏移量。
如果主節點發現從節點通信延遲超過 repl-timeout 配置的值(默認 60 秒),則判定從節點下線,斷
開復制客?端連接。從節點恢復連接后,心跳機制繼續進?。