前言
如果某個服務器程序,只部署在一個物理服務器上就可能會面臨一下問題(單點問題)
- 可用性問題,如果這個機器掛了,那么對應的客戶端服務也相繼斷開
- 性能/支持的并發量有限
所以為了解決這些問題,就要引入分布式系統,在分布式系統中,會在多臺服務器上部署redis服務,從而形成一個redis集群,此時就可以讓這個集群給整個分布式系統提供更穩定有效的服務。
redis存在多種部署方式
- 主從模式
- 主從+哨兵模式
- 集群模式
主從模式
介紹
在若干個redis節點(物理服務器)中,有的節點被作為主節點,有的則作為從節點分別部署在redis-server進程中,從節點上的數據跟隨主節點上的數據變化并保持一致。也就是說如果主節點上保存了一些數據,那么就要將這些數據復制一份出來給從節點,當主節點對這些數據有修改時,從節點也要進行對應的修改,并且從節點上的數據是不能主動或直接修改的,只能讀取數據。
//從節點也可以看作是主節點的副本。
通過引入從節點,讓我們的服務器有了更多的硬件資源,后續如果有客戶端來進行讀取數據,就可以在所有節點中隨機挑選一個進行讀取,因為從節點的數據和主節點的數據是保持一致的,也就可以互相分擔更多的請求量,從而提高了服務器的性能。
而且引入多臺服務器,也能大大提高服務器的穩定性,當有一臺機器掛了的時候,還可以訪問其他服務器,如果是主節點掛了,那么影響的也只是寫操作,讀操作依然可以通過從節點進行(主節點只有一個)。但是這種方式只能提高讀操作的效率和穩定性,對于寫操作因為只能在主節點上完成所以并沒有什么提高。
通過單機實現主從模式
雖然主從模式需要多臺服務器才能部署,但是我們可以通過在一臺機器上,開啟多個redis進程服務來模擬一個主從模式,但是這些進程的端口號必須不同。
這里我們來模擬部署一個主節點兩個從節點
首先先復制兩個配置文件,在配置文件中配置不同的redis端口
mkdir redis-conf
cp /etc/redis/redis.conf ./slave1.conf
cp /etc/redis/redis.conf ./slave2.conf
接著配置端口號
這個daemonize表示讓redis按照后臺進程的方式來運行
最后運行redis服務器
vim slave1.conf
vim slave2.conf
redis-server ./slave1.conf
redis-server ./slave2.conf
可以看到三個端口都成功運行,但是這只是三個redis服務器,互相獨立并沒有構成主從結構,想要實現主從結構還需要進一步配置
想要配置成主從結構,需要使用slaveof
- 在配置文件中加入slaveof (masterHost) (masterPort)隨redis啟動時生效
- 在redis-server啟動命令時加入 --slaveof (masterHost) (masterPort)隨redis關閉
- 直接使用redis命令:slaveof?(masterHost) (masterPort)
這里我們使用配置文件的方式
將兩個文件都加上該配置,配置完之后需要重新啟動redis,這里世界使用kill -9
netstat -anp | grep redis
kill -9 3748325 3748331
不過這種停止redis-server的方式是和之前直接運行redis-server命令的方式搭配,如果使用的是service redis-server start 的方式啟動,就必須使用service redis-server stop停止,因為使用service redis-server start的方式啟動的進程,如果使用kill -9殺掉之后,這個redis-server進程會立刻自動重啟
?
當重新啟動后會看到多了幾個tcp連接,這些連接就代表著主從節點直接的連接,每兩個是一對。主節點就相當于服務器,從節點就相當于客戶端。
//另外一個連接則是單獨啟動redis-cli來和redis主節點建立的連接
我們來看看到底有沒有連上
可以看到我們在6379端口插入的key,在6380依然可以查看到 并且在6380端口不能進行寫操作,說明我們已經構建了一個主從模式。
查看redis節點信息
info replication
?主節點信息
- role:表示是主節點還是從節點
- connected_slaves:表示該主節點有幾個從節點
- slave:ip:表示從節點地址
- port:表示從節點端口號
- state:表示從節點在線狀態
- offset: 表示主節點和從節點之間同步數據的進度
- lag:表示延遲
從節點信息
可以看到從節點也有?connected_slaves表示從節點也可以有自己的從節點,主從結構不一定就是一個主機連多臺從節點,每個從節點也可以再連其他從節點。
斷開主從連接
slaveof no one
直接使用這個命令,就會斷開當前的主從關系,當從節點斷開主節點時,雖然已經不屬于這個從節點了,但是里面的數據是不會被刪除的,只是后面主節點的數據再發生更改,這個從節點就不會再同步數據了。
主節點
從節點
從信息中可以看到主從節點的關系已經斷開,接下來我們實現是否還能同步數據
數據已經無法同步
因為6379和6380已經不是主從節點,所以我們就可以更換6380的主節點
看以看到已經成功把6380的主節點更換成6381,主從模式的結構也發生了改變
此時6380端口的redis服務的數據又和6379一致了,不過雖然此時的6381看起來像是6380的主節點,但是實際上并不是,他仍然是一個從節點不能進行寫操作,只是作為6380同步數據的來源。
//剛才通過slaveof修改了主從結構,但是此處的主從結構修改是臨時的,如果重啟了redis服務器,仍然會變成剛開始的結構,因為我們的配置文件里所配置的結構就是和剛開始的結構一樣。
拓撲結構
一主一從
一主一從結構是最簡單的復制拓撲結構,用于主節點出現故障時向從節點提供故障轉移支持。
這種結構可以幫主節點分擔讀數據請求,但是如果寫數據請求太多,此時也是會給主節點造成不小的壓力,可以通過關閉主節點的AOF,只在從節點上開啟。也就是只讓主節點進行寫內存操作,這樣就可以減小主節點的壓力。
但是這樣的設定有一個問題,如果主節點掛了,不能讓他自動重啟,因為如果自動重啟了,此時因為沒有AOF文件,就會導致主節點數據丟失,并且因為主從復制,就會把從節點的數據也給刪了。所以當主節點掛了之后,應該讓主節點先在從節點那里獲取AOF文件后,在啟動。
一主多從(星形結構)
一種多從的結構方式,可以使節點讀寫分離,當有大量讀請求時可以將請求分攤給各個從節點,還可以讓一個從節點專門處理一些比較復雜的讀操作以此來提高整體穩定性。
但是主節點上的數據改變時,需要把數據同時同步給所以從節點,也就意味著,隨著從節點個數的增加,每次同步數據都會帶來更大的開銷。而且主從節點之間需要通過網絡通信,這就要求主節點有更大的帶寬,進而提高整體設備的成本。
樹形主從結構
通過這樣的結果就可以讓主節點有固定的從節點,可以通過從節點來對其他從節點的數據進行同步,但是相應的同步的效率也會變低
主從復制原理?
主從復制流程
- 保存主節點信息,開始配置主從同步關系后,從節點只保存主節點的地址信息
- 主從節點建立tcp連接(三次握手),從節點內部通過每秒運行的定時任務,維護復制相關邏輯,當定時任務發現存在存在新的主節點后,會嘗試與主節點建立基于tcp的網絡連接,如果連接失敗,定時任務會無限重試直到連接成功或者用戶停止主從復制。
- 發送ping命令,驗證主節點是否正常工作
- 如果redis主節點設置了密碼(requirepass參數),就要驗證權限,從節點通過配置masterauth參數來設置密碼。
- 同步數據集,主節點會把當前的所有數據全部發送給從節點,這步也是耗時最大的,分為全量同步和部分同步
- 命令持續復制,從節點復制主節點數據后,主節點會持續把命令發給從節點,從節點執行修改命令,確保主從數據一致
redis提供了psync replicationid offset命令來完成數據同步,不過一般這個命令是由服務器建立tcp連接后自動執行。
replicationid
這個id是由主節點生成的,每次服務器重啟主節點的replicationid都會不同,并且每個主節點有兩個id,id1和id2,但是一般只是用id1,從節點和主節點建立關系就會獲取主節點的replicationid。另外當從節點通過一些心跳包確定主節點已經掛了的話,就會自己成為主節點,并且給自己生成一個replicationid,但是此時這個節點會通過id2任然記住之前的主節點的id,等到主節點上線后這個節點就可以通過id2再找到原來的主節點。
?offset 偏移量
主節點和從節點上都會維護offset偏移量,主節點的偏移量就是,主節點會有很多修改操作,每個命令都會占用幾個字節,主節點就會把這些字節累加。從節點的偏移量就描述了同步數據的進度,如果從節點和主節點的replicationid和offset偏移量都一樣,就可以認為主從節點數據一致。
psync流程?
從節點發送psync命令給主節點,主節點會根據psync的參數(id和offset)以及自身數據情況決定響應情況。
- FULLRESYNC:全量數據同步
- CONTINFU:增量數據同步
- -ERR:比較老版本的redis不支持psync
psync可以從主節點獲取全部數據也可以獲得部分數據主要看參數offset,如果是-1(默認)的話就是獲取全量數據,如果是一個具體的值就是部分復制而且從當前偏移量開始復制,不過也不是從節點想從哪個地方復制就從那個地方復制,主節點也會根據實際判斷。
什么時候全量復制
- 首次和主節點進行數據同步
- 主節點不方便進行部分復制
什么時候部分復制
- 從節點已經從主節點上復制過數據了,因為一些原因斷開連接了,從節點需要重新從主節點這邊同步數據,此時因為從節點上已經有數據了所以只需要復制一部分就行
全量復制流程
1)從節點發送psync命令向主節點要數據,因為是第一次進行復制則psync參數就是(?,-1)表示進行全量復制。
2)主節點收到命令解析后回復FULLRESYNC響應
3)從節點收到響應后會保存主節點的運行信息
4)之后主節點會自動執行bgsave命令,進行快照操作完成持久化,并復制出一個新的rdb文件。(因為從節點的數據和主節點的數據要求完全同步,所以必須使用新的rdb文件以保證數據的實時性)
5)主節點將新復制的rdb文件傳輸給從節點(因為復制rdb文件是一個較高消耗的操作,而且這次是全量復制所以要傳輸的數據也比較多,所以整體過程較為耗時。就會導致在這個過程中會有新的修改命令,那么我們從節點接收到的rdb文件就不是最新的了)
6)因為無法保證傳輸的rdb文件是最新的,所以主節點會將復制和傳輸rdb文件期間接收到的寫命令放入一個緩沖區,當從節點保存完rdb文件后,主節點就會將緩沖區中的數據傳輸給從節點,這些數據任然是按照rdb的二進制格式追加到從節點的rdb文件中,以保證主從節點數據的完全一致。
7)從節點清除自身的舊數據
8)從節點加載rdb文件
9)如果從節點開啟了AOF,那么在加載的時候就會產生大量的aof日志,因為全量復制的數據量很大就會出現很多冗余的日志,因此這里就會執行bgrewriteaof,將aof文件里該刪的刪,該合并的合并,對文件進行一個整理。
無硬盤模式全量復制
在剛剛的流程中主節點復制一份新的rdb文件目的就是為了,將數據傳輸給從節點,那么為了節省開銷,就可以省去這個讀寫硬盤的操作(復制rdb),直接將數據通過網絡傳輸給從節點,這樣就節省了一次復制rdb的時間,從節點接收到數據后也是寫入到rdb文件然后加載數據,那么將這個過程也省略掉,直接把收到的數據進行加載,這樣就又省了一系列讀寫硬盤的操作。
部分復制流程
當主從節點都正常工作一段時間后,此時從節點已經有了主節點的復制,如果在某一時刻因為網絡抖動,或者其他原因主從節之間的連接斷開了的話,后續再連接上就只需要進行部分復制(具體連接過程上文有提到)。
1)如果主從節點斷開的時間超過repi-timeout設定的時間,那么主節點就認為從節點斷開了
2)從節點在斷開連接時依然會響應命令,但是由于這些復制命令發不出去都會放在一個積壓緩沖區里(一個用數組實現的環形隊列)
3)重新連接后,從節點會根據之前保存的主節點的replicationid和offset通過psync的參數發送給主節點?
4)主節點接收到psync命令后會對replicationid和offset(描繪的就是主從節點的復制進度)進行判斷,如果replicationid不是自己的replicationid主節點就會認為這個從節點是新連接上的就會進行全量復制,如果replicationid正確則進行部分復制。判斷offset的值,主要是判斷從節點當前的復制進度是多少,如果從節點當前缺失的部分剛好是積壓緩沖區里的數據,就可以直接將這部分數據復制過去,如果不是則需要將之前缺失的數據也復制過去,最后主節點還要響應CONTINUE表示這是一次部分復制。
實時復制
從節點已經和主節點同步好了數據,但是主節點依然會又源源不斷的新的請求,主節點的數據改變,從節點的數據也就要改變。
主從節點之間會通過TCP建立長連接,然后主節點會把自己收到的修改數據的請求通過連接發送給從節點,從節點在根據這些請求修改自己的數據。(如果主從結構是樹型的那么發送的過程就會較為耗時)
因為在實時復制時,主從節點需要保證連接的穩定,就會通過心跳機制來確定對方是否正常在線。
- 主節點:默認是每10s給從節點發送一個ping命令,從節點收到后就會返回pong
- 從節點:默認每個1s就給主節點發送一個特殊的請求,就會通知主節點自己當前的復制進度offset
總結?
全量復制的部分復制都是在從節點第一次連接主節點時進行的,如果以前沒連過就進行全量復制,如果連接過又斷開的就進行部分復制,也就是說全量復制和部分復制都是給從節點進行一個初始化的。而后續的主從節點間數據的同步則是通過實時復制。