wordpress頁面教程視頻小紅書搜索優(yōu)化
Redis:分布式 - 主從復制
- 概念
- 配置主從模式
- info replication
- slave-read-only
- tcp-nodelay
- 命令
- slaveof
- 主從結構
- 一主一從
- 一主多從
- 主從復制流程
- 數(shù)據(jù)同步命令
- 全量同步
- 部分同步
- 實時同步
- 節(jié)點晉升
概念
Redis
的最佳應用,還是要在分布式系統(tǒng)中。對于非分布式系統(tǒng),也就是只有一個主機提供服務,此時一旦該主機崩潰,那么整個服務就崩潰了,這稱為單點問題
。
因此引入分布式系統(tǒng),多個主機共同完成一個服務,既能提高服務并發(fā)的能力,又能避免單點問題,就算一個主機崩潰了,還有其他主機可以處理服務。
而分布式系統(tǒng),常見的模式為以下三種:
- 主從模式
- 主從 + 哨兵模式
- 集群模式
主從模式,顧名思義就是有主服務器(主節(jié)點
)和從屬服務器從節(jié)點
,此時從節(jié)點的所有數(shù)據(jù)都要去和主節(jié)點同步。那么從節(jié)點就要去復制主節(jié)點的數(shù)據(jù),在Redis
中,從節(jié)點復制了主節(jié)點的數(shù)據(jù)后,不允許修改,確保了數(shù)據(jù)完全來自于主節(jié)點。
這種模式下,寫入操作都會分配給主節(jié)點,而所有查詢操作都分配給從節(jié)點,這樣就能保證主節(jié)點的數(shù)據(jù)都是最新的,從節(jié)點通過數(shù)據(jù)同步,完成數(shù)據(jù)更新。
本博客講解Redis
的主從模式。
配置主從模式
由于大部分人手上都只有一臺主機或者云服務器,此時想要打造一個分布式系統(tǒng)就需要用一些其他技巧,而不是真的在多個主機上部署分布式。
其實在一臺主機上,是可以允許多個redis-server
進程的,只要保證每個進程的端口號不同,那么就可以有多個redis-server
存在。
首先找一個合適的位置,創(chuàng)建一個目錄,用于存放從節(jié)點的配置文件:
拷貝redis,conf
配置文件到當前目錄,由于我打算創(chuàng)建兩個叢節(jié)點,所以拷貝了兩份。
找到port
選項:
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379
默認的端口號是6379
,此端口號修改為其它端口,不要與主節(jié)點沖突。
找到daemon
選項
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
# When Redis is supervised by upstart or systemd, this parameter has no impact.
daemonize yes
保證該選項是yes
,這樣Redis
才能在后臺運行。
修改這兩個配置文件后,通過以下指令啟動:
redis-server 配置文件地址
啟動后通過ps
查看,可以看到同時有三個Redis
在運行:
我綁定的三個端口分別是:6379
、6380
、6381
。
想要啟動不同的客戶端,只需要通過-p
選項指定不同的端口:
但是此時三個節(jié)點是單獨的三個服務,還沒有構成主從結構。
配置主從需要通過slaveof
,有以下三種方式:
- 在配置?件中加?
slaveof {masterHost} {masterPort}
,當Redis
啟動時生效 - 在
redis-server
啟動命令時加?--slaveof {masterHost} {masterPort}
- 直接使?
redis
命令:slaveof {masterHost} {masterPort}
此處通過修改配置文件完成主從配置,因為其是持久的,后兩種方式在每車次啟動時都要輸入額外的命令。
在兩個slave.conf
的最末尾加上以下內容:
# 配置主從復制
slaveof 127.0.0.1 6379
再重啟兩個服務,此時兩個節(jié)點就變成了主節(jié)點的從節(jié)點了。
要先殺掉兩個進行,kill -9 PID
:
隨后再通過之前的命令啟動:
redis-server ./slave1.conf
redis-server ./slave2.conf
通過netstat
查看網(wǎng)絡情況:
可以發(fā)現(xiàn),除了三個redis-server
,還有很多其它的redis
網(wǎng)絡連接,這是因為主從之間,要進行數(shù)據(jù)傳輸,所以要創(chuàng)建額外的網(wǎng)絡連接。
測試一下:
左側端口為6379
主節(jié)點,右側為6380
從節(jié)點,主節(jié)點設置key1 111
,從節(jié)點可以get
得到,但是當從節(jié)點試圖寫入數(shù)據(jù),發(fā)生報錯,表示不允許修改數(shù)據(jù)。
info replication
通過指令info replication
,可以查看主從相關的信息。
- 主節(jié)點
6379
:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=1602971,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=1602971,lag=0
master_failover_state:no-failover
master_replid:23006f1139dc753a96d489311987709770f6c36d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1602971
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:551881
repl_backlog_histlen:1051091
-
role
:表示當前節(jié)點為主節(jié)點 -
connected_slaves
:當前有兩個從節(jié)點 -
slave0
:第一個從節(jié)點的相關信息,ip
:地址port
:端口state
:狀態(tài)offset
:同步情況,多個從節(jié)點的該值可能不同,因為同步是要時間的lag
:當前主從節(jié)點之間數(shù)據(jù)傳輸?shù)难舆t
-
master_replid
:主節(jié)點的專屬id -
offset
:主節(jié)點的數(shù)據(jù)進度,與從節(jié)點的offset
匹配,如果從節(jié)點的offset
小于主節(jié)點,說明從節(jié)點沒有同步完畢,數(shù)據(jù)版本是落后的 -
repl_backlog_xxx
:積壓緩沖區(qū),后續(xù)講解 -
從節(jié)點
6380
:
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_read_repl_offset:1602985
slave_repl_offset:1602985
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:23006f1139dc753a96d489311987709770f6c36d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1602985
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:551895
repl_backlog_histlen:1051091
master_xxx
:主節(jié)點的一些信息slave_priority
:一個優(yōu)先級,如果主節(jié)點崩潰了,會從新選主節(jié)點,與該優(yōu)先級有關master_replid
:主節(jié)點的id
這些內容也不需要記憶,可以去官網(wǎng)查詢,官方文檔有很詳細的解釋。
slave-read-only
默認情況下,從節(jié)點只能讀取數(shù)據(jù)。其實從節(jié)點也可以通過配置文件修改的,只需要把slave-read-only=no
,此時從節(jié)點就可以修改數(shù)據(jù)。但是要注意的是,從節(jié)點修改的數(shù)據(jù),是不會同步給主節(jié)點的,這就會導致主從的數(shù)據(jù)不一致,所以不要修改這個配置文件。
tcp-nodelay
Redis
主從之間,是通過TCP連接完成數(shù)據(jù)的同步的,在TCP內部,有一個nagel
算法,它是一種捎帶應答
機制的實現(xiàn)。如果啟用了該選項,那么TCP的延遲就會變高,但是消耗的帶寬會降低。
而主從之間有時候需要高頻同步數(shù)據(jù),這就對TCP延遲有很高的要求,如果延遲太大,主從數(shù)據(jù)不一致,查詢時就很有可能得到過期的數(shù)據(jù)。那么此時就可以通過設置tcp-nodelay
,關閉這個nagel
算法,這樣雖然帶寬增加了,但是延遲就會降低。
這兩種方式傳輸沒有好壞之分,而是根據(jù)場景需求,如果網(wǎng)絡環(huán)境比較復雜,消耗帶寬太高,就很容易丟包,這就得不償失了。比如在同一個機房內部署的服務器,那么此時網(wǎng)絡就很簡單,建議關閉這個nagel
算法。但是如果是跨越很遠的數(shù)據(jù)傳輸,比如說在好幾個地域布置了機房,構建了一個跨越全國甚至全球的分布式系統(tǒng),此時還是開啟這個nagel
比較好。
命令
slaveof
slaveof no one
用于斷開主從關系,從節(jié)點執(zhí)行該命令后,就不再有主節(jié)點了,而是自成主節(jié)點。
示例:
從節(jié)點6380
執(zhí)行slaveof no one
后,再次info replication
,role:master
說明此時自己已經(jīng)是主節(jié)點了,不再從屬其他節(jié)點。要注意的是,從節(jié)點變成主節(jié)點后,原先的數(shù)據(jù)不會丟失,可以繼續(xù)操作原先的數(shù)據(jù)。
slaveof
用于設置當前節(jié)點為其它節(jié)點的主節(jié)點。
語法:
slaveof ip port
示例:
此處通過slaveof
重新將6379
設置為了6380
的主節(jié)點。
不論是salveof
,還是slaveof no one
,都是臨時修改主從關系,一旦服務重啟,仍然依照配置文件設置主從關系。
主從結構
在Redis
中,有很多種主從的組織方式,它們構成了不同的拓撲結構。
一主一從
最簡單的主從結構,自然是一主一從,如下:
這種結構的功能,一般是為了防止單點問題,如果主節(jié)點崩潰,此時從節(jié)點立刻代替主節(jié)點完成服務。當寫命令并發(fā)量較高時,從節(jié)點上開始AOF
持久化方式,但是主節(jié)點只使用RDB
方式。這樣從節(jié)點就代替主節(jié)點完成持久化,而主節(jié)點可以空出更多的資源來完成寫入操作。
但是這種方式要關閉主節(jié)點的自動重啟功能,因為主節(jié)點使用RDB
持久化,此時數(shù)據(jù)往往不是最新的。一旦主節(jié)點重啟,那么就會通過RDB
恢復數(shù)據(jù),導致主節(jié)點得到舊數(shù)據(jù)。而這個舊數(shù)據(jù)又會同步給從節(jié)點,此時從節(jié)點的AOF
新數(shù)據(jù)就被舊數(shù)據(jù)覆蓋了,這就很坑了。
一主多從
實際上,數(shù)據(jù)庫的查詢操作頻率是遠大于插入操作的,所以一主多從往往是更常見的選擇,讓多個從節(jié)點完成讀取數(shù)據(jù)的任務,來提高并發(fā)能力。
- 星形結構
這種情況下,往往還需要一個負載均衡器,來分發(fā)讀取的流量到多個從節(jié)點上,讓多個從節(jié)點接收差不多的訪問量。
但是這就會導致主節(jié)點要同時與好幾個從節(jié)點進行數(shù)據(jù)同步,那么主節(jié)點的網(wǎng)絡壓力會很大,因此又出現(xiàn)了以下的樹形結構。
- 樹形結構
這種結構下,主節(jié)點只需要與少量的從節(jié)點進行同步,而其它節(jié)點作為從節(jié)點的從節(jié)點,完成二次同步。這樣就可以有效減少主節(jié)點的網(wǎng)絡壓力,但是這種情況下,數(shù)據(jù)的同步時間會變長,因為要經(jīng)過多層的同步。
樹形結構和星形結構也是各有優(yōu)劣,如果希望降低同步的時延,不怕主節(jié)點承擔太多網(wǎng)絡壓力,就用星形結構。如果希望主節(jié)點的網(wǎng)絡壓力得到緩解,可以犧牲一部分同步時延,那么使用樹形結構。
主從復制流程
接下來看一下主從關系是如何建立的,流程如下:
- 保存主節(jié)點信息:
保存主節(jié)點的ip
、port
等信息
- 建立主從
TCP
連接
從節(jié)點內部通過每秒運行的定時任務維護復制相關邏輯,當定時任務發(fā)現(xiàn)存在新的主節(jié)點后,會嘗試與主節(jié)點建立基于TCP 的網(wǎng)絡連接。如果從節(jié)點無法建立連接,定時任務會無限重試直到連接成功或者用戶停止主從復制。
- 從節(jié)點發(fā)送
ping
命令:
連接建立成功之后,從節(jié)點通過 ping
命令確認主節(jié)點在應用層上是工作良好的。
- 權限驗證:
如果主節(jié)點設置了requirepass 參數(shù),則需要密碼驗證,從節(jié)點通過配置 masterauth參數(shù)來設置密碼。如果驗證失敗,則從節(jié)點的復制將會停止。
- 同步數(shù)據(jù)集:
對于首次建立復制的場景,主節(jié)點會把當前持有的所有數(shù)據(jù)全部發(fā)送給從節(jié)點,這步操作基本是耗時最長的,所以又劃分稱兩種情況:全量同步
和部分同步
- 命令持續(xù)復制:
當從節(jié)點復制了主節(jié)點的所有數(shù)據(jù)之后,針對之后的修改命令,主節(jié)點會持續(xù)的把命令發(fā)送給從節(jié)點,從節(jié)點執(zhí)行修改命令,保證主從數(shù)據(jù)的一致性。
接下來詳細講解一下,到底Redis
是如何完成第5,6步驟中數(shù)據(jù)的同步的。
數(shù)據(jù)同步命令
Redis
中提供了一個命令psync
,其可以完成主從之間的數(shù)據(jù)同步過程。
語法:
psync replcationid offset
這兩個參數(shù),需要進行簡單的講解。
- replication id
replid
稱為復制ID,每次主節(jié)點重啟,或者從節(jié)點晉升為主節(jié)點,都會生成一個replid
,當從節(jié)點與主節(jié)點建立連接后,從節(jié)點就可以得到主節(jié)點的replid
。
通過info replication
,可以查詢到以下內容:
master_replid:23006f1139dc753a96d489311987709770f6c36d
master_replid2:0000000000000000000000000000000000000000
第一個master_replid
,就記錄著主節(jié)點的replid
,那么master_replid2
的作用是什么?
有的時候,主節(jié)點網(wǎng)絡不好,從節(jié)點會誤判主節(jié)點下線,此時從節(jié)點就會晉升為主節(jié)點,也會生成自己的replid
。此處的master_replid2
,會把之前的主節(jié)點的replid
記錄下來。
等到主節(jié)點網(wǎng)絡恢復后,重新與從節(jié)點建立連接,此時從節(jié)點不知道主節(jié)點是重啟了,還是網(wǎng)絡恢復了。就會拿master_replid2
與主節(jié)點的replid
對比,如果相同,說明主節(jié)點沒有重啟,只是網(wǎng)絡出問題了,此時從節(jié)點會重新認主。如果不同,那么就是主節(jié)點下線了。
- offset
offset
稱為偏移量,其用于維護節(jié)點的數(shù)據(jù)進度,每當主節(jié)點寫入數(shù)據(jù)后,都會把命令的字節(jié)長度進行累加記錄到offset
中。也就是說,隨著數(shù)據(jù)的寫入,這個值會越來越大。
而從節(jié)點每次更新數(shù)據(jù),也會增加自己的偏移量,表示自己同步的進度。當從節(jié)點的偏移量和主節(jié)點的偏移量完全相同,那么說明從節(jié)點已經(jīng)同步到主節(jié)點的所有數(shù)據(jù)了。
replid
和offset
共同確定一個唯一的數(shù)據(jù)集
只要 replid
和 offset
相同,那么兩個節(jié)點上的所有數(shù)據(jù)完全相同。
psync replcationid offset
如果 offset
為-1
,那么此時進行全量復制,將所有的數(shù)據(jù)都復制一份到從節(jié)點。如果offset
為具體的一個偏移量,那么從節(jié)點將從偏移量開始往后的所有數(shù)據(jù)都復制過來。
如果psync
不傳入任何參數(shù),那么replid offset
默認為?
和-1
,表示從節(jié)點不知道主節(jié)點的replid
和offset
。
但是不論從饑餓點發(fā)送什么命令,如果主節(jié)點過于繁忙,那么此時不一定會執(zhí)行從節(jié)點預期的復制方式。比如從節(jié)點申請進行全量復制,此時主節(jié)點無法承受這么多網(wǎng)絡壓力,那么可能就會變成部分復制。
當輸入psync
后,可能得到以下三種結果:
+FULLRSYNC replid offset
:進行全量復制+CONTINUE
:進行部分復制-ERR
:說明主節(jié)點不支持psync
,此時可以使用sync
此處sync
是在前臺運行的命令,一旦執(zhí)行sync
,主節(jié)點的所有命令都會被阻塞。
全量同步
全量復制是 Redis
最早支持的復制方式,主從第一次建立復制時必須進行一次全量復制。
全量復制流程圖如下:
- 從節(jié)點發(fā)送
psync
命令給主節(jié)點進行數(shù)據(jù)同步,由于是第一次進行復制,從節(jié)點沒有主節(jié)點的運行 ID 和復制偏移量,所以發(fā)送psync ? -1
。 - 主節(jié)點根據(jù)命令,解析出要進行全量復制,回復
+FULLRESYNC
響應。 - 從節(jié)點接收主節(jié)點的運行信息進行保存,比如主節(jié)點的
replid
。 - 主節(jié)點執(zhí)行
bgsave
進行RDB
文件的持久化。這一步與第三步同時進行,就算主節(jié)點已經(jīng)有RDB
文件了,但是由于RDB
的數(shù)據(jù)進度是比較落后的,所以還是要重新生成一個。 - 主節(jié)點發(fā)送
RDB
文件給從節(jié)點,從節(jié)點保存RDB
數(shù)據(jù)到本地硬盤。 - 主節(jié)點將從生成
RDB
到接收完成期間執(zhí)行的寫命令,寫入緩沖區(qū)中,等從節(jié)點保存完RDB
文件后,主節(jié)點再將緩沖區(qū)內的數(shù)據(jù)補發(fā)給從節(jié)點,補發(fā)的數(shù)據(jù)仍然按照RDB
的二進制格式追加寫入到收到的RDB
文件中,保持主從一致性。 - 從節(jié)點清空自身原有舊數(shù)據(jù)。
- 從節(jié)點加載
RDB
文件得到與主節(jié)點一致的數(shù)據(jù)。 - 如果從節(jié)點加載
RDB
完成之后,開啟了AOF
持久化功能,它會進行bgrewrite
重寫操作,因為上面這個過程從節(jié)點收到了大量數(shù)據(jù),此時AOF
也在同步記錄數(shù)據(jù),此時進行一次重寫,對剛才收到的數(shù)據(jù)進行一個壓縮。
- 無硬盤模式
新版的Redis
,對以上過程又做了優(yōu)化,主節(jié)點在生成RDB
文件時,要把數(shù)據(jù)寫入文件,然后再把文件傳輸給從節(jié)點。而無硬盤模式下,主節(jié)點生成了RDB
格式的數(shù)據(jù)后,不會寫入文件,而是直接發(fā)送給從節(jié)點。這樣就減少了大量硬盤IO,提高了同步效率。
從節(jié)點也相同,之前的從節(jié)點會把收到的RDB
數(shù)據(jù)存儲到硬盤生成文件,然后再加載文件。無硬盤模式下,從節(jié)點直接加載網(wǎng)絡中的RDB
數(shù)據(jù),不再寫入硬盤。
部分同步
部分復制主要是 Redis
針對全量復制的過高開銷做出的一種優(yōu)化措施,使用 psync replicationld offset
命令實現(xiàn)。當從節(jié)點正在復制主節(jié)點時,如果出現(xiàn)網(wǎng)絡閃斷或者命令丟失等異常情況時,從節(jié)點會向主節(jié)點要求補發(fā)丟失的命令數(shù)據(jù),如果主節(jié)點的復制積壓緩沖區(qū)存在數(shù)據(jù),則直接發(fā)送給從節(jié)點,這樣就可以保持主從節(jié)點復制的一致性。補發(fā)的這部分數(shù)據(jù)一般遠遠小于全量數(shù)據(jù),所以開銷很小。
部分復制流程如圖所示:
- 當主從節(jié)點之間出現(xiàn)網(wǎng)絡中斷時,如果超過
repl-timeout
時間,主節(jié)點會認為從節(jié)點故障并中斷復制連接。 - 主從連接中斷期間,主節(jié)點依然響應命令,但這些復制命令都因網(wǎng)絡中斷無法及時發(fā)送給從節(jié)點,所以暫時將這些命令滯留在復制積壓緩沖區(qū)中。
- 當主從節(jié)點網(wǎng)絡恢復后,從節(jié)點再次連上主節(jié)點。
- 從節(jié)點將之前保存的
replicationld
和offset
作為psync
的參數(shù)發(fā)送給主節(jié)點,請求進行部分復制。 - 主節(jié)點接到
psync
請求后,進行必要的驗證。隨后根據(jù)offset
去復制積壓緩沖區(qū)查找合適的數(shù)據(jù)并響應+CONTINUE
給從節(jié)點。 - 主節(jié)點將需要從節(jié)點同步的數(shù)據(jù)發(fā)送給從節(jié)點,最終完成一致性。
實時同步
主從節(jié)點在建立復制連接后,主節(jié)點會把自己收到的修改操作,通過 TCP 長連接的方式,源源不斷的傳輸給從節(jié)點,從節(jié)點就會根據(jù)這些請求來同時修改自身的數(shù)據(jù),從而保持和主節(jié)點數(shù)據(jù)的一致性。
另外,這樣的長連接,需要通過心跳包的方式來維護連接狀態(tài),(這里的心跳是指應用層自己實現(xiàn)的心跳,而不是 TCP 自帶的心跳)。
- 主從節(jié)點彼此都有心跳檢測機制,各自模擬成對方的客戶端進行通信。
- 主節(jié)點默認每隔 10 秒對從節(jié)點發(fā)送
ping
命令,判斷從節(jié)點的存活性和連接狀態(tài)。 - 從節(jié)點默認每隔1秒向主節(jié)點發(fā)送
replconfack{offset}
命令,給主節(jié)點上報自身當前的復制偏移重。
如果主節(jié)點發(fā)現(xiàn)從節(jié)點通信延遲超過repl-timeout
配置的值(默認60秒),則判定從節(jié)點下線,斷開復制客戶端連接。從節(jié)點恢復連接后,心跳機制繼續(xù)進行。
那么以上三種同步機制,和主從復制有什么關系?在整個主從同步的過程中,最后兩步分別是同步數(shù)據(jù)集
和持續(xù)復制命令
。
- 同步數(shù)據(jù)集:
- 如果是第一次同步,觸發(fā)
全量同步
- 如果是斷線重連,觸發(fā)
部分同步
- 如果是第一次同步,觸發(fā)
- 持續(xù)復制命令:
- 進行
實時同步
- 進行
節(jié)點晉升
先前提及過很多次,從節(jié)點是可以晉升為主節(jié)點的,那么什么時候從節(jié)點會晉升?
這分情況,對于一般的主從復制,情況如下:
- 主動斷開主從關系
用戶可以通過slaveof no one
命令,斷開與主節(jié)點的連接,此時從節(jié)點斷開連接后,自動晉升為主節(jié)點。
- 主節(jié)點崩潰
在一般的主從情況下,如果主節(jié)點崩潰,此時從節(jié)點不會自動晉升,需要用戶進行手動操作。這是一個非常難辦的問題,因為服務器崩潰是不可預知的,如果大半夜服務崩潰了,此時程序員又不能及時重啟,就會造成很大麻煩。后續(xù)Redis
引入了哨兵機制,處理這種情況下的自動晉升。