主頁 > 軟體設計 > redis系統學習三:主從復制的原理與注意事項

redis系統學習三:主從復制的原理與注意事項

2020-12-08 14:35:17 軟體設計

前言

在前面的兩篇文章中,分別介紹了Redis的記憶體模型和Redis的持久化,

在Redis的持久化中曾提到,Redis高可用的方案包括持久化、主從復制(及讀寫分離)、哨兵和集群,其中持久化側重解決的是Redis資料的單機備份問題(從記憶體到硬碟的備份);而主從復制則側重解決資料的多機熱備,此外,主從復制還可以實作負載均衡和故障恢復,

這篇文章中,將詳細介紹Redis主從復制的方方面面,包括:如何使用主從復制、主從復制的原理(重點是全量復制和部分復制、以及心跳機制)、實際應用中需要注意的問題(如資料不一致問題、復制超時問題、復制緩沖區溢位問題)、主從復制相關的配置(重點是repl-timeout、client-output-buffer-limit slave)等,


目錄

一、主從復制概述

二、如何使用主從復制

1. 建立復制

2. 實體

3. 斷開復制

三、主從復制的實作原理

1. 連接建立階段

2. 資料同步階段

3. 命令傳播階段

四、【資料同步階段】全量復制和部分復制

1. 全量復制

2. 部分復制

3. psync命令的執行

4. 部分復制演示

五、【命令傳播階段】心跳機制

1. 主->從:PING

2. 從->主:REPLCONF ACK

六、應用中的問題

1. 讀寫分離及其中的問題

2. 復制超時問題

3. 復制中斷問題

4. 各場景下復制的選擇及優化技巧

5. 復制相關的配置

6. 單機記憶體大小限制

7. info Replication

七、總結


一、主從復制概述

主從復制,是指將一臺Redis服務器的資料,復制到其他的Redis服務器,前者稱為主節點(master),后者稱為從節點(slave);資料的復制是單向的,只能由主節點到從節點,

默認情況下,每臺Redis服務器都是主節點;且一個主節點可以有多個從節點(或沒有從節點),但一個從節點只能有一個主節點,

主從復制的作用

主從復制的作用主要包括:

  1. 資料冗余:主從復制實作了資料的熱備份,是持久化之外的一種資料冗余方式,
  2. 故障恢復:當主節點出現問題時,可以由從節點提供服務,實作快速的故障恢復;實際上是一種服務的冗余,
  3. 負載均衡:在主從復制的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis資料時應用連接主節點,讀Redis資料時應用連接從節點),分擔服務器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis服務器的并發量,
  4. 高可用基石:除了上述作用以外,主從復制還是哨兵和集群能夠實施的基礎,因此說主從復制是Redis高可用的基礎,

二、如何使用主從復制

為了更直觀的理解主從復制,在介紹其內部原理之前,先說明我們需要如何操作才能開啟主從復制,

1. 建立復制

需要注意,主從復制的開啟,完全是在從節點發起的;不需要我們在主節點做任何事情,

從節點開啟主從復制,有3種方式:

(1)組態檔

在從服務器的組態檔中加入:slaveof <masterip> <masterport>

(2)啟動命令

redis-server啟動命令后加入 --slaveof <masterip> <masterport>

(3)客戶端命令

Redis服務器啟動后,直接通過客戶端執行命令:slaveof <masterip> <masterport>,則該Redis實體成為從節點,

上述3種方式是等效的,下面以客戶端命令的方式為例,看一下當執行了slaveof后,Redis主節點和從節點的變化,

2. 實體

準備作業:啟動兩個節點

方便起見,實驗所使用的主從節點是在一臺機器上的不同Redis實體,其中主節點監聽6379埠,從節點監聽6380埠;從節點監聽的埠號可以在組態檔中修改:

啟動后可以看到:

兩個Redis節點啟動后(分別稱為6379節點和6380節點),默認都是主節點,

建立復制

此時在6380節點執行slaveof命令,使之變為從節點:

觀察效果

下面驗證一下,在主從復制建立后,主節點的資料會復制到從節點中,

(1)首先在從節點查詢一個不存在的key:

(2)然后在主節點中增加這個key:

(3)此時在從節點中再次查詢這個key,會發現主節點的操作已經同步至從節點:

(4)然后在主節點洗掉這個key:

(5)此時在從節點中再次查詢這個key,會發現主節點的操作已經同步至從節點:

3. 斷開復制

通過slaveof <masterip> <masterport>命令建立主從復制關系以后,可以通過slaveof no one斷開,需要注意的是,從節點斷開復制后,不會洗掉已有的資料,只是不再接受主節點新的資料變化,

從節點執行slaveof no one后,列印日志如下所示;可以看出斷開復制后,從節點又變回為主節點,

主節點列印日志如下:

推薦一下自己的linuxC/C++交流群:973961276!整理了一些個人覺得比較好的學習書籍、視頻資料以及大廠面經視頻,有需要的小伙伴可以進群獲取哦!

三、主從復制的實作原理

上面一節中,介紹了如何操作可以建立主從關系;本小節將介紹主從復制的實作原理,

主從復制程序大體可以分為3個階段:連接建立階段(即準備階段)、資料同步階段、命令傳播階段;下面分別進行介紹,

1. 連接建立階段

該階段的主要作用是在主從節點之間建立連接,為資料同步做好準備,

步驟1:保存主節點資訊

從節點服務器內部維護了兩個欄位,即masterhost和masterport欄位,用于存盤主節點的ip和port資訊,

需要注意的是,slaveof是異步命令,從節點完成主節點ip和port的保存后,向發送slaveof命令的客戶端直接回傳OK,實際的復制操作在這之后才開始進行,

這個程序中,可以看到從節點列印日志如下:

步驟2:建立socket連接

從節點每秒1次呼叫復制定時函式replicationCron(),如果發現了有主節點可以連接,便會根據主節點的ip和port,創建socket連接,如果連接成功,則:

從節點:為該socket建立一個專門處理復制作業的檔案事件處理器,負責后續的復制作業,如接收RDB檔案、接收命令傳播等,

主節點:接收到從節點的socket連接后(即accept之后),為該socket創建相應的客戶端狀態,并將從節點看做是連接到主節點的一個客戶端,后面的步驟會以從節點向主節點發送命令請求的形式來進行,

這個程序中,從節點列印日志如下:

步驟3:發送ping命令

從節點成為主節點的客戶端之后,發送ping命令進行首次請求,目的是:檢查socket連接是否可用,以及主節點當前是否能夠處理請求,

從節點發送ping命令后,可能出現3種情況:

(1)回傳pong:說明socket連接正常,且主節點當前可以處理請求,復制程序繼續,

(2)超時:一定時間后從節點仍未收到主節點的回復,說明socket連接不可用,則從節點斷開socket連接,并重連,

(3)回傳pong以外的結果:如果主節點回傳其他結果,如正在處理超時運行的腳本,說明主節點當前無法處理命令,則從節點斷開socket連接,并重連,

在主節點回傳pong情況下,從節點列印日志如下:

步驟4:身份驗證

如果從節點中設定了masterauth選項,則從節點需要向主節點進行身份驗證;沒有設定該選項,則不需要驗證,從節點進行身份驗證是通過向主節點發送auth命令進行的,auth命令的引數即為組態檔中的masterauth的值,

如果主節點設定密碼的狀態,與從節點masterauth的狀態一致(一致是指都存在,且密碼相同,或者都不存在),則身份驗證通過,復制程序繼續;如果不一致,則從節點斷開socket連接,并重連,

步驟5:發送從節點埠資訊

身份驗證之后,從節點會向主節點發送其監聽的埠號(前述例子中為6380),主節點將該資訊保存到該從節點對應的客戶端的slave_listening_port欄位中;該埠資訊除了在主節點中執行info Replication時顯示以外,沒有其他作用,

2. 資料同步階段

主從節點之間的連接建立以后,便可以開始進行資料同步,該階段可以理解為從節點資料的初始化,具體執行的方式是:從節點向主節點發送psync命令(Redis2.8以前是sync命令),開始同步,

資料同步階段是主從復制最核心的階段,根據主從節點當前狀態的不同,可以分為全量復制和部分復制,下面會有一章專門講解這兩種復制方式以及psync命令的執行程序,這里不再詳述,

需要注意的是,在資料同步階段之前,從節點是主節點的客戶端,主節點不是從節點的客戶端;而到了這一階段及以后,主從節點互為客戶端,原因在于:在此之前,主節點只需要回應從節點的請求即可,不需要主動發請求,而在資料同步階段和后面的命令傳播階段,主節點需要主動向從節點發送請求(如推送緩沖區中的寫命令),才能完成復制,

3. 命令傳播階段

資料同步階段完成后,主從節點進入命令傳播階段;在這個階段主節點將自己執行的寫命令發送給從節點,從節點接收命令并執行,從而保證主從節點資料的一致性,

在命令傳播階段,除了發送寫命令,主從節點還維持著心跳機制:PING和REPLCONF ACK,由于心跳機制的原理涉及部分復制,因此將在介紹了部分復制的相關內容后單獨介紹該心跳機制,

延遲與不一致

需要注意的是,命令傳播是異步的程序,即主節點發送寫命令后并不會等待從節點的回復;因此實際上主從節點之間很難保持實時的一致性,延遲在所難免,資料不一致的程度,與主從節點之間的網路狀況、主節點寫命令的執行頻率、以及主節點中的repl-disable-tcp-nodelay配置等有關,

repl-disable-tcp-nodelay no:該配置作用于命令傳播階段,控制主節點是否禁止與從節點的TCP_NODELAY;默認no,即不禁止TCP_NODELAY,當設定為yes時,TCP會對包進行合并從而減少帶寬,但是發送的頻率會降低,從節點資料延遲增加,一致性變差;具體發送頻率與Linux內核的配置有關,默認配置為40ms,當設定為no時,TCP會立馬將主節點的資料發送給從節點,帶寬增加但延遲變小,

一般來說,只有當應用對Redis資料不一致的容忍度較高,且主從節點之間網路狀況不好時,才會設定為yes;多數情況使用默認值no,

四、【資料同步階段】全量復制和部分復制

在Redis2.8以前,從節點向主節點發送sync命令請求同步資料,此時的同步方式是全量復制;在Redis2.8及以后,從節點可以發送psync命令請求同步資料,此時根據主從節點當前狀態的不同,同步方式可能是全量復制或部分復制,后文介紹以Redis2.8及以后版本為例,

  1. 全量復制:用于初次復制或其他無法進行部分復制的情況,將主節點中的所有資料都發送給從節點,是一個非常重型的操作,
  2. 部分復制:用于網路中斷等情況后的復制,只將中斷期間主節點執行的寫命令發送給從節點,與全量復制相比更加高效,需要注意的是,如果網路中斷時間過長,導致主節點沒有能夠完整地保存中斷期間執行的寫命令,則無法進行部分復制,仍使用全量復制,

1. 全量復制

Redis通過psync命令進行全量復制的程序如下:

(1)從節點判斷無法進行部分復制,向主節點發送全量復制的請求;或從節點發送部分復制的請求,但主節點判斷無法進行部分復制;具體判斷程序需要在講述了部分復制原理后再介紹,

(2)主節點收到全量復制的命令后,執行bgsave,在后臺生成RDB檔案,并使用一個緩沖區(稱為復制緩沖區)記錄從現在開始執行的所有寫命令

(3)主節點的bgsave執行完成后,將RDB檔案發送給從節點;從節點首先清除自己的舊資料,然后載入接收的RDB檔案,將資料庫狀態更新至主節點執行bgsave時的資料庫狀態

(4)主節點將前述復制緩沖區中的所有寫命令發送給從節點,從節點執行這些寫命令,將資料庫狀態更新至主節點的最新狀態

(5)如果從節點開啟了AOF,則會觸發bgrewriteaof的執行,從而保證AOF檔案更新至主節點的最新狀態

下面是執行全量復制時,主從節點列印的日志;可以看出日志內容與上述步驟是完全對應的,

主節點的列印日志如下:

從節點列印日志如下圖所示:

其中,有幾點需要注意:從節點接收了來自主節點的89260個位元組的資料;從節點在載入主節點的資料之前要先將老資料清除;從節點在同步完資料后,呼叫了bgrewriteaof,

通過全量復制的程序可以看出,全量復制是非常重型的操作:

(1)主節點通過bgsave命令fork子行程進行RDB持久化,該程序是非常消耗CPU、記憶體(頁表復制)、硬碟IO的;關于bgsave的性能問題,可以參考 深入學習Redis(2):持久化

(2)主節點通過網路將RDB檔案發送給從節點,對主從節點的帶寬都會帶來很大的消耗

(3)從節點清空老資料、載入新RDB檔案的程序是阻塞的,無法回應客戶端的命令;如果從節點執行bgrewriteaof,也會帶來額外的消耗

2. 部分復制

由于全量復制在主節點資料量較大時效率太低,因此Redis2.8開始提供部分復制,用于處理網路中斷時的資料同步,

部分復制的實作,依賴于三個重要的概念:

(1)復制偏移量

主節點和從節點分別維護一個復制偏移量(offset),代表的是主節點向從節點傳遞的位元組數;主節點每次向從節點傳播N個位元組資料時,主節點的offset增加N;從節點每次收到主節點傳來的N個位元組資料時,從節點的offset增加N,

offset用于判斷主從節點的資料庫狀態是否一致:如果二者offset相同,則一致;如果offset不同,則不一致,此時可以根據兩個offset找出從節點缺少的那部分資料,例如,如果主節點的offset是1000,而從節點的offset是500,那么部分復制就需要將offset為501-1000的資料傳遞給從節點,而offset為501-1000的資料存盤的位置,就是下面要介紹的復制積壓緩沖區,

(2)復制積壓緩沖區

復制積壓緩沖區是由主節點維護的、固定長度的、先進先出(FIFO)佇列,默認大小1MB;當主節點開始有從節點時創建,其作用是備份主節點最近發送給從節點的資料,注意,無論主節點有一個還是多個從節點,都只需要一個復制積壓緩沖區,

在命令傳播階段,主節點除了將寫命令發送給從節點,還會發送一份給復制積壓緩沖區,作為寫命令的備份;除了存盤寫命令,復制積壓緩沖區中還存盤了其中的每個位元組對應的復制偏移量(offset),由于復制積壓緩沖區定長且是先進先出,所以它保存的是主節點最近執行的寫命令;時間較早的寫命令會被擠出緩沖區,

由于該緩沖區長度固定且有限,因此可以備份的寫命令也有限,當主從節點offset的差距過大超過緩沖區長度時,將無法執行部分復制,只能執行全量復制,反過來說,為了提高網路中斷時部分復制執行的概率,可以根據需要增大復制積壓緩沖區的大小(通過配置repl-backlog-size);例如如果網路中斷的平均時間是60s,而主節點平均每秒產生的寫命令(特定協議格式)所占的位元組數為100KB,則復制積壓緩沖區的平均需求為6MB,保險起見,可以設定為12MB,來保證絕大多數斷線情況都可以使用部分復制,

從節點將offset發送給主節點后,主節點根據offset和緩沖區大小決定能否執行部分復制:

  • 如果offset偏移量之后的資料,仍然都在復制積壓緩沖區里,則執行部分復制;
  • 如果offset偏移量之后的資料已不在復制積壓緩沖區中(資料已被擠出),則執行全量復制,

(3)服務器運行ID(runid)

每個Redis節點(無論主從),在啟動時都會自動生成一個隨機ID(每次啟動都不一樣),由40個隨機的十六進制字符組成;runid用來唯一識別一個Redis節點,通過info Server命令,可以查看節點的runid:

主從節點初次復制時,主節點將自己的runid發送給從節點,從節點將這個runid保存起來;當斷線重連時,從節點會將這個runid發送給主節點;主節點根據runid判斷能否進行部分復制:

  • 如果從節點保存的runid與主節點現在的runid相同,說明主從節點之前同步過,主節點會繼續嘗試使用部分復制(到底能不能部分復制還要看offset和復制積壓緩沖區的情況);
  • 如果從節點保存的runid與主節點現在的runid不同,說明從節點在斷線前同步的Redis節點并不是當前的主節點,只能進行全量復制,

3. psync命令的執行

在了解了復制偏移量、復制積壓緩沖區、節點運行id之后,本節將介紹psync命令的引數和回傳值,從而說明psync命令執行程序中,主從節點是如何確定使用全量復制還是部分復制的,

psync命令的執行程序可以參見下圖(圖片來源:《Redis設計與實作》):

(1)首先,從節點根據當前狀態,決定如何呼叫psync命令:

  • 如果從節點之前未執行過slaveof或最近執行了slaveof no one,則從節點發送命令為psync ? -1,向主節點請求全量復制;
  • 如果從節點之前執行了slaveof,則發送命令為psync <runid> <offset>,其中runid為上次復制的主節點的runid,offset為上次復制截止時從節點保存的復制偏移量,

(2)主節點根據收到的psync命令,及當前服務器狀態,決定執行全量復制還是部分復制:

  • 如果主節點版本低于Redis2.8,則回傳-ERR回復,此時從節點重新發送sync命令執行全量復制;
  • 如果主節點版本夠新,且runid與從節點發送的runid相同,且從節點發送的offset之后的資料在復制積壓緩沖區中都存在,則回復+CONTINUE,表示將進行部分復制,從節點等待主節點發送其缺少的資料即可;
  • 如果主節點版本夠新,但是runid與從節點發送的runid不同,或從節點發送的offset之后的資料已不在復制積壓緩沖區中(在佇列中被擠出了),則回復+FULLRESYNC <runid> <offset>,表示要進行全量復制,其中runid表示主節點當前的runid,offset表示主節點當前的offset,從節點保存這兩個值,以備使用,

4. 部分復制演示

在下面的演示中,網路中斷幾分鐘后恢復,斷開連接的主從節點進行了部分復制;為了便于模擬網路中斷,本例中的主從節點在局域網中的兩臺機器上,

網路中斷

網路中斷一段時間后,主節點和從節點都會發現失去了與對方的連接(關于主從節點對超時的判斷機制,后面會有說明);此后,從節點便開始執行對主節點的重連,由于此時網路還沒有恢復,重連失敗,從節點會一直嘗試重連,

主節點日志如下:

從節點日志如下:

網路恢復

網路恢復后,從節點連接主節點成功,并請求進行部分復制,主節點接收請求后,二者進行部分復制以同步資料,

主節點日志如下:

從節點日志如下:

五、【命令傳播階段】心跳機制

在命令傳播階段,除了發送寫命令,主從節點還維持著心跳機制:PING和REPLCONF ACK,心跳機制對于主從復制的超時判斷、資料安全等有作用,

1.主->從:PING

每隔指定的時間,主節點會向從節點發送PING命令,這個PING命令的作用,主要是為了讓從節點進行超時判斷,

PING發送的頻率由repl-ping-slave-period引數控制,單位是秒,默認值是10s,

關于該PING命令究竟是由主節點發給從節點,還是相反,有一些爭議;因為在Redis的官方檔案中,對該引數的注釋中說明是從節點向主節點發送PING命令,如下圖所示:

但是根據該引數的名稱(含有ping-slave),以及代碼實作,我認為該PING命令是主節點發給從節點的,相關代碼如下:

2. 從->主:REPLCONF ACK

在命令傳播階段,從節點會向主節點發送REPLCONF ACK命令,頻率是每秒1次;命令格式為:REPLCONF ACK {offset},其中offset指從節點保存的復制偏移量,REPLCONF ACK命令的作用包括:

(1)實時監測主從節點網路狀態:該命令會被主節點用于復制超時的判斷,此外,在主節點中使用info Replication,可以看到其從節點的狀態中的lag值,代表的是主節點上次收到該REPLCONF ACK命令的時間間隔,在正常情況下,該值應該是0或1,如下圖所示:

(2)檢測命令丟失:從節點發送了自身的offset,主節點會與自己的offset對比,如果從節點資料缺失(如網路丟包),主節點會推送缺失的資料(這里也會利用復制積壓緩沖區),注意,offset和復制積壓緩沖區,不僅可以用于部分復制,也可以用于處理命令丟失等情形;區別在于前者是在斷線重連后進行的,而后者是在主從節點沒有斷線的情況下進行的,

(3)輔助保證從節點的數量和延遲:Redis主節點中使用min-slaves-to-write和min-slaves-max-lag引數,來保證主節點在不安全的情況下不會執行寫命令;所謂不安全,是指從節點數量太少,或延遲過高,例如min-slaves-to-write和min-slaves-max-lag分別是3和10,含義是如果從節點數量小于3個,或所有從節點的延遲值都大于10s,則主節點拒絕執行寫命令,而這里從節點延遲值的獲取,就是通過主節點接收到REPLCONF ACK命令的時間來判斷的,即前面所說的info Replication中的lag值,

六、應用中的問題

1. 讀寫分離及其中的問題

在主從復制基礎上實作的讀寫分離,可以實作Redis的讀負載均衡:由主節點提供寫服務,由一個或多個從節點提供讀服務(多個從節點既可以提高資料冗余程度,也可以最大化讀負載能力);在讀負載較大的應用場景下,可以大大提高Redis服務器的并發量,下面介紹在使用Redis讀寫分離時,需要注意的問題,

(1)延遲與不一致問題

前面已經講到,由于主從復制的命令傳播是異步的,延遲與資料的不一致不可避免,如果應用對資料不一致的接受程度程度較低,可能的優化措施包括:優化主從節點之間的網路環境(如在同機房部署);監控主從節點延遲(通過offset)判斷,如果從節點延遲過大,通知應用不再通過該從節點讀取資料;使用集群同時擴展寫負載和讀負載等,

在命令傳播階段以外的其他情況下,從節點的資料不一致可能更加嚴重,例如連接在資料同步階段,或從節點失去與主節點的連接時等,從節點的slave-serve-stale-data引數便與此有關:它控制這種情況下從節點的表現;如果為yes(默認值),則從節點仍能夠回應客戶端的命令,如果為no,則從節點只能回應info、slaveof等少數命令,該引數的設定與應用對資料一致性的要求有關;如果對資料一致性要求很高,則應設定為no,

(2)資料過期問題

在單機版Redis中,存在兩種洗掉策略:

  • 惰性洗掉:服務器不會主動洗掉資料,只有當客戶端查詢某個資料時,服務器判斷該資料是否過期,如果過期則洗掉,
  • 定期洗掉:服務器執行定時任務洗掉過期資料,但是考慮到記憶體和CPU的折中(洗掉會釋放記憶體,但是頻繁的洗掉操作對CPU不友好),該洗掉的頻率和執行時間都受到了限制,

在主從復制場景下,為了主從節點的資料一致性,從節點不會主動洗掉資料,而是由主節點控制從節點中過期資料的洗掉,由于主節點的惰性洗掉和定期洗掉策略,都不能保證主節點及時對過期資料執行洗掉操作,因此,當客戶端通過Redis從節點讀取資料時,很容易讀取到已經過期的資料,

Redis 3.2中,從節點在讀取資料時,增加了對資料是否過期的判斷:如果該資料已過期,則不回傳給客戶端;將Redis升級到3.2可以解決資料過期問題,

(3)故障切換問題

在沒有使用哨兵的讀寫分離場景下,應用針對讀和寫分別連接不同的Redis節點;當主節點或從節點出現問題而發生更改時,需要及時修改應用程式讀寫Redis資料的連接;連接的切換可以手動進行,或者自己寫監控程式進行切換,但前者回應慢、容易出錯,后者實作復雜,成本都不算低,

(4)總結

在使用讀寫分離之前,可以考慮其他方法增加Redis的讀負載能力:如盡量優化主節點(減少慢查詢、減少持久化等其他情況帶來的阻塞等)提高負載能力;使用Redis集群同時提高讀負載能力和寫負載能力等,如果使用讀寫分離,可以使用哨兵,使主從節點的故障切換盡可能自動化,并減少對應用程式的侵入,

2. 復制超時問題

主從節點復制超時是導致復制中斷的最重要的原因之一,本小節單獨說明超時問題,下一小節說明其他會導致復制中斷的問題,

超時判斷意義

在復制連接建立程序中及之后,主從節點都有機制判斷連接是否超時,其意義在于:

(1)如果主節點判斷連接超時,其會釋放相應從節點的連接,從而釋放各種資源,否則無效的從節點仍會占用主節點的各種資源(輸出緩沖區、帶寬、連接等);此外連接超時的判斷可以讓主節點更準確的知道當前有效從節點的個數,有助于保證資料安全(配合前面講到的min-slaves-to-write等引數),

(2)如果從節點判斷連接超時,則可以及時重新建立連接,避免與主節點資料長期的不一致,

判斷機制

主從復制超時判斷的核心,在于repl-timeout引數,該引數規定了超時時間的閾值(默認60s),對于主節點和從節點同時有效;主從節點觸發超時的條件分別如下:

(1)主節點:每秒1次呼叫復制定時函式replicationCron(),在其中判斷當前時間距離上次收到各個從節點REPLCONF ACK的時間,是否超過了repl-timeout值,如果超過了則釋放相應從節點的連接,

(2)從節點:從節點對超時的判斷同樣是在復制定時函式中判斷,基本邏輯是:

  • 如果當前處于連接建立階段,且距離上次收到主節點的資訊的時間已超過repl-timeout,則釋放與主節點的連接;
  • 如果當前處于資料同步階段,且收到主節點的RDB檔案的時間超時,則停止資料同步,釋放連接;
  • 如果當前處于命令傳播階段,且距離上次收到主節點的PING命令或資料的時間已超過repl-timeout值,則釋放與主節點的連接,

主從節點判斷連接超時的相關源代碼如下:

/* Replication cron function, called 1 time per second. */

void replicationCron(void) {

    static long long replication_cron_loops = 0;



    /* Non blocking connection timeout? */

    if (server.masterhost &&

        (server.repl_state == REDIS_REPL_CONNECTING ||

         slaveIsInHandshakeState()) &&

         (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)

    {

        redisLog(REDIS_WARNING,"Timeout connecting to the MASTER...");

        undoConnectWithMaster();

    }



    /* Bulk transfer I/O timeout? */

    if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER &&

        (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)

    {

        redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value.");

        replicationAbortSyncTransfer();

    }



    /* Timed out master when we are an already connected slave? */

    if (server.masterhost && server.repl_state == REDIS_REPL_CONNECTED &&

        (time(NULL)-server.master->lastinteraction) > server.repl_timeout)

    {

        redisLog(REDIS_WARNING,"MASTER timeout: no data nor PING received...");

        freeClient(server.master);

    }



    //此處省略無關代碼……



    /* Disconnect timedout slaves. */

    if (listLength(server.slaves)) {

        listIter li;

        listNode *ln;

        listRewind(server.slaves,&li);

        while((ln = listNext(&li))) {

            redisClient *slave = ln->value;

            if (slave->replstate != REDIS_REPL_ONLINE) continue;

            if (slave->flags & REDIS_PRE_PSYNC) continue;

            if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)

            {

                redisLog(REDIS_WARNING, "Disconnecting timedout slave: %s",

                    replicationGetSlaveName(slave));

                freeClient(slave);

            }

        }

    }



    //此處省略無關代碼……



}


需要注意的坑  

下面介紹與復制階段連接超時有關的一些實際問題:

(1)資料同步階段:在主從節點進行全量復制bgsave時,主節點需要首先fork子行程將當前資料保存到RDB檔案中,然后再將RDB檔案通過網路傳輸到從節點,如果RDB檔案過大,主節點在fork子行程+保存RDB檔案時耗時過多,可能會導致從節點長時間收不到資料而觸發超時;此時從節點會重連主節點,然后再次全量復制,再次超時,再次重連……這是個悲傷的回圈,為了避免這種情況的發生,除了注意Redis單機資料量不要過大,另一方面就是適當增大repl-timeout值,具體的大小可以根據bgsave耗時來調整,

(2)命令傳播階段:如前所述,在該階段主節點會向從節點發送PING命令,頻率由repl-ping-slave-period控制;該引數應明顯小于repl-timeout值(后者至少是前者的幾倍),否則,如果兩個引數相等或接近,網路抖動導致個別PING命令丟失,此時恰巧主節點也沒有向從節點發送資料,則從節點很容易判斷超時,

(3)慢查詢導致的阻塞:如果主節點或從節點執行了一些慢查詢(如keys *或者對大資料的hgetall等),導致服務器阻塞;阻塞期間無法回應復制連接中對方節點的請求,可能導致復制超時,

3. 復制中斷問題

主從節點超時是復制中斷的原因之一,除此之外,還有其他情況可能導致復制中斷,其中最主要的是復制緩沖區溢位問題,

復制緩沖區溢位

前面曾提到過,在全量復制階段,主節點會將執行的寫命令放到復制緩沖區中,該緩沖區存放的資料包括了以下幾個時間段內主節點執行的寫命令:bgsave生成RDB檔案、RDB檔案由主節點發往從節點、從節點清空老資料并載入RDB檔案中的資料,當主節點資料量較大,或者主從節點之間網路延遲較大時,可能導致該緩沖區的大小超過了限制,此時主節點會斷開與從節點之間的連接;這種情況可能引起全量復制->復制緩沖區溢位導致連接中斷->重連->全量復制->復制緩沖區溢位導致連接中斷……的回圈,

復制緩沖區的大小由client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默認值為client-output-buffer-limit slave 256MB 64MB 60,其含義是:如果buffer大于256MB,或者連續60s大于64MB,則主節點會斷開與該從節點的連接,該引數是可以通過config set命令動態配置的(即不重啟Redis也可以生效),

當復制緩沖區溢位時,主節點列印日志如下所示:

需要注意的是,復制緩沖區是客戶端輸出緩沖區的一種,主節點會為每一個從節點分別分配復制緩沖區;而復制積壓緩沖區則是一個主節點只有一個,無論它有多少個從節點,

4. 各場景下復制的選擇及優化技巧

在介紹了Redis復制的種種細節之后,現在我們可以來總結一下,在下面常見的場景中,何時使用部分復制,以及需要注意哪些問題,

(1)第一次建立復制

此時全量復制不可避免,但仍有幾點需要注意:如果主節點的資料量較大,應該盡量避開流量的高峰期,避免造成阻塞;如果有多個從節點需要建立對主節點的復制,可以考慮將幾個從節點錯開,避免主節點帶寬占用過大,此外,如果從節點過多,也可以調整主從復制的拓撲結構,由一主多從結構變為樹狀結構(中間的節點既是其主節點的從節點,也是其從節點的主節點);但使用樹狀結構應該謹慎:雖然主節點的直接從節點減少,降低了主節點的負擔,但是多層從節點的延遲增大,資料一致性變差;且結構復雜,維護相當困難,

(2)主節點重啟

主節點重啟可以分為兩種情況來討論,一種是故障導致宕機,另一種則是有計劃的重啟,

主節點宕機

主節點宕機重啟后,runid會發生變化,因此不能進行部分復制,只能全量復制,

實際上在主節點宕機的情況下,應進行故障轉移處理,將其中的一個從節點升級為主節點,其他從節點從新的主節點進行復制;且故障轉移應盡量的自動化,后面文章將要介紹的哨兵便可以進行自動的故障轉移,

安全重啟:debug reload

在一些場景下,可能希望對主節點進行重啟,例如主節點記憶體碎片率過高,或者希望調整一些只能在啟動時調整的引數,如果使用普通的手段重啟主節點,會使得runid發生變化,可能導致不必要的全量復制,

為了解決這個問題,Redis提供了debug reload的重啟方式:重啟后,主節點的runid和offset都不受影響,避免了全量復制,

如下圖所示,debug reload重啟后runid和offset都未受影響:

但debug reload是一柄雙刃劍:它會清空當前記憶體中的資料,重新從RDB檔案中加載,這個程序會導致主節點的阻塞,因此也需要謹慎,

(3)從節點重啟

從節點宕機重啟后,其保存的主節點的runid會丟失,因此即使再次執行slaveof,也無法進行部分復制,

(4)網路中斷

如果主從節點之間出現網路問題,造成短時間內網路中斷,可以分為多種情況討論,

第一種情況:網路問題時間極為短暫,只造成了短暫的丟包,主從節點都沒有判定超時(未觸發repl-timeout);此時只需要通過REPLCONF ACK來補充丟失的資料即可,

第二種情況:網路問題時間很長,主從節點判斷超時(觸發了repl-timeout),且丟失的資料過多,超過了復制積壓緩沖區所能存盤的范圍;此時主從節點無法進行部分復制,只能進行全量復制,為了盡可能避免這種情況的發生,應該根據實際情況適當調整復制積壓緩沖區的大小;此外及時發現并修復網路中斷,也可以減少全量復制,

第三種情況:介于前述兩種情況之間,主從節點判斷超時,且丟失的資料仍然都在復制積壓緩沖區中;此時主從節點可以進行部分復制,

5. 復制相關的配置

這一節總結一下與復制有關的配置,說明這些配置的作用、起作用的階段,以及配置方法等;通過了解這些配置,一方面加深對Redis復制的了解,另一方面掌握這些配置的方法,可以優化Redis的使用,少走坑,

配置大致可以分為主節點相關配置、從節點相關配置以及與主從節點都有關的配置,下面分別說明,

(1)與主從節點都有關的配置

首先介紹最特殊的配置,它決定了該節點是主節點還是從節點:

1) slaveof <masterip> <masterport>:Redis啟動時起作用;作用是建立復制關系,開啟了該配置的Redis服務器在啟動后成為從節點,該注釋默認注釋掉,即Redis服務器默認都是主節點,

2) repl-timeout 60:與各個階段主從節點連接超時判斷有關,見前面的介紹,

(2)主節點相關配置

1) repl-diskless-sync no:作用于全量復制階段,控制主節點是否使用diskless復制(無盤復制),所謂diskless復制,是指在全量復制時,主節點不再先把資料寫入RDB檔案,而是直接寫入slave的socket中,整個程序中不涉及硬碟;diskless復制在磁盤IO很慢而網速很快時更有優勢,需要注意的是,截至Redis3.0,diskless復制處于實驗階段,默認是關閉的,

2) repl-diskless-sync-delay 5:該配置作用于全量復制階段,當主節點使用diskless復制時,該配置決定主節點向從節點發送之前停頓的時間,單位是秒;只有當diskless復制打開時有效,默認5s,之所以設定停頓時間,是基于以下兩個考慮:(1)向slave的socket的傳輸一旦開始,新連接的slave只能等待當前資料傳輸結束,才能開始新的資料傳輸 (2)多個從節點有較大的概率在短時間內建立主從復制,

3) client-output-buffer-limit slave 256MB 64MB 60:與全量復制階段主節點的緩沖區大小有關,見前面的介紹,

4) repl-disable-tcp-nodelay no:與命令傳播階段的延遲有關,見前面的介紹,

5) masterauth <master-password>:與連接建立階段的身份驗證有關,見前面的介紹,

6) repl-ping-slave-period 10:與命令傳播階段主從節點的超時判斷有關,見前面的介紹,

7) repl-backlog-size 1mb:復制積壓緩沖區的大小,見前面的介紹,

8) repl-backlog-ttl 3600:當主節點沒有從節點時,復制積壓緩沖區保留的時間,這樣當斷開的從節點重新連進來時,可以進行部分復制;默認3600s,如果設定為0,則永遠不會釋放復制積壓緩沖區,

9) min-slaves-to-write 3與min-slaves-max-lag 10:規定了主節點的最小從節點數目,及對應的最大延遲,見前面的介紹,

(3)從節點相關配置

1) slave-serve-stale-data yes:與從節點資料陳舊時是否回應客戶端命令有關,見前面的介紹,

2) slave-read-only yes:從節點是否只讀;默認是只讀的,由于從節點開啟寫操作容易導致主從節點的資料不一致,因此該配置盡量不要修改,

6. 單機記憶體大小限制

在 深入學習Redis(2):持久化 一文中,講到了fork操作對Redis單機記憶體大小的限制,實際上在Redis的使用中,限制單機記憶體大小的因素非常之多,下面總結一下在主從復制中,單機記憶體過大可能造成的影響:

(1)切主:當主節點宕機時,一種常見的容災策略是將其中一個從節點提升為主節點,并將其他從節點掛載到新的主節點上,此時這些從節點只能進行全量復制;如果Redis單機記憶體達到10GB,一個從節點的同步時間在幾分鐘的級別;如果從節點較多,恢復的速度會更慢,如果系統的讀負載很高,而這段時間從節點無法提供服務,會對系統造成很大的壓力,

(2)從庫擴容:如果訪問量突然增大,此時希望增加從節點分擔讀負載,如果資料量過大,從節點同步太慢,難以及時應對訪問量的暴增,

(3)緩沖區溢位:(1)和(2)都是從節點可以正常同步的情形(雖然慢),但是如果資料量過大,導致全量復制階段主節點的復制緩沖區溢位,從而導致復制中斷,則主從節點的資料同步會全量復制->復制緩沖區溢位導致復制中斷->重連->全量復制->復制緩沖區溢位導致復制中斷……的回圈,

(4)超時:如果資料量過大,全量復制階段主節點fork+保存RDB檔案耗時過大,從節點長時間接收不到資料觸發超時,主從節點的資料同步同樣可能陷入全量復制->超時導致復制中斷->重連->全量復制->超時導致復制中斷……的回圈,

此外,主節點單機記憶體除了絕對量不能太大,其占用主機記憶體的比例也不應過大:最好只使用50%-65%的記憶體,留下30%-45%的記憶體用于執行bgsave命令和創建復制緩沖區等,

7. info Replication

在Redis客戶端通過info Replication可以查看與復制相關的狀態,對于了解主從節點的當前狀態,以及解決出現的問題都會有幫助,

主節點:

從節點:

對于從節點,上半部分展示的是其作為從節點的狀態,從connectd_slaves開始,展示的是其作為潛在的主節點的狀態,

info Replication中展示的大部分內容在文章中都已經講述,這里不再詳述,

七、總結

下面回顧一下本文的主要內容:

1、主從復制的作用:宏觀的了解主從復制是為了解決什么樣的問題,即資料冗余、故障恢復、讀負載均衡等,

2、主從復制的操作:即slaveof命令,

3、主從復制的原理:主從復制包括了連接建立階段、資料同步階段、命令傳播階段;其中資料同步階段,有全量復制和部分復制兩種資料同步方式;命令傳播階段,主從節點之間有PING和REPLCONF ACK命令互相進行心跳檢測,

4、應用中的問題:包括讀寫分離的問題(資料不一致問題、資料過期問題、故障切換問題等)、復制超時問題、復制中斷問題等,然后總結了主從復制相關的配置,其中repl-timeout、client-output-buffer-limit slave等對解決Redis主從復制中出現的問題可能會有幫助,

主從復制雖然解決或緩解了資料冗余、故障恢復、讀負載均衡等問題,但其缺陷仍很明顯:故障恢復無法自動化;寫操作無法負載均衡;存盤能力受到單機的限制;這些問題的解決,需要哨兵和集群的幫助,我將在后面的文章中介紹,歡迎關注,


好了,文章就寫到這吧,最后推薦一下不錯的c/c++ linux服務器后臺服務器高級架構師課程,每天晚上八點都會有直播,學習編程的朋友不妨點一下免費報名,上課的時候會有通知,有時間的時候就可以去聽聽哦

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/231598.html

標籤:其他

上一篇:位元組/騰訊/京東面試經驗的Java程式員分享面試中的突破點

下一篇:vc++自動斷開wifi功能

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more