一塊兒學Redis系列 - 複製

簡述

咱們知道Redis是一款優秀的內存數據庫,大多數場景下,咱們使用Redis做爲分佈式內存緩存使用,但也有部分高併發高性能場景,直接選用Redis存儲數據,這就對Redis的持久化及高可用提出了要求。html

Redis同步流程

每一個Redis master擁有一個replication ID,而且每一個master記錄一個offset而且在每一個byte數據流發給slave時保持遞增。redis

當slave鏈接到master時,slave利用PSYNC命令將其舊master的replication ID和當前處理到的offset發給master,若是master校驗沒有問題,master將只發送增量的部分。可是,若是master的backlog隊列中沒有足夠的緩存,或者slave在請求的是一個未知的歷史的offset,這時會發生一個全量同步。數據庫

所謂的全量同步,其實就是指的master節點上發生bgsave併產生一個RDB文件,與此同時申請一個buffer保存全部的新命令,當RDB文件生成完畢後,master會開始發送這個文件給slave,slave將其保存在本地磁盤並load這個文件到內存。而後master開始發送全部buffer裏面保存的命令給slave執行。緩存

這裏順便提一下SYNC命令,它與PSYNC的不一樣就是它不是部分的,它每次執行都是一個全量的同步,在後續版本已經不使用了,可是爲了兼容性,保留了命令行的調用方式。bash

聊聊Replication ID

實際上每一個節點擁有兩個replication IDs: main ID和secondary ID。 replication ID用來標記當前數據集的版本,每次一個實例做爲master啓動或者slave晉升做爲一個master,會生成一個新的replication ID。slave在鏈接master以後,會繼承master節點的replication ID,因此兩個實例若是擁有相同的replication ID,而且offset相同,則說明他們當前的數據相同。併發

那麼爲何每一個節點有兩個replication IDs呢,由於當發生failover,某個slave晉升爲master,此時其餘的slaves會以舊的replication ID來請求同步,這時晉升的slave,也就是新master是知道舊master的replication ID的,它用secondary ID做爲main ID,也就是繼承了舊master的replication ID,得以保障主從複製能夠繼續。less

聊聊複製積壓緩衝區 - backlog

先看一下配置異步

# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
#
# A client is immediately disconnected once the hard limit is reached, or if
# the soft limit is reached and remains reached for the specified number of
# seconds (continuously).
normal 0 0 0 
slave 256mb 64mb 60 
pubsub 32mb 8mb 60
複製代碼

意思是若是slave緩衝區的大小超過256mb,或該緩衝區的大小超過64mb而且持續60s,會馬上斷開slave的鏈接。slave在repl-timeout時間後發現沒有同步任何數據,從新發起PSYNC同步請求。分佈式

相關配置以下:函數

# The following option sets the replication timeout for:
#
# 1) Bulk transfer I/O during SYNC, from the point of view of slave.
# 2) Master timeout from the point of view of slaves (data, pings).
# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings).
#
# It is important to make sure that this value is greater than the value
# specified for repl-ping-slave-period otherwise a timeout will be detected
# every time there is low traffic between the master and the slave.
repl-timeout 60
# Set the replication backlog size. The backlog is a buffer that accumulates
# slave data when slaves are disconnected for some time, so that when a slave
# wants to reconnect again, often a full resync is not needed, but a partial
# resync is enough, just passing the portion of data the slave missed while
# disconnected.
#
# The bigger the replication backlog, the longer the time the slave can be
# disconnected and later be able to perform a partial resynchronization.
#
# The backlog is only allocated once there is at least a slave connected.
repl-backlog-size 1mb
repl-backlog-ttl 3600
複製代碼

查看當前Replication狀態

role:master
connected_slaves:1
slave0:ip=172.16.13.128,port=9720,state=online,offset=7885515926852,lag=1
master_replid:e44c50848619ddcf19dc140466519e8309727c81
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:7885516112507
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:7885515063932
repl_backlog_histlen:1048576
複製代碼

生產實踐建議

1)主節點的bgsave操做,會給主節點帶來一個短期的性能抖動,緣由Redis的主進程調用fork函數時是一個同步操做,主進程會等待fork函數返回才繼續執行後面的代碼,當內存較大時,fork的返回時間會變長。 優化:因爲fork函數的執行時間與內存使用量及機型有關,因此控制單個節點的內存大小就很關鍵,咱們的事件是控制單個節點的maxmemory爲8GB,這樣能夠有效控制bgsave的fork調用產生的性能抖動。 2)寫RDB文件時,會產生磁盤IO,也會帶來性能的消耗。 優化:對於從節點這個性能消耗能夠忽略,因此能夠關閉主節點的bgsave,並在從節點開啓。可是前面提到的全量同步時發生bgsave生成的RDB文件,在某些磁盤壓力大的系統下會致使糟糕的性能表現,因此在2.8.18版本之後,開始支持不須要落盤的全量同步模式。具體參考配置項:repl-diskless-sync及repl-diskless-sync-delay

相關技術簡介

bgsave

bgsave原理是使用操做系統的fork函數,產生一個子進程,子進程擁有當前主進程的內存快照(實際上不是複製了所有的內存內容而是基於COW),父進程經過COW(Copy On Write)技術操做內存,在修改前將修改的page頁進行拷貝,再行修改,這樣就不會影響子進程看到的內存內容,子進程就能夠異步的將內存中的數據序列化並寫入磁盤。

COW - Copy On Write

在複製一個對象的時候並非真正的把原先的對象複製到內存的另一個位置上,而是在新對象的內存映射表中設置一個指針,指向源對象的位置,並把那塊內存的Copy-On-Write位設置爲1.

這樣,在對新的對象執行讀操做的時候,內存數據不發生任何變更,直接執行讀操做;而在對新的對象執行寫操做時,將真正的對象複製到新的內存地址中,並修改新對象的內存映射表指向這個新的位置,並在新的內存位置上執行寫操做。

這個技術須要跟虛擬內存和分頁同時使用,好處就是在執行復制操做時由於不是真正的內存複製,而只是創建了一個指針,於是大大提升效率。但這不是一直成立的,若是在複製新對象以後,大部分對象都還須要繼續進行寫操做會產生大量的分頁錯誤,得不償失。因此COW高效的狀況只是在複製新對象以後,在一小部分的內存分頁上進行寫操做。

傳統的fork函數直接把全部資源複製給新的進程,效率很低下。 寫時拷貝在須要寫入時,數據纔會被複制,沒有數據寫入時,fork()的開銷實際只是複製父進程的頁表以及給子進程建立惟一的進程描述符。有數據要寫入前,會將將要改變的數據頁複製給子進程。

詳細參考:www.cnblogs.com/biyeymyhjob…

相關參考

redis.io/topics/pers… redis.io/topics/repl… www.cnblogs.com/lukexwang/p… www.cnblogs.com/biyeymyhjob…

相關文章
相關標籤/搜索