springboot redis 断线重连_Redis的主从复制是如何做的?复制过程中也会产生各种问题?...
如果Redis的讀寫請求量很大,那么單個實例很有可能承擔不了這么大的請求量,如何提高Redis的性能呢?你也許已經想到了,可以部署多個副本節點,業務采用讀寫分離的方式,把讀請求分擔到多個副本節點上,提高訪問性能。要實現讀寫分離,就必須部署多個副本,每個副本需要實時同步主節點的數據。
Redis也提供了完善的主從復制機制,使用非常簡單的命令,就可以構建一個多副本節點的集群。
同時,當主節點故障宕機時,我們可以把一個副本節點提升為主節點,提高Redis的可用性。可見,對于故障恢復,也依賴Redis的主從復制,它們都是Redis高可用的一部分。
這篇文章我們就來介紹一下Redis主從復制流程和原理,以及在復制過程中有可能產生的各種問題。
構建主從復制集群
假設我們現在有一個節點A,它經過寫入一段時間的數據寫入后,內存中保存了一些數據。
此時我們再部署一個節點B,需要讓節點B成為節點A的數據副本,并且之后與節點A保持實時同步,如何做呢?
Redis提供了非常簡單的命令:slaveof。我們只需要在節點B上執行以下命令,就可以讓節點B成為節點A的數據副本:
slaveof 節點A_host:節點A_port節點B就會自動與節點A建立數據同步,如果節點A的數據量不大,等待片刻,就能看到節點B擁有與節點A相同的數據,同時在節點A上產生的數據變更,也會實時同步到節點B上。
通過這樣簡單的方式,我們可以非常方便地構建一個master-slave集群,業務可以在master上進行寫入,在slave上讀取數據,實現讀寫分離,提高訪問性能。
那么主從節點的復制是如何進行的?下面我們就來分析其中的原理。
主從復制流程
為了方便下面講解,我們這里把節點A叫做master節點,節點B叫做slave節點。
當我們在slave上執行slaveof命令時,這個復制流程會經過以下階段:
- slave發送psync $runid $offset給master,請求同步數據
- master檢查slave發來的runid和offset參數,決定是發送全量數據還是部分數據
- 如果slave是第一次與master同步,或者master-slave斷開復制太久,則進行全量同步master在后臺生成RDB快照文件,通過網絡發給slaveslave接收到RDB文件后,清空自己本地數據庫slave加載RDB數據到內存中
- 如果master-slave之前已經建立過數據同步,只是因為某些原因斷開了復制,此時只同步部分數據master根據slave發來的數據位置offset,只發送這個位置之后的數據給slaveslave接收這些差異數據,更新自己的數據,與maser保持一致
- 之后master產生的寫入,都會傳播一份給slave,slave與master保持實時同步
下面分別介紹全量同步和部分同步的詳細流程。
全量同步
當我們在節點B上執行slaveof命令后,節點B會與節點A建立一個TCP連接,然后發送psync $runid $offset命令,告知節點A需要開始同步數據。
這兩個參數的具體含義如下:
- runid:master節點的唯一標識
- offset:slave需要從哪個位置開始同步數據
什么是runid?在啟動Redis實例時,Redis會為每個實例隨機分配一個長度為40位的十六進制字符串,用來標識實例的唯一性,也就是說,runid就是這個實例的唯一標識。
由于是第一次同步,slave并不知道master的runid,所以slave會r發送psync ? -1,表示需要全量同步數據。
master在收到slave發來的psync后,會給slave回復+fullsync $runid $offset,這個runid就是master的唯一標識,slave會記錄這個runid,用于后續斷線重連同步請求。
之后master會在后臺生成一個RDB快照文件。
RDB文件生成之后,master把這個RDB文件通過網絡發送給slave,slave收到RDB文件后,清空整個實例,然后加載這個RDB數據到內存中,此時slave擁有了與master接近一致的數據。
為什么是接近一致?因為master在生成RDB和slave加載RDB的過程是比較耗時的,在這個過程中,master產生新的寫入,這些新寫入的命令目前在slave上是沒有執行的。這些命令master如何與slave保持一致呢?
Redis會把這些增量數據寫入到一個叫做復制緩沖區(repl_baklog)的地方暫存下來,這個復制緩沖區是一個固定大小的隊列,由配置參數repl-backlog-size決定,默認1MB,可以通過配置文件修改它的大小。
由于是固定大小的隊列,所以如果這個緩沖區被寫滿,那么它之前的內容會被覆蓋掉。
注意:無論slave有多少個,master的復制緩沖區只有一份,它實際上就是暫存master最近寫入的命令,供多個slave部分同步時使用。
待slave加載RDB文件完成之后,master會把復制緩沖區的這些增量數據發送給slave,slave依次執行這些命令,就能保證與master擁有相同的數據。
之后master再收到的寫命令,會實時傳播給slave節點,slave與master執行同樣的命令,這樣slave就可以與master保持實時數據的同步。
部分同步
如果在復制過程中,因為網絡抖動或其他原因,導致主從連接斷開,等故障恢復時,slave是否需要重新同步master的數據呢?
在Redis的2.8版本之前,確實是這么干的,每次主從斷開復制,重新連接后,就會觸發一次全量數據的同步。
可見,這么做的代價是非常大的,而且耗時耗力。后來在Redis在這方面進行了改進,在2.8版本之后,Redis支持部分同步數據。
當主從斷開重新建立連接后,slave向master發送同步請求:psync $runid $offset,因為之前slave在第一次全量同步時,已經記錄下了master的runid,并且slave也知道目前自己復制到了哪個位置offset。
這時slave就會告知master,之前已經同步過數據了,這次只需要把offset這個位置之后的數據發送過來就可以了。
master收到psync命令之后,檢查slave發來的runid與自身的runid一致,說明之前已經同步過數據,這次只需要同步部分數據即可。
但是slave需要的offset之后的數據,master還保存著嗎?
前面我們介紹了master自身會有一個復制緩沖區(repl-backlog),這個緩沖區暫存了最近寫入的命令,同時記錄了這些命令的offset位置。此時master就會根據slave發來的這個offset在復制緩沖區中查詢是否還保留著這個位置之后的數據。
如果有,那么master給slave回復+continue,表示這次只同步部分數據。之后master把復制緩沖區offset之后的數據給slave即可,slave執行這些命令后就與master達到一致。
如果master復制緩沖區找不到offset之后的數據,說明斷開的時間太久,復制緩沖區的內容已經被新的內容覆蓋了,此時master只能觸發全量數據同步。
命令傳播
slave經過全量同步或部分同步后,之后master實時產生的寫入,是如何實時同步的?
很簡單,master每次執行完新的寫入命令后,也會把這個命令實時地傳播給slave,slave執行與master相同的操作,就可以實時與master保持一致。
需要注意的是,master傳播給slave的命令是異步執行的,也就是說在master上寫入后,馬上在slave上查詢是有可能查不到的,因為異步執行存在一定的延遲。
slave與master建立連接后,slave就屬于master的一個client,master會為每個client分配一個client output buffer,master和每個client通信都會先把數據寫入到這個內存buffer中,再通過網絡發送給這個client。
但是,由于這個buffer是占用Redis實例內存的,所以不能無限大。所以Redis提供了控制buffer大小的參數限制:
# 普通client buffer限制 client-output-buffer-limit normal 0 0 0# slave client buffer限制client-output-buffer-limit slave 256mb 64mb 60# pubsub client buffer限制client-output-buffer-limit pubsub 32mb 8mb 60這個參數的格式為:client-output-buffer-limit $type $hard_limit $soft_limit $soft_seconds,其含義為:如果client的buffer大小達到了hard_limit或在達到了soft_limit并持續了soft_seconds時間,那么Redis會強制斷開與client的連接。
對于slave的client,默認的限制是,如果buffer達到了256MB,或者達到64MB并持續了1分鐘,那么master就會強制斷開slave的連接。
這個配置的大小在某些場景下,也會影響到主從的數據同步,我們下面會具體介紹到。
心跳機制
在命令傳播階段,為了保證master-slave數據同步的穩定進行,Redis還設計了一些機制維護這個復制鏈路,這種機制主要通過心跳來完成,主要包括兩方面:
- master定時向slave發送ping,檢查slave是否正常
- slave定時向master發送replconf ack $offset,告知master自己復制的位置
在master這一側,master向slave發送ping的頻率由repl-ping-slave-period參數控制,默認10秒,它的主要作用是讓slave節點進行超時判斷,如果slave在規定時間內沒有收到master的心跳,slave會自動釋放與master的連接,這個時間由repl-timeout決定,默認60秒。
同樣,在slave這邊,它也會定時向master發送replconf ack $offset命令,頻率為每1秒一次,其中offset是slave當前復制到的數據偏移量,這么做的主要作用如下:
- 讓master檢測slave的狀態:如果master超過repl-timeout時間未收到slave的replconf ack $offset命令,則master主動斷開與slave的連接
- master檢測slave丟失的命令:master根據slave發送的offset并與自己對比,如果發現slave發生了數據丟失,master會重新發送丟失的數據,前提是master的復制緩沖區中還保留這些數據,否則會觸發全量同步
- 數據安全性保障:Redis提供了min-slaves-to-write和min-slaves-max-lag參數,用于保障master在不安全的情況下禁止寫入,min-slaves-to-write表示至少存在N個slave節點,min-slaves-max-lag表示slave延遲必須小于這個時間,那么master才會接收寫命令,否則master認為slave節點太少或延遲過大,這種情況下是數據不安全的,實現這個機制就依賴slave定時發送replconf ack $offset讓master知曉slave的情況,一般情況下,我們不會開啟這個配置,了解一下就好
可見,master和slave節點通過心跳機制共同維護它們之間數據同步的穩定性,并在同步過程中發生問題時可以及時自動恢復。
我們可以可以在master上執行info命令查看當前所有slave的同步情況:
role:master # redis的角色connected_slaves:1 # slave節點數slave0:ip=127.0.0.1,port=6480,state=online,offset=22475,lag=0 # slave信息、slave復制到的偏移位置、距離上一次slave發送心跳的時間間隔(秒)master_repl_offset:22475 # master當前的偏移量repl_backlog_active:1 # master有可用的復制緩沖區repl_backlog_size:1048576 # master復制緩沖區大小通過這些信息,我們能看到slave與master的數據同步情況,例如延遲了多大的數據,slave多久沒有發送心跳給master,以及master的復制緩沖區大小。
復制過程中的問題
在整個數據復制的過程中,故障是時有發生的,例如網絡延遲過大、網絡故障、機器故障等。
所以在復制過程中,有一些情況需要我們格外注意,必要時需要針對性進行參數配置的調整,否則同步過程中會發生很多意外問題。
主要問題分為以下幾個方面,下面依次來介紹。
主從斷開復制后,重新復制觸發了全量同步?
上面我們有提到,主從建立同步時,優先檢測是否可以嘗試只同步部分數據,這種情況就是針對于之前已經建立好了復制鏈路,只是因為故障導致臨時斷開,故障恢復后重新建立同步時,為了避免全量同步的資源消耗,Redis會優先嘗試部分數據同步,如果條件不符合,才會觸發全量同步。
這個判斷依據就是在master上維護的復制緩沖區大小,如果這個緩沖區配置的過小,很有可能在主從斷開復制的這段時間內,master產生的寫入導致復制緩沖區的數據被覆蓋,重新建立同步時的slave需要同步的offset位置在master的緩沖區中找不到,那么此時就會觸發全量同步。
如何避免這種情況?解決方案就是適當調大復制緩沖區repl-backlog-size的大小,這個緩沖區的大小默認為1MB,如果實例寫入量比較大,可以針對性調大此配置。
但這個配置不能調的無限大,因為它會額外占用內存空間。如果主從斷開復制的時間過長,那么觸發全量復制在所難免的,我們需要保證主從節點的網絡質量,避免頻繁斷開復制的情況發生。
master寫入量很大,主從斷開復制?
主從經過全量同步和部分同步后,之后master產生了寫入命令,會實時傳播給slave節點,如果在這個過程中發生了復制斷開,那么一定是在這個過程中產生了問題。我們來分析這個過程是如何處理命令傳播的。
上面我們也提到了,主從建立同步鏈路后,由于slave也是master的一個client,master會對每個client維護一個client output buffer,master產生寫命令執行完成后,會把這個命令寫入到這個buffer中,然后等待Redis的網絡循環事件把buffer中數據通過Socket發送給slave,發送成功后,master釋放buffer中的內存。
如果master在寫入量非常大的情況下,可能存在以下情況會導致master的client output buffer內存持續增長:
- 主從節點機器存在一定網絡延遲(例如機器網卡負載比較高),master無法及時的把數據發送給slave
- slave由于一些原因無法及時處理master發來的命令(例如開啟了AOF并實時刷盤,磁盤IO負載高)
當遇到上面情況時,master的client output buffer持續增長,直到觸發默認配置的閾值限制client-output-buffer-limit slave 256mb 64mb 60,那么master則會把這個slave連接強制斷開,這就會導致復制中斷。
之后slave重新發送復制請求,但是以上原因可能依舊存在,經過一段時間后又產生上述問題,主從連接再次被斷開,周而復始,主從頻繁斷開鏈接,無法正常復制數據。
解決方案是,適當調大client-output-buffer-limit的閾值,并且解決slave寫入慢的情況,保證master發給slave的數據可以很快得處理完成,這樣才能避免頻繁斷開復制的問題。
添加slave節點,master發生阻塞?
當主從建立同步進行全量同步數據時,master會fork出一個子進程,掃描全量數據寫入到RDB文件中。
這個fork操作,并不是沒有代價的。fork在創建子進程時,需要父進程拷貝一份內存頁表給子進程,如果master占用的內存過大,那么fork時需要拷貝的內存頁表也會比較耗時,在完成fork之前,Redis整個進程都會阻塞住,無法處理任何的請求,所以業務會發現Redis突然變慢了,甚至發生超時的情況。
我們可以執行info可以看到latest_fork_usec參數,單位微妙。這就是最后一次fork的耗時時間,我們可以根據這個時間來評估fork時間是否符合預期。
對于這種情況,可以優化方案如下:
- 一定保證機器擁有足夠的CPU資源和內存資源
- 單個Redis實例內存不要太大,大實例拆分成小實例
通過以上方式避免fork引發的父進程長時間阻塞問題。
主從全量同步數據時很慢?
之前我們已經了解到,主從全量復制會經過3個階段:
- master生成RDB文件
- master把RDB文件發送給slave
- slave清空數據庫,加載RDB文件到內存
如果發現全量同步數據非常耗時,我們根據以上階段來分析原因:
- master實例數據比較大,并且機器的CPU負載較高時,在生成RDB時耗大量CPU資源,導致RDB生成很慢
- master和slave的機器網絡帶寬被打滿,導致master發送給slave的RDB文件網絡傳輸時變慢
- slave機器內存不夠用,但開啟了swap機制,導致內存不足以加載RDB文件,數據被寫入到磁盤上,導致數據加載變慢
通過以上情況可以看出,主從復制時,會消耗CPU、內存、網卡帶寬各方面的資源,我們需要合理規劃服務器資源,保證資源的充足。并且針對大實例進行拆分,能避免很多復制中的問題。
總結
這篇文章我們介紹了Redis主從復制的流程和工作原理,以及在復制過程中可能引發的問題。
雖然搭建一個復制集群很簡單,但其中涉及到的細節也很多。Redis在復制過程也可能存在各種問題,我們需要設置合適的配置參數和合理運維Redis,才能保證Redis有穩定可用的副本數據,為我們的高可用提供基礎。
總結
以上是生活随笔為你收集整理的springboot redis 断线重连_Redis的主从复制是如何做的?复制过程中也会产生各种问题?...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GTA5义警怎么改装
- 下一篇: 自然灾害有哪些(最常见的自然灾害)