主從復制
單點問題:
在分布式系統中,如果某個服務器程序,只有一個節點(也就是一個物理服務器)來部署這個服務器程序的話,那么可能會出現以下問題:
1.可用性問題:如果這個機器掛了,那么意味著怎么服務器也就掛了
2.性能/支持的并發量也是有限的
在分布式系統中,往往會希望多個服務器來部署Redis服務,從而構造一個Redis集群;此時就可以讓這個集群給整個分布式提供更加高效/穩定的數據存儲服務等;
在分布式系統中,有多個服務器部署Redis,往往有以下幾種方式:
1.主從模式
2.主從+哨兵
3.集群
配置主從模式:
在若干個Redis節點中,有“主”節點,也有“從”節點?
例如:
從節點必須聽主節點,從節點內的數據跟主節點保存同步(從節點就是主節點的副本)~
在原本,只有一個主節點保持大量的數據,引入從節點,可以將主節點上的數據復制出來放到從節點中,后續主節點有任何修改也會同步到從節點上~
主從模式主要是針對“讀操作”進行并發了和可用性的提高?
而“寫操作”只能依賴主節點,主節點只能有一個
在實際業務中,“讀操作”往往比“寫操作”更加頻繁
如果掛了從節點的話,影響并不會很多啊,可以從其他從節點讀取數據,效果是一樣的;
但如果掛了主節點的話,那么只能讀數據,不能寫數據了,還有有一定的影響~
因為目前只有一臺云服務器,所有需要啟動多個Redis服務器進程,配置一個主節點,倆個從節點
主節點默認端口為6379,從節點默認端口分別為6380,6381
1.給從節點設置配置文件
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis# mkdir redis-conf
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis# cd redis-conf/
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# pwd
/etc/redis/redis-conf
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# cp /etc/redis/redis.conf ./slave1.conf
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# cp /etc/redis/redis.conf ./slave2.conf
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# ls
slave1.conf slave2.conf
在它們的配置文件中修改:?
修改端口號
?
修改其 daemonize 為 yes。
刪除它們的進程 重新啟動?
?此時用命令 info replication 可以查看它們當前的屬性
127.0.0.1:6379> set key 111
OK
127.0.0.1:6379> get key
"111"
127.0.0.1:6379>
root@iZbp1j57qexhmdoyijkse9Z:/etc/redis/redis-conf# redis-cli -p 6380
127.0.0.1:6380> get key
"111"
127.0.0.1:6380> set key 222
(error) READONLY You can't write against a read only replica.
此時主節點插入了數據 從節點也可以查詢到,但是從節點不能插入數據?
此時我們的結構已經搭建完成
拓撲結構:?
若干個節點之間,按照怎么樣的方式來進行組織連接:一主一從、一主多從、樹狀主從結構。
一主一從?
這是最簡單的結構,用于主節點出現宕機時從節點提供故障轉移支持;
如果寫數據請求太多的話,會給主節點帶來一定的壓力;
那么可以關閉主節點的AOF機制,只保留從節點上的AOF,這樣既可以保證數據的安全性,也可以避免持久化對主節點的性能影響;
但如果主節點掛了的話,此時沒有AOF文件,那么將會丟失數據,進一步進行主從同步,從節點的數據也被刪除了~
改進方法:當主節點掛了話,主節點從從節點上獲取AOF文件數據
?一主多從
在數據量較大的場景下,可以把讀命令負載均衡到不同的從節點上分擔壓力;
同時一些耗時的讀命令可以指定?臺專門的從節點執行,避免破壞整體的穩定性。
但是隨之從節點的增加,同步每一條數據,就需要傳輸多次,導致主節點的負載
樹形結構:
?樹據寫入節點 A 之后會同步給 B 和 C 節點,B 節點進一步把數據同步給 D 和 E 節點。當主節點需要掛載等多個從節點時為了避免對主節點的性能干擾,但同步的延時是比剛才長的
原理:
主從節點建立復制流程圖:
1.先保存主節點的ip和端口變量等信息
2、建立TCP的網絡連接,三次握手,為了驗證通信雙方是否能夠正確的讀寫數據
3.驗證主節點是否能夠正常的工作 從節點發送ping命令 主節點返回pong
4.如果主節點設置了 requirepass 參數,則需要密碼驗證
數據同步:
Redis提供了psync命名完成主從數據的同步
PSYNC 的語法格式? ? ?PSYNC replicationid offset
理解下面過程,先了解一下前提概念:
?1.replicationid/replid (復制id)
主節點的復制id,主節點重新啟動,或者從從節點晉升到主節點的時候,也會生成這個id(即便是同一個主節點,在它重啟的時候,生成的id都是不同的)
主節點和從節點建立復制關系的時候,就會從主節點這邊獲取replicationId
2.offset(偏移量)
這個是主節點和從節點上都會維護的一個參數-------偏移量(整數)
主節點上會收到很多的修改操作的命令,每個命令會占據幾個字節,煮雞蛋會把這些修改命令的字節數都累加起來~
從節點的偏移量就描述了此時從節點這邊的數據同步到哪一步~
如果從節點和主節點上的偏移量一致,說明已經同步了~
replid + offset 共同標識了一個 "數據集".
如果兩個節點, 他們的 replid 和 offset 都相同, 則這兩個節點上持有的數據, 就一定相同.
在 info replication 命令中可以查看到:
psync過程:
1)從節點發送 psync 命令給主節點,replid 和 offset 的默認值分別是 ? 和 -1. ??
2)主節點根據 psync 參數和自身數據情況決定響應結果:?
- ?如果回復 +FULLRESYNC replid offset,則從節點需要進行全量復制流程。?
- ?如果回復 +CONTINEU,從節點進行部分復制流程。?
- ?如果回復 -ERR,說明 Redis 主節點版本過低,不支持 psync 命令。從節點可以使用 sync 命令進行全量復制
同步過程分為:全量復制和部分復制。?
- 全量復制:一般用于初次復制場景,Redis 早期支持的復制功能只有全量復制,它會把主節點全部數據一次性發送給從節點,當數據量較大時,會對主從節點和網絡造成很大的開銷。
- 部分復制:用于處理在主從復制中因網絡閃斷等原因造成的數據丟失場景,當從節點再次連上主節點后,如果條件允許,主節點會補發數據給從節點。因為補發的數據遠小于全量數據,可以有效避免全量復制的過高開銷。
主要就是看offset這里的進度~
如果offset寫作-1,就是獲取全量數據;
如果寫作具體的整數,那么就是從當前偏移量位置獲取數據;
全量復制:
當首次主節點進行數據同步的時候或者主節點不方便部分復制的時候;
全量復制流程:
1)從節點發送 psync 命令給主節點進行數據同步,由于是第一次進行復制,從節點沒有主節點的運行 ID 和復制偏移量,所以發送 psync ? -1。?
2)主節點根據命令,解析出要進行全量復制,回復 +FULLRESYNC 響應。?
3)從節點接收主節點的運行信息進行保存。?
4)主節點執行 bgsave 進行 RDB 文件的持久化。?
5)主節點發送 RDB 文件給從節點,從節點保存 RDB 數據到本地硬盤。?
6)主節點將從生成 RDB 到接收完成期間執行的寫命令,寫入緩沖區中,等從節點保存完 RDB ?件后,主節點再將緩沖區內的數據補發給從節點,補發的數據仍然按照 rdb 的?進制格式追加寫入到收到的 rdb 文件中. 保持主從一致性。?
7)從節點清空自身原有舊數據。?
8)從節點加載 RDB 文件得到與主節點一致的數據。?
9)如果從節點加載 RDB 完成之后,并且開啟了 AOF 持久化功能,它會進行 bgrewrite 操作,得到最近的 AOF 文件。
部分復制:
從節點要從主節點這里進行全量復制,全量復制開銷很大,有些時候,從節點本身就已經持有主節點大部分數據,所有不需要全量復制;或者出現網絡波動的情況,主節點修改的數據無法同步過來,等到網絡波動結束,主節點和從節點重新建立連接,那么就需要同步數據~
流程:
?
1)當主從節點之間出現網絡中斷時,如果超過 repl-timeout 時間,主節點會認為從節點故障并終端復制連接。
2)主從連接中斷期間主節點依然響應命令,但這些復制命令都因網絡中斷無法及時發送給從節點,所以暫時將這些命令滯留在復制積壓緩沖區中。
3)當主從節點網絡恢復后,從節點再次連上主節點。?
4)從節點將之前保存的 replicationId 和 復制偏移量作為 psync 的參數發送給主節點,請求進行部分復制。
5)主節點接到 psync 請求后,進行必要的驗證。隨后根據 offset 去復制積壓緩沖區查找合適的數據,并響應 +CONTINUE 給從節點。?
6)主節點將需要從節點同步的數據發送給從節點,最終完成一致性。
實時復制:
主從節點建立連接之后,主節點會把自己之后收到的修改操作,通過tcp長連接的方式,源源不斷的傳輸給主節點,從節點會根據這些請求同時修改自身的數據,保存主從數據一致性~
這樣的長連接,會通過心跳包的方式維護連接狀態;
心跳包機制:
主節點:默認每隔10s給從節點發送ping命令,從節點收到返回pong
從節點:默認每隔1s給主節點發送一個特定的請求,就會上報此時從節點復制數據的進度(offset)
如果主節點發現從節點通信延遲超過 repl-timeout 配置的值(默認 60 秒),則判定從節點下線,斷開復制客戶端連接。從節點恢復連接后,心跳機制繼續進行。
哨兵(Sentinel)
在主從模式中,一旦主節點掛了,那么就只剩下從節點了;雖然能夠提供讀操作,但是不能寫入數據,從節點不能自動的升級為主節點,不能替原來主節點對應的角色;通過人工的方式太不靠譜了(人不能一天24個小時盯著機器看),所有此時Redis提供了“哨兵”機制就是為了解決這一問題,讓程序保存“高可用”狀態~
當主節點出現故障時,Redis Sentinel 能自動完成故障發現和故障轉移,并通知應用方,從而實現真正的高可用。
?
哨兵架構:
每一個哨兵節點都是單獨的進程,并且會提供奇數個
Redis哨兵的核心功能就是
這些哨兵會對現有的主從節點建立長連接,定期發送心跳包,借助上述的機制,就可以及時發現某個主機掛了;
如果從節點掛了,那么其實沒有關系;
如果主節點掛了,那么哨兵就要發揮它們的作用;
此時一個哨兵發現掛了還不夠,需要多個哨兵節點來共同認同這件事,為了防止誤判的情況;如果發現主節點確實掛了,那么哨兵們就會在這些從節點中推薦一個新的主節點;挑選出新的主節點之后,哨兵節點就會控制這個新的主節點,執行讓其他的從節點轉移到新的主節點上面;最后哨兵會通知客戶端程序,告知新的主節點是哪個,讓后續客戶端再進行寫操作針對新的主節點進行操作~~~
?通過上面的介紹,可以看出 Redis Sentinel 具有以下幾個功能:?
- 監控: Sentinel 節點會定期檢測 Redis 數據節點、其余哨兵節點是否可達。?
- 故障轉移: 實現從節點晉升(promotion)為主節點并維護后續正確的主從關系。?
- ?通知: Sentinel 節點會將故障轉移的結果通知給應用方。
部署哨兵(基于docker)
由于只有一個云服務器,但是需要6個節點,基于在虛擬機上實現;
docker可以認為是一個“輕量級”的虛擬機;起到了虛擬機這樣隔離環境的效果,但是又不會吃很多硬件資源;
略......................
哨兵選舉原理:
1.主觀下線:
當主節點宕機時,此時主節點和三個哨兵節點的心跳包沒有了;
站在哨兵節點的角度上,主節點發生了嚴重故障,因此三個哨兵節點會把主節點判斷為主觀下線(SDown);
2.客觀下線:
多個哨兵節點認為主節點掛了,那就是掛了,當故障得票數 >= 配置的法定票數
這就是客觀下線(ODown)
3.選舉出哨兵的leader
此時接下來只需要從剩下的從節點中選一個當主節點出來,但這個工作不需要多個哨兵都參與,只需要選出一個代表(leader),由這個leader負責升級從節點到主節點這個過程;
哨兵之間的選舉涉及到Raft算法:
Raft 的領導選舉是通過“投票”機制來完成的,每個節點都會投票選舉一個領導者。在選舉過程中,候選人會向其他節點請求投票,如果得到大多數節點的支持,它就成為新的領導者。
簡而言之, Raft 算法的核心就是 "先下手為強". 誰率先發出了拉票請求, 誰就有更大的概率成為 leader.
無論選到誰作為leader都不重要,只需要能選出來一個節點即可~
4) leader 挑選出合適的從節點成為新的主節點
挑選規則:
- 比較優先級. 優先級高(數值小的)的上位. 優先級是配置文件中的配置項( slave-priority ?或者?replica-priority ).?
- 比較 ?replication offset ?誰復制的數據多, 高的上位. ?
- 比較 ?run id , 誰的 id 小 誰上位.??
當某個 slave 節點被指定為 master 之后, ?
1. leader 指定該節點執行 ?slave no one , 成為 master?
2. leader 指定剩余的 slave 節點, 都依附于這個新 master?
集群
上述的哨兵模式,雖然提高了系統的可用性,但是真實存儲數據的還是主節點和從節點,所有的數據都存儲在主從節點上;如果數據量超過了主從節點的內存上,那么就可能出現嚴重問題了~
所以就可以加入多臺機器,可以解決內存不夠的情況,引入多組主從節點,從而構成一個更大的整體,稱為集群~
例如此時1TB的數據被分塊成三份,那么每一組機器只需要存儲整個數據全集的 1/3 即可.
數據分片算法
數據可以分層多份,那么要怎么樣分,才是最合適的呢?
1.哈希求余
借鑒了哈希表的基本思想,借助hash函數,把一個key映射到一個整數上,再針對數組的長度求余,就可以得到一個數組下標
設有 N 個分片, 使用?[0, N-1] 這樣序號進行編號.
N 為 3 的時候, [100, 120] 這 21 個 hash 值的分布 (此處假定計算出的 hash 值是一個簡單的整數, 方便肉眼觀察)
優點: 簡單高效, 數據分配均勻.
但是缺點更加明顯,一旦需要擴容,原有的映射規則就會被破壞,此時需要重新計算,重新排列,所有需要大量的搬運
如上圖可以看到, 整個擴容一共 21 個 key, 只有 3 個 key 沒有經過搬運, 其他的 key 都是搬運過的.
2.一致性哈希算法
在哈希求余算法過程中,每個key屬于哪個分片,是交替出現的(這就導致搬運成本大大提高),在一致性哈希算法中,可以將交替出現,改進成連續出現~
?此時,如果新增一塊分片,那么只需在環上重新安排一塊區間
此時只需將0號分片的數據搬運到3號分片即可,那么1號和2號分片的區間是不變的~
但是由于1號和2號數據不變,那么它們所屬的分片上的數據量就不均勻了,有的多有的少,數據傾斜;
3.哈希槽分區算法 (Redis 使用)
為了解決上述搬運成本高和數據分配不均勻的問題,Redis引入了哈希槽算法;
hash_slot = crc16(key) % 16384??crc16 也是一種 hash 算法.
相當于是把整個哈希值, 映射到 16384 個槽位上, 也就是 [0, 16383].
16384 其實是 16 * 1024, 也就是 2^14.然后再把這些槽位比較均勻的分配給每個分片. 每個分片的節點都需要記錄自己持有哪些分片.
?假設當前有三個分片, 一種可能的分配方式:?
? 0 號分片: [0, 5461], 共 5462 個槽位?
? 1 號分片: [5462, 10923], 共 5462 個槽位?
? 2 號分片: [10924, 16383], 共 5460 個槽位?
?如果需要進行擴容, 比如新增一個 3 號分片, 就可以針對原有的槽位進行重新分配. ?
比如可以把之前每個分片持有的槽位, 各拿出一點, 分給新分篇. ?
?種可能的分配方式:?
? 0 號分片: [0, 4095], 共 4096 個槽位?
? 1 號分片: [5462, 9557], 共 4096 個槽位?
? 2 號分篇: [10924, 15019], 共 4096 個槽位?
? 3 號分片[4096, 5461] + [9558, 10923] + [15019, 16383], 共 4096 個槽位?
搭建集群:
通常集群的搭建都是三個主節點,六個從節點這樣的形式
搭建過程復雜略..........
?主節點宕機處理流程:
-
自動故障檢測
節點間通過?Gossip 協議?定期交換狀態信息。若某主節點被多數節點標記為不可達(通過心跳超時判斷),集群會觸發故障轉移。 -
故障轉移流程
-
從節點發起競選。
-
其他主節點投票選出新的主節點。
-
原主節點的槽重新分配給新主節點。
-
客戶端自動重定向到新主節點。
-
小結:?
主從模式主要是數據冗余和讀寫分離。主節點處理寫,從節點復制數據并處理讀,這樣可以提高讀的吞吐量。但主從模式的問題在于主節點單點故障,這時候就需要哨兵來監控和自動故障轉移。哨兵確保當主節點宕機時,能自動選一個從節點升級為主,保證系統的高可用性。不過,主從加哨兵還是單主節點,寫操作還是受限于主節點的性能,無法水平擴展。
接下來是集群,它通過分片解決數據量過大和寫性能的問題。集群將數據分散到多個主節點,每個節點負責一部分數據,這樣寫操作可以分布到不同節點,提高了整體性能。同時,每個主節點有對應的從節點,保證高可用。用戶可能需要處理大量數據或高并發寫入,這時候集群就比主從加哨兵更合適。
對比總結
架構 | 核心目標 | 優點 | 缺點 | 適用場景 |
---|---|---|---|---|
主從模式 | 數據冗余、讀寫分離 | 簡單易用,成本低 | 手動故障恢復,單點寫入瓶頸 | 小規模應用,數據備份 |
哨兵模式 | 高可用性(自動故障轉移) | 自動化故障轉移,服務發現 | 無法擴展寫性能,不分片 | 中等規模,高可用需求 |
集群模式 | 數據分片 + 高可用 + 水平擴展 | 支持大規模數據和高并發,高可用 | 配置復雜,事務受限 | 大數據量、高并發、高可用需求 |
-
主從模式?→?主從 + 哨兵?→?集群模式
隨著業務增長,逐步從單點冗余過渡到自動化高可用,最終通過分片解決性能和容量瓶頸。 -
集群模式?是終極方案,但復雜度高,需根據實際需求權衡是否必要。