天天看點

Redis之複制

1.配置

1.1 建立複制

  參與複制的Redis執行個體劃分為主節點(master) 和從節點(slave)。預設情況下,Redis都是主節點。每個從節點隻能有一個主節點,而主節點可以同時具有多個從節點。複制的資料流是單向的,隻能由主節點複制到從節點。配置複制的方式有以下三種:

1) 在配置檔案中加入slaveof {masterHost} {masterPort} 随 Redis啟動生效。

2) 在 redis-server 啟動指令後加入--slaveof {masterHost} {masterPort}生效。

3) 直接使用指令:slaveof {masterHost} {masterPort}生效。

  綜上所述,slaveof指令在使用時,可以運作期動态配置,也可以提前寫到配置檔案中。例如本地啟動兩個端口為6379和6380的 Redis 節點,在127.0.0.1:6380執行如下指令:

127.0.0.1:6380 >slaveof 127.0.0.1 6379      

  slaveof 配置都是在從節點發起,這時6379 作為主節點,6380作為從節點。複制關系建立後執行如下指令測試:

127.0.0.1:6379>set hello redis
OK  .
127.0.0.1:6379>get hello
"redis"
127.0.0.1:6380>get hello
"redis"      

  從運作結果中看到複制已經工作了,針對主節點6379的任何修改都可以同步到從節點6380中。

  slaveof 本身是異步令,執行slaveof指令時,節點隻儲存主節點資訊後傳回,後續複制流程在節點内部異步執行。主從節點複制成功建立後,可以使用 info replication 指令檢視複制相關狀态,如下所示

1) 主節點6379複制狀态資訊:

127.0.0.1:6379>info replication
# Replication
role:master
connected_slaves:1
slave0: ip=127.0 .0 .1,port=6379,state = online ,offset= 43 #lag=0
....      

2) 從節點6380複制狀态資訊:

127.0.0.1:6380>info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0      

1.2 斷開複制

  slaveof指令不但可以建立複制,還可以在從節點執行slaveof no one來斷開與主節點複制關系。例如在6380節點上執行slaveof no one來斷開複制。

Redis之複制
Redis之複制

  斷開複制主要流程:

1) 斷開與主節點複制關系。

2) 從節點晉升為主節點。

  從節點斷開複制後并不會抛棄原有資料,隻是無法再擷取主節點上的資料變化。通過slaveof指令還可以實作切主操作,所謂切主是指把目前從節點對主節點的複制切換到另一個主節點。執行slaveof {newMasterlp} {newMasterPort}指令即可,例如把6380節點從原來的複制6379節點變為複制6381節點,如圖6-3所示。

  切主操作流程如下:

1) 斷開與舊主節點複制關系。

2) 與新主節點建複制關系。

3) 删除從節點目前所有資料。

4) 對新主節點進行複制操作。

  切主後從節點會清空之前所有的資料,線上人工操作時小心slaveof在錯誤的節點上執行或者指向錯誤的主節點。

1.3 安全性

  對于資料比較重要的節點,主節點會通過設定requirepass參數進行密碼驗證,這時所有的用戶端通路必須使用auth指令實行校驗。從節點與主節點的複制連接配接是通過一個特殊辨別的用戶端來完成,是以需要配置從節點的masterauth參數與主節點密碼保持一緻,這樣從節點才可以正确地連接配接到主節點并發起複制流程。

1.4 隻讀

  預設情況下,從節點使用slave-read-only=yes配置為隻讀模式。由于複制隻能從主節點到從節點,對于從節點的任何修改主節點都無法感覺,修改從節點會造成主從資料不一緻。是以建議線上不要修改從節點的隻讀模式。

1.5 傳輸延遲

  主從節點一般部署在不同機器上,複制時的網絡延遲就成為需要考慮的問題,Redis為我們提供了repl-disable-tcp-nodelay 參數用于控制是否關閉 TCP_NODELAY, 預設關閉,說明如下:

□ 當關閉時,主節點産生的指令資料無論大小都會及時地發送給從節點,這樣主從間延遲會變小,但增加了網絡帶寬的消耗。适用于主從之間的網絡環境良好的場景,如同機架或同機房部署。

□ 當開啟時,主節點會合并較小的TCP資料包進而節省帶寬。預設發送時間間隔取決.于 Linux的核心,一般預設為40毫秒。這種配置節省了帶寬但增大主從之間的延遲。适用于主從網絡環境複雜或帶寬緊張的場景,如跨機房部署。

  部署主從節點時需要考慮網絡延遲、帶寬使用率、防災級别等因素,如要求低延遲時,建議同機架或同機房部署并關閉 repl-disable-tcp-nodelay; 如果考慮高容災性,可以同城跨機房部署并開啟 repl-disable-tcp-nodelay。

1.6 拓撲

  Redis的複制拓撲結構可以支援單層或多層複制關系,根據拓撲複雜性可以分為以下三種:一主一從、一主多從、樹狀主從結構,下面分别介紹。

1.一主一從結構

Redis之複制

  一主一從結構是最簡單的複制拓撲結構,用于主節點出現當機時從節點提供故障轉移支援。當應用寫指令并發量較高且需要持久化時,可以隻在從節點上開啟AOF, 這樣既保證資料安全性同時也避免了持久化對主節點的性能幹擾。但需要注意的是,當主節點關閉持久化功能時,如果主節點脫機要避免自動重新開機操作。因為主節點之前沒有開啟持久化功能自動重新開機後資料集為空,這時從節點如果繼續複制主節點會導緻從節點資料也被清空的情況,喪失了持久化的意義。安全的做法是在從節點上執行 slaveof no one 斷開與主節點的複制關系,再重新開機主節點進而避免這一問題。

2.—主多從結構

Redis之複制

  一主多從結構(又稱為星形拓撲結構)使得應用端可以利用多個從節點實作讀寫分離(見圖6-5)。對于讀占比較大的場景,可以把讀指令發送到從節點來分擔主節點壓力。同時在日常開發中如果需要執行一些比較耗時的讀指令,如:keys、sort等,可以在其中一台從節點上執行,防止慢查詢對主節點造成阻塞進而影響線上服務的穩定性。對于寫并發量較高的場景,多個從節點會導緻主節點寫指令的多次發送進而過度消耗網絡帶寬,同時也加重了主節點的負載影響服務穩定性。

3.樹狀主從結構

Redis之複制

  樹狀主從結構(又稱為樹狀拓撲結構)使得從節點不但可以複制主節點資料,同時可以作為其他從節點的主節點繼續向下層複制。通過引入複制中間層,可以有效降低主節點負載和需要傳送給從節點的資料量。如圖所示,資料寫入節點A後會同步到B和C節點,B節點再把資料同步到D和E節點,資料實作了一層一層的向下複制。當主節點需要挂載多個從節點時為了避免對主節點的性能幹擾,可以采用樹狀主從結構降低主節點壓力。

2.原理

2.1 複制過程

  在從節點執行Slaveof指令後,複制過程便開始運作,下面詳細介紹建立複制的完整流程,如圖6-7所示。

Redis之複制

  從圖中可以看出複制過程大緻分為6個過程:

1) 儲存主節點(master)資訊。

執行slaveof後從節點隻儲存主節點的位址資訊便直接傳回,這時建立複制流程還沒有開始,在從節點6380執行info replication可以看到如下資訊:

master_host:127.0.0.1
master_port: 6379
master_link_status: down      

從統計資訊可以看出,主節點的ip和port被儲存下來,但是主節點的連接配接狀态(master_link_status)是下線狀态。執行slaveof後Redis會列印如下日志:

SLAVE OF 127.0.0.1:6379 enabled (user request from 'id=65 addr=127.0.0.1:58090
          fd=5 name= age = 11 idle=0 flags=N db=0 sub=0 psub=0 multi= -1 qbuf=0 qbuf-free=
          32768 obl = 0  oll = 0 omem= 0 events=r cmd=slaveof')      

通過該日志可以幫助運維人員定位發送slaveof指令的用戶端,友善追蹤和發現問題。

2) 從節點(slave) 内部通過每秒運作的定時任務維護複制相關邏輯,當定時任務發存在新的主節點後,會嘗試與該節點建立網絡連接配接,如圖6-8所示。

Redis之複制

從節點會建立一個socket套接字,例如圖6-8中從節點建立了一個端口為24555的套接字,專門用于接受主節點發送的複制指令。從節點連接配接成功後列印如下日志:

* Connecting to MASTER 127.0.0.1:6379
* MASTER <-> SLAVE sync started      

如果從節點無法建立連接配接,定時任務會無限重試直到連接配接成功或者執行slaveof no one取消複制,如圖6-9所示。

關于連接配接失敗,可以在從節點執行info replication査看master_link_down_since_seconds名額,它會記錄與主節點連接配接失敗的系統時間。從節點連接配接主節點失敗時也會每秒列印如下日志,友善運維人員發現問題:

* Error condition on socket for SYNC:  {socket_error_reason}      

3) 發送ping指令

連接配接建立成功後從節點發送ping請求進行首次通信,ping請求主要目的如下:

□ 檢測主從之間網絡套接字是否可用。

□ 檢測主節點目前是否可接受處理指令。

如果發送ping指令後,從節點沒有收到主節點的pong回複或者逾時,比如網絡逾時或者主節點正在阻塞無法響應指令,從節點會斷開複制連接配接,下次定時任務會發起重連,如圖 6-10所示。

Redis之複制
Redis之複制

從節點發送的Ping指令成功傳回,Redis列印如下日志,并繼續後續複制流程:

Master replied to PING, replication can continue...      

4) 權限驗證

如果主節點設定了 requirepass參數,則需要密碼驗證,從節點必須配置masterauth參數保證與主節點相同的密碼才能通過驗證;如果驗證失敗複制将終止,從節點重新發起複制流程。

5) 同步資料集

主從複制連接配接正常通信後,對于首次建立複制的場景,主節點會把持有的資料全部發送給從節點,這部分操作是耗時最長的步驟。Redis在 2.8版本以後采用新複制指令psync進行資料同步,原來的sync指令依然支援,保證新舊版本的相容性。新版同步劃分兩種情況:全量同步和部分同步,下一節将重點介紹.

6) 指令持續複制

當主節點把目前的資料同步給從節點後,便完成了複制的建立流程。接下來主節點會持續地把寫指令發送給從節點,保證主從資料一緻性。

2.2 資料同步

  Redis在2.8及以上版本使用psync指令完成主從資料同步,同步過程分為: 全量複制和部分複制。

□ 全量複制: 一般用于初次複制場景,Redis早期支援的複制功能隻有全量複制,它會把主節點全部資料一次性發送給從節點,當資料量較大時,會對主從節點和網絡造成很大的開銷。

□ 部分複制:用于處理在主從複制中因網絡閃斷等原因造成的資料丢失場景,當從節點再次連上主節點後,如果條件允許,主節點會補發丢失資料給從節點。因為補發的資料遠遠小于全量資料,可以有效避免全量複制的過高開銷。

  部分複制是對老版複制的重大優化,有效避免了不必要的全量複制操作。是以當使用複制功能時,盡量采用2.8以上版本的Redis。

  psync指令運作需要以下元件支援:

□ 主從節點各自複制偏移量。

□ 主節點複制積壓緩沖區。

□ 主節點運作id。

1.複制偏移量

  參與複制的主從節點都會維護自身複制偏移量。主節點(master) 在處理完寫人指令後,會把指令的位元組長度做累加記錄,統計資訊在info relication中的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 relication  中的slave_repl_offset名額中:

127.0.0.1:6380> info replication
# Replication
role:slave
slave_repl_offset :1055214      

  複制偏移量的維護如6-11所示。

  

Redis之複制

  通過對比主從節點的複制偏移量,可以判斷主從節點資料是否一緻。

  2.複制積壓緩沖區

  複制積壓緩沖區是儲存在主節點上的一個固定長度的隊列,預設大小為1MB,當主節點有連接配接的從節點(slave) 時被建立,這時主節點(master) 響應寫指令時,不但會把指令發送給從節點,還會寫入複制積壓緩沖區,如圖6-12所示。

Redis之複制

  由于緩沖區本質上是先進先出的定長隊列,是以能實作儲存最近已複制資料的功能,用于部分複制和複制指令丢失的資料補救。複制緩沖區相關統計資訊儲存在主節點的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         //已儲存資料的有效長度。
role:slave
slave_repl_offset :1055214      

  根據統計名額,可算出複制積壓緩沖區内的可用偏移量範圍: [repl_backlog_first_byte_offset,repl_backlog_first_byte_offset+repl_backlog_histlen]

  3.主節點運作ID

  每個Redis節點啟動後都會動态配置設定一個40位的十六進制字元串作為運作ID。運作 ID的主要作用是用來唯一識别Redis節點,比如從節點儲存主節點的運作ID識别自己正在複制的是哪個主節點。如果隻使用ip+port的方式識别主節點,那麼主節點重新開機變更了整體資料集(如替換 RDB/AOF檔案),從節點再基于偏移量複制資料将是不安全的,是以當運作 ID變化後從節點将做全量複制。可以運作 info server 指令檢視目前節點的運作ID:

127.0.0.1:6379> info server
# Server
redis_version: 3.0.7
run_id:545f7c76183d0798a327591395bG30000ee6def9      

  需要注意的是 Redis 關閉再啟動後,運作ID會随之改變,例如執行如下指令:

# redis-cli -p 6379 in fo server | grep run_id
run_id:545f7c76183d0798a327591395b030000ee6def9
# redis-cli -p shutdown
# redis-server redis-6379.conf
# redis-cli -p 6379 info server I grep run_id
run_id:2b2ec5f49f752f35c2b2da4d05775b5b3aaa57ca      

如何在不改變運作ID的情況下重新開機呢?

當需要調優一些記憶體相關配置,例如:hash-max-ziplist-value等,這些配置需要 Redis 重新加載才能優化已存在的資料,這時可以使用 debug reload 指令重新加載RDB 并保持運作 ID 不變,進而有效避免不必要的全量複制。指令如下:

# redis-cli -p 6379 info server I grep run_id
run_id:2b2ec5f49f752f35c2b2da4d05775b5b3aaa57ca
# redis-cli debug reload
OK
# redis-cli -p 6379 info server I grep run_id
run_id:2b2ec5f49f752f35c2b2da4d05775b5b3aaa57ca      

debug reload指令會阻塞目前Redis節點主線程,阻塞期間會生成本地RDB快照并清空資料之後再加載RDB檔案。是以對于大資料量的主節點和無法容忍阻塞的應用場景,謹慎使用。

4.psync指令

  從節點使用psync指令完成部分複制和全量複制功能,指令格式:psync {runld} {offset}, 參數含義如下:

□ runld: 從節點所複制主節點的運作id。

□ offset: 目前從節點已複制的資料偏移量。

  psync指令運作流程如圖6-13所示。

Redis之複制

  流程說明:

1) 從節點(slave) 發送psync指令給主節點,參數runld是目前從節點儲存的主節點運作ID,如果沒有則預設值為?,參數offset是目前從節點儲存的複制偏移量,如果是第一次參與複制則預設值為-1。

2) 主節點(master) 根據psync參數和自身資料情況決定響應結果:

    • 如果回複 +FULLRESYNC {runId}{offset},那麼從節點将觸發全量複制流程。
    • 如果回複 +CONTINUE,從節點将觸發部分複制流程。
    • 如果回複 +ERR, 說明主節點版本低于Redis 2.8,無法識别psync指令,從節點将發送舊版的sync指令觸發全量複制流程。

2.3 全量複制

Redis之複制

  全量複制是Redis最早支援的複制方式,也是主從第一次建立複制時必須經曆的階段。觸發全量複制的指令是sync和psync, 它們的對應版本如圖6-14所示。

  這裡主要介紹psync全量複制流程,它與 2.8以前的sync全量複制機制基本一緻。全量複制的完整運作流程如圖6-15所示。

Redis之複制

1) 發送psync指令進行資料同步,由于是第一次進行複制,從節點沒有複制偏移量和主節點的運作是以發送psync ?-1。

2) 主節點根據 psync ?-1 解析出目前為全量複制,回複 +FULLRESYNC 響應0

3) 從節點接收主節點的響應資料儲存運作ID和偏移量offset, 執行到目前步驟時從節點列印如下日志:

Partial resynchronization not possible (no cached master)
Full resync from master : 92dlcbl4ff7ba97816216f7beb839efe036775b2:216789      

4) 主節點執行bgsave儲存RDB檔案到本地,主節點bgsave相關日志如下:

M * Full resync requested by slave 127.0.0.1:6380
M * Starting BGSAVE for SYNC with target:disk
C * Background saving started by pid 32618
C * RDB:0 MB of memory used by copy-on-write
M * Background saving terminated with success      

Redis3.0之後在輸出的日志開頭會有M、S、C 等辨別,對應的含義是:M=目前為主節點曰志,S=目前為從節點曰志,C=子程序曰志,我們可以根據日志辨別快速識别出每行日志的角色資訊。

5) 主節點發送RDB檔案給從節點,從節點把接收的RDB檔案儲存在本地并直接作為從節點的資料檔案,接收完RDB後從節點列印相關日志,可以在日志中査看主節點發送的資料量:

16:24:03.057  * MASTER <-> SLAVE sync: receiving 24777842 bytes from master      

需要注意,對于資料量較大的主節點,比如生成的RDB檔案超過6GB以上時要格外小心。傳輸檔案這一步操作非常耗時,速度取決于主從節點之間網絡帶寬,通過細緻分析Full resync和MASTER <-> SLAVE這兩行日志的時間差,可以算出RDB檔案從建立到傳輸完畢消耗的總時間。如果總時間超過repl-timeout所配置的值(預設60秒),從節點将放棄接受RDB檔案并清理已經下載下傳的臨時檔案,導緻全量複制失敗,此時從節點列印如下日志:

M 27 May 12:10:31.169 # Timeout receiving bulk data from MASTER... If the problem
persists try to set the 'repl-timeout' parameter in redis.conf to a larger value.      

針對資料量較大的節點,建議調大repl-timeout參數防止出現全量同步資料逾時。例如對于千兆網卡的機器,網卡帶寬理論峰值大約每秒傳輸100MB,在不考慮其他程序消耗帶寬的情況下,6GB的RDB檔案至少需要60秒傳輸時間,預設配置下,極易出現主從資料同步逾時。

關于無盤複制: 為了降低主節點磁盤開銷,Redis支援無盤複制,生成的RDB檔案不儲存到硬碟而是直接通過網絡發送給從節點,通過repl-diskless-sync參數控制,預設關閉。無盤複制适用于主節點所在機器磁盤性能較差但網絡帶寬較充裕的場景。注意無盤複制目前依然處于試驗階段,線上使用需要做好充分測試。

6) 對于從節點開始接收RDB快照到接收完成期間,主節點仍然響應讀寫指令,是以主節點會把這期間寫指令資料儲存在複制用戶端緩沖區内,當從節點加載完RDB檔案後,主節點再把緩沖區内的資料發送給從節點,保證主從之間資料一緻性。如果主節點建立和傳輸RDB的時間過長,對于高流量寫入場景非常容易造成主節點複制用戶端緩沖區溢出。預設配置為 client-output-buffer-limit slave 256MB 64MB 60,如果 60 秒内緩沖區消耗持續大于64MB 或者直接超過256MB時,主節點将直接關閉複制用戶端連接配接,造成全量同步失敗。對應日志如下:

M 27 May 12:13:33.669 # Client id=2 addr=127.0.0.1: 24555 age=1 id le=1 flags=S
qbuf=0 qbuf-free=0 obl=18824 oll=21382 omem=268442640 events=r cmd=psync
scheduled to be closed ASAP for overcoming of output buffer limits .      

是以,運維人員需要根據主節點資料量和寫指令并發量調整client-output-buffer-limit slave 配置,避免全量複制期間用戶端緩沖區溢出。

對于主節點,當發送完所有的資料後就認為全量複制完成,列印成功日志:Synchronization with slave 127.0.0.1:6380 succeeded,但是對于從節點全量複制依然沒有完成,還有後續步驟需要處理。

7) 從節點接收完主節點傳送來的全部資料後會清空自身舊資料,該步驟對應如下日志:

16:24:02.234 * MASTER <-> SLAVE sync: Flushing old data      

8) 從節點清空資料後開始加載RDB檔案,對于較大的RDB檔案,這一步操作依然比較耗時,可以通過計算日志之間的時間差來判斷加載RDB的總耗時,對應如下日志:

16:24:03.578 * MASTER <-> SLAVE sync: Loading DB in memory
16:24:06.756 * MASTER <-> SLAVE sync: Finished with success      

對于線上做讀寫分離的場景,從節點也負責響應讀指令。如果此時從節點正出于全量複制階段或者複制中斷,那麼從節點在響應讀指令可能拿到過期或錯誤的資料。對于這種場景,Redis複制提供了slave-serve-stale-data 參數,預設開啟狀态。如果開啟則從節點依然響應所有指令。對于無法容忍不一緻的應用場景可以設定no來關閉指令執行,此時從節點除了info和slaveof 指令之外所有的指令隻傳回“SYNC with master inprogress”資訊。

9) 從節點成功加載完RDB後,如果目前節點開啟了AOF持久化功能,它會立刻做bgrew riteaof操作,為了保證全量複制後AOF持久化檔案立刻可用。

通過分析全量複制的所有流程,讀者會發現全量複制是一個非常耗時費力的操作。它的時間開銷主要包括:

□ 主節點 bgsave 時間。

□ RDB檔案網絡傳輸時間。

□ 從節點清空資料時間。

□ 從節點加載RDB的時間。

□ 可能的AOF重寫時間。

例如我們線上資料量在6G左右的主節點,從節點發起全量複制的總耗時在2分鐘左右。是以當資料量達到一定規模之後,由于全量複制過程中将進行多次持久化相關操作和網絡資料傳輸,這期間會大量消耗主從節點所在伺服器的CPU、記憶體和網絡資源。是以除了第一次複制時采用全量複制在所難免之外,對于其他場景應該規避全量複制的發生。正因為全量複制的成本問題,Redis實作了部分複制功能。

2.4 部分複制

  部分複制主要是Redis針對全量複制的過高開銷做出的一種優化措施,使用psync {runld} {offset}指令實作。當從節點(slave) 正在複制主節點(master)時,如果出現網絡閃斷或者指令丢失等異常情況時,從節點會向主節點要求補發丢失的指令資料,如果主節點的複制積壓緩沖區記憶體在這部分資料則直接發送給從節點,這樣就可以保持主從節點複制的一緻性。補發的這部分資料一般遠遠小于全量資料,是以開銷很小。部分複制的流程如圖6-16所示。

Redis之複制

流程說明;

1) 當主從節點之間網絡出現中斷時,如果超過repl-timeout時間,主節點會認為從節點故障并中斷複制連接配接,列印如下日志:

M # Disconnecting timedout slave:127.0.0.1:6380
M # Connection with slave 127.0.0.1:6380 lost .      

如果此時從節點沒有當機,也會列印與主節點連接配接丢失日志:

S # Connection with master lost.
S * Caching the disconnected master state.      

2) 主從連接配接中斷期間主節點依然響應指令,但因複制連接配接中斷指令無法發送給從節點,不過主節點内部存在的複制積壓緩沖區,依然可以儲存最近一段時間的寫指令資料,預設最大緩存1MB。

3) 當主從節點網絡恢複後,從節點會再次連上主節點,列印如下日志:

S * Connecting to MASTER 127.0.0.1:6379
S * MASTER <-> SLAVE sync started
S * Non blocking connect for SYNC fired the event.
S * Master replied to PING, replication can continue...      

4) 主從連接配接恢複後,由于從節點之前儲存了自身已複制的偏移量和主節點的運作ID。是以會把它們當作psync參數發送給主節點,要求進行部分複制操作。該行為對應從節點日志如下:

S * Trying a partial resynchronization (request 2b2ec5f49f752f35c2b2da4d05775b5b3aaa57ca:49768480).      

  5) 主節點接到psync指令後首先核對參數runid是否與自身一緻,如果一緻,說明之前複制的是目前主節點;之後根據參數offset在自身複制積壓緩沖區查找,如果偏移量之後的資料存在緩沖區中,則對從節點發送+C0NTINUE響應,表示可以進行部分複制。從節點接到回複後列印如下日志:

S * Successful partial resynchronization with master.
S * MASTER <-> SLAVE sync:Master accepted a Partial Resynchronization.      

6) 主節點根據偏移量把複制積壓緩沖區裡的資料發送給從節點,保證主從複制進入正常狀态。發送的資料量可以在主節點的日志擷取,如下所示:

M * Slave 127.0.0.1:6380 asks for synchronization
M * Partial resynchronization request from 127.0.0.1:6380 accepted. Sending 78
       bytes of backlog starting from offset 49769216.      

從日志中可以發現這次部分複制隻同步了78位元組,傳遞的資料遠遠小于全量資料。

2.5 心跳

  主從節點在建立複制後,它們之間維護着長連接配接并彼此發送心跳指令。

  主從心跳判斷機制:

1) 主從節點彼此都有心跳檢測機制,各自模拟成對方的用戶端進行通信,通過client list指令檢視複制相關用戶端資訊,主節點的連接配接狀态為 flags=M, 從節點連接配接狀态為flags=S。

2) 主節點預設每隔10秒對從節點發送Ping指令,判斷從節點的存活性和連接配接狀态。可通過參數repl-ping-slave-period控制發送頻率。

3) 從節點在主線程中每隔1秒發送replconf ack {offset}指令,給主節點上報自身目前的複制偏移量。replconf指令主要作用如下:

    • 實時監測主從節點網絡狀态。
    • 上報自身複制偏移量,檢查複制資料是否丢失,如果從節點資料丢失,再從主節點的複制緩沖區中拉取丢失資料。
    • 實作保證從節點的數量和延遲性功能,通過min-slaves-to-write、min-slaves-max-lag參數配置定義。

  主節點根據replconf指令判斷從節點逾時時間,展現在info replication統計中的 lag資訊中,lag表示與從節點最後一次通信延遲的秒數,正常延遲應該在0 和 1之間。如果超過repl-timeout配置的值(預設60秒),則判定從節點下線并斷開複制用戶端連接配接。即使主節點判定從節點下線後,如果從節點重新恢複,心跳檢測會繼續進行。

2.6 異步複制

  主節點不但負責資料讀寫,還負責把寫指令同步給從節點。寫指令的發送過程是異步完成,也就是說主節點自身處理完寫指令後直接傳回給用戶端,并不等待從節點複制完成。

  主節點複制流程:

1) 主節點6379接收處理指令。

2) 指令處理完之後傳回響應結果。

3) 對于修改指令異步發送給6380從節點,從節點在主線程中執行複制的指令。

  由于主從複制過程是異步的,就會造成從節點的資料相對主節點存在延遲。具體延遲多少位元組,我們可以在主節點執行info replication指令檢視相關名額獲得。如下:

slave0:ip=127.0.0.1,port=6380,state=online,offset=841,lag=1
master_repl_offset:841      

  在統計資訊中可以看到從節點slaveO資訊,分别記錄了從節點的ip和 port, 從節點的狀态,offset表示目前從節點的複制偏移量,master_repl_offset表示目前主節點的複制偏移量,兩者的內插補點就是目前從節點複制延遲量。Redis的複制速度取決于主從之間網絡環境,repl-disable-tcp-nodelay,指令處理速度等。正常情況下,延遲在1秒以内。

3.開發與運維中的問題

  了解了複制原理之後,本節我們重點分析基于複制的應用場景。通過複制機制,資料集可以存在多個副本(從節點)。這些副本可以應用于讀寫分離、故障轉移(failover)、實時備份等場景。但是在實際應用複制功能時,依然有一些坑需要跳過。

3.1 讀寫分離

  對于讀占比較高的場景,可以通過把一部分讀流量分攤到從節點(slave) 來減輕主節點(master)壓力,同時需要注意永遠隻對主節點執行寫操作。

  當使用從節點響應讀請求時,業務端可能會遇到如下問題:

□ 複制資料延遲。

□ 讀到過期資料。

□ 從節點故障。

1.資料延遲

  redis複制資料的延遲由于異步複制特性是無法避免的,延遲取決于網絡帶寬和指令阻塞情況,比如剛在主節點寫入資料後立刻在從節點上讀取可能擷取不到。需要業務場景允許短時間内的資料延遲。對于無法容忍大量延遲場景,可以編寫外部監控程式監聽主從節點的複制偏移量,當延遲較大時觸發報警或者通知用戶端避免讀取延遲過高的從節點,實作邏輯如圖6-20所示。

Redis之複制

  說明如下:

1) 監控程式(monitor)定期檢查主從節點的偏移量,主節點偏移量在info replication的master_repl_offset名額記錄,從節點偏移量可以查詢主節點的slaveO字段的offset名額,它們的內插補點就是主從節點延遲的位元組量。

2) 當延遲位元組量過高時,比如超過10MB。監控程式觸發報警并通知用戶端從節點延遲過高。可以采用Zookeeper的監聽回調機制實作用戶端通知。

3) 用戶端接到具體的從節點高延遲通知後,修改讀指令路由到其他從節點或主節點上。當延遲恢複後,再次通知用戶端,恢複從節點的讀指令請求。這種方案的成本比較高,需要單獨修改适配Redis的用戶端類庫。如果涉及多種語言成本将會擴大。用戶端邏輯需要識别出讀寫請求并自動路由,還需要維護故障和恢複的通知。采用此方案視具體的業務而定,如果允許不一緻性或對延遲不敏感的業務可以忽略,也可以采用Redis叢集方案做水準擴充。

2.讀到過期資料

  當主節點存儲大量設定逾時的資料時,如緩存資料,Redis内部需要維護過期資料删除政策,删除政策主要有兩種:惰性删除和定時删除。

  惰性删除:主節點每次處理讀取指令時,都會檢查鍵是否逾時,如果逾時則執行del指令删除鍵對象,之後del指令也會異步發送給從節點。需要注意的是為了保證複制的一緻性,從節點自身永遠不會主動删除逾時資料,如圖6-21。

Redis之複制

  定時删除:Redis主節點在内部定時任務會循環采樣一定數量的鍵,當發現采樣的鍵過期時執行del指令,之後再同步給從節點,如圖6-22所示。

  如果此時資料大量逾時,主節點采樣速度跟不上過期速度且主節點沒有讀取過期鍵的操作,那麼從節點将無法收到del指令。這時在從節點上可以讀取到已經逾時的資料。Redis3.2版本解決了這個問題,從節點讀取資料之前會檢查鍵的過期時間來決定是否傳回資料,可以更新到3.2版本來規避這個問題。

Redis之複制

  3.從節點故障冋題

  對于從節點的故障問題,需要在用戶端維護可用從節點清單,當從節點故障時立刻切換到其他從節點或主節點上。這個過程類似上文提到的針對延遲過高的監控處理,需要開發人員改造用戶端類庫。

  綜上所出,使用Redis做讀寫分離存在一定的成本。Redis本身的性能非常高,開發人員在使用額外的從節點提升讀性能之前,盡量在主節點上做充分優化,比如解決慢查詢,持久化阻塞,合理應用資料結構等,當主節點優化空間不大時再考慮擴充。建議大家在做讀寫分離之前,可以考慮使用Redis Cluster等分布式解決方案,這樣不止擴充了讀性能還可以擴充寫性能和可支撐資料規模,并且一緻性和故障轉移也可以得到保證,對于用戶端的維護邏輯也相對容易。

3.2 主從配置不一緻

  主從配置不一緻是一個容易忽視的問題。對于有些配置主從之間是可以不一緻,比如:主節點關閉AOF在從節點開啟。但對于記憶體相關的配置必須要一緻,比如maxmemory,hash-max-ziplist-entries等參數。當配置的maxmemory從節點小于主節點,如果複制的資料量超過從節點maxmemory時,它會根據maxmemory-policy政策進行記憶體溢出控制,此時從節點資料已經丢失,但主從複制流程依然正常進行,複制偏移量也正常。修複這類問題也隻能手動進行全量複制。當壓縮清單相關參數不一緻時,雖然主從節點存儲的資料一緻但實際記憶體占用情況差異會比較大。

3.3 規避全量複制

  全量複制是一個非常消耗資源的操作,前面做了具體說明。是以如何規避全量複制是需要重點關注的運維點。下面我們對需要進行全量複制的場景逐個分析:

□ 第一次建立複制: 由于是第一次建立複制,從節點不包含任何主節點資料,是以須進行全量複制才能完成資料同步。對于這種情況全量複制無法避免。當對資料量較大且流量較高的主節點添加從節點時,建議在低峰時進行操作,或者盡量規避使用大資料量的Redis節點。

□ 節點運作ID不比對: 當主從複制關系建立後,從節點會儲存主節點的運作ID,如果此時主節點因故障重新開機,那麼它的運作ID會改變,從節點發現主節點運作ID不比對時,會認為自己複制的是一個新的主節點進而進行全量複制。對于這種情況應該從架構上規避,比如提供故障轉移功能。當主節點發生故障後,手動提升從節點為主節點或者采用支援自動故障轉移的哨兵或叢集方案。

□ 複制積壓緩沖區不足: 當主從節點網絡中斷後,從節點再次連上主節點時會發送psync {offset} {runld}指令請求部分複制,如果請求的偏移量不在主節點的積壓緩沖區内,則無法提供給從節點資料,是以部分複制會退化為全量複制。針對這種情況需要根據網絡中斷時長,寫指令資料量分析出合理的積壓緩沖區大小。網絡中斷一般有閃斷、機房割接、網絡分區等情況。這時網絡中斷的時長一般在分鐘級(net_break_time)。寫指令資料量可以統計高峰期主節點每秒info replication的master_repl_offset 內插補點擷取(write_size_per_minute)。積壓緩沖區預設為1MB,對于大流量場景顯然不夠,這時需要增大積壓緩沖區,保證 repl_backlog_size > net_break_time * write_size_per_minute,進而避免因複制積壓緩沖區不足造成的全量複制。

3.4 規避複制風暴

  複制風暴是指大量從節點對同一主節點或者對同一台機器的多個主節點短時間内發起全量複制的過程。複制風暴對發起複制的主節點或者機器造成大量開銷,導緻CPU、記憶體、帶寬消耗。是以我們應該分析出複制風暴發生的場景,提前采用合理的方式規避。規避方式有如下幾個。

1.單主節點複制風暴

  單主節點複制風暴一般發生在主節點挂載多個從節點的場景。當主節點重新開機恢複後,從節點會發起全量複制流程,這時主節點就會為從節點建立RDB快照,如果在快照建立完畢之前,有多個從節點都嘗試與主節點進行全量同步,那麼其他從節點将共享這份RDB快照。這點Redis做了優化,有效避免了建立多個快照。但是,同時向多個從節點發送RDB快照,可能使主節點的網絡帶寬消耗嚴重,造成主節點的延遲變大,極端情況會發生主從節點連接配接斷開,導緻複制失敗。

  解決方案首先可以減少主節點(master)挂載從節點(slave)的數量,或者采用樹狀複制結構,加人中間層從節點用來保護主節點,如圖 6-23所示。

Redis之複制

  2.單機器複制風暴

  由于Redis的單線程架構,通常單台機器會部署多個Redis執行個體。當一台機器(machine)上同時部署多個主節點(master)時。如果這台機器出現故障或網絡長時間中斷,當它重新開機恢複後,會有大量從節點(slave)針對這台機器的主節點進行全量複制,會造成目前機器網絡帶寬耗盡。

  如何避免?方法如下:

□ 應該把主節點盡量分散在多台機器上,避免在單台機器上部署過多的主節點。

□ 當主節點所在機器故障後提供故障轉移機制,避免機器恢複後進行密集的全量複制。

4.總結

  1. 複制支援樹狀結構,從節點可以複制另一個從節點,實作一層層向下的複制流。

  2. Redis 2.8之後複制的流程分為: 全量複制和部分複制。全量複制需要同步全部主節點的資料集,大量消耗機器和網絡資源。而部分複制有效減少因網絡異常等原因造成的不必要全量複制情況。通過配置合理的複制積壓緩沖區盡量避免全量複制。

  3. 主從節點之間維護心跳和偏移量檢查機制,保證主從節點通信正常和資料一緻。

  4. Redis為了保證高性能複制過程是異步的,寫指令處理完後直接傳回給用戶端,不等待從節點複制完成。是以從節點資料集會有延遲情況。

  5. 當使用從節點用于讀寫分離時會存在資料延遲、過期資料、從節點可用性等問題,需要根據自身業務提前作出規避。

  6. 在運維過程中,主節點存在多個從節點或者一台機器上部署大量主節點的情況下,會有複制風暴的風險

作者:小家電維修

出處:https://www.cnblogs.com/lizexiong/

轉世燕還故榻,為你銜來二月的花。