《三次給你聊清楚Redis》之簡單聊聊多機實現
四、多機實現

4.1、舊版復制
Redis 的復制功能分為同步(sync)和命令傳播(command propagate)兩個操作:
同步操作用于將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態。
命令傳播操作用于在主服務器的數據庫狀態被修改, 導致主從服務器的數據庫狀態出現不一致時, 讓主從服務器的數據庫重新回到一致狀態。
同步
當客戶端向從服務器發送?SLAVEOF?命令, 要求從服務器復制主服務器時, 從服務器首先需要執行同步操作, 也即是, 將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態。
從服務器對主服務器的同步操作需要通過向主服務器發送?SYNC?命令來完成, 以下是?SYNC?命令的執行步驟:
從服務器向主服務器發送?SYNC?命令。
收到?SYNC?命令的主服務器執行?BGSAVE?命令, 在后臺生成一個 RDB 文件, 并使用一個緩沖區記錄從現在開始執行的所有寫命令。
當主服務器的?BGSAVE?命令執行完畢時, 主服務器會將?BGSAVE?命令生成的 RDB 文件發送給從服務器, 從服務器接收并載入這個 RDB 文件, 將自己的數據庫狀態更新至主服務器執行?BGSAVE?命令時的數據庫狀態。
主服務器將記錄在緩沖區里面的所有寫命令發送給從服務器, 從服務器執行這些寫命令, 將自己的數據庫狀態更新至主服務器數據庫當前所處的狀態。
。
命令傳播
在同步操作執行完畢之后, 主從服務器兩者的數據庫將達到一致狀態, 但這種一致并不是一成不變的 —— 每當主服務器執行客戶端發送的寫命令時, 主服務器的數據庫就有可能會被修改, 并導致主從服務器狀態不再一致。
舉個例子, 假設一個主服務器和一個從服務器剛剛完成同步操作, 它們的數據庫都保存了相同的五個鍵?k1?至?k5
如果這時, 客戶端向主服務器發送命令?DEL?k3?, 那么主服務器在執行完這個?DEL?命令之后, 主從服務器的數據庫將出現不一致: 主服務器的數據庫已經不再包含鍵?k3?, 但這個鍵卻仍然包含在從服務器的數據庫里面
為了讓主從服務器再次回到一致狀態, 主服務器需要對從服務器執行命令傳播操作: 主服務器會將自己執行的寫命令 —— 也即是造成主從服務器不一致的那條寫命令 —— 發送給從服務器執行, 當從服務器執行了相同的寫命令之后, 主從服務器將再次回到一致狀態。
缺陷
。
其中可以明顯看出重新連接主服務器之后,SYNC命令創建包含k1-k10089的RDB文件。而事實上只需要再同步斷線后的k10087-k10089即可。SYNC的“全同步”對于從服務來說是不必要的。
SYNC命令非常消耗資源,原因有三點:
1)主服務器執行BGSAVE命令生成RDB文件,這個生成過程會大量消耗主服務器資源(CPU、內存和磁盤I/O資源)
2)主服務器需要將自己生成的RBD文件發送給從從服務器,這個發送操作會消耗主從服務器大量的網絡資源(帶寬與流量)
3)接收到RDB文件你的從服務器需要載入RDB文件,載入期間從服務器會因為阻塞而導致沒辦法處理命令請求。
4.2新版復制
sync雖然解決了數據同步問題,但是在數據量比較大情況下,從庫斷線從來依然采用全量復制機制,無論是從數據恢復、寬帶占用來說,sync所帶來的問題還是很多的。于是redis從2.8開始,引入新的命令psync。
psync有兩種模式:完整重同步和部分重同步。
部分重同步主要依賴三個方面來實現,依次介紹。
offset(復制偏移量):
主庫和從庫分別各自維護一個復制偏移量(可以使用info replication查看),用于標識自己復制的情況:
在主庫中代表主節點向從節點傳遞的字節數,在從庫中代表從庫同步的字節數。
每當主庫向從節點發送N個字節數據時,主節點的offset增加N
從庫每收到主節點傳來的N個字節數據時,從庫的offset增加N。
因此offset總是不斷增大,這也是判斷主從數據是否同步的標志,若主從的offset相同則表示數據同步量,不通則表示數據不同步。
replication backlog buffer(復制積壓緩沖區):
復制積壓緩沖區是一個固定長度的FIFO隊列,大小由配置參數repl-backlog-size指定,默認大小1MB。
需要注意的是該緩沖區由master維護并且有且只有一個,所有slave共享此緩沖區,其作用在于備份最近主庫發送給從庫的數據。
在主從命令傳播階段,主節點除了將寫命令發送給從節點外,還會發送一份到復制積壓緩沖區,作為寫命令的備份。
除了存儲最近的寫命令,復制積壓緩沖區中還存儲了每個字節相應的復制偏移量,由于復制積壓緩沖區固定大小先進先出的隊列,所以它總是保存的是最近redis執行的命令。
所以,重連服務器后,從服務器會發送自己的復制偏移量offset給主服務器,
如果offset偏移量之后的數據仍然存在于復制擠壓緩沖區,就執行部分重同步操作。
相反,執行完整重同步操作。
run_id(服務器運行的唯一ID)
每個redis實例在啟動時候,都會隨機生成一個長度為40的唯一字符串來標識當前運行的redis節點,查看此id可通過命令info server查看。
當主從復制在初次復制時,主節點將自己的runid發送給從節點,從節點將這個runid保存起來,當斷線重連時,從節點會將這個runid發送給主節點。主節點根據runid判斷能否進行部分復制:
如果從節點保存的runid與主節點現在的runid相同,說明主從節點之前同步過,主節點會更具offset偏移量之后的數據判斷是否執行部分復制,如果offset偏移量之后的數據仍然都在復制積壓緩沖區里,則執行部分復制,否則執行全量復制;
如果從節點保存的runid與主節點現在的runid不同,說明從節點在斷線前同步的redis節點并不是當前的主節點,只能進行全量復制;
psync流程:
復制
客戶端向服務器端發送:SLAVEOF
1、設置主服務器的地址和端口
存到masterhost和mastterport兩個屬性里之后,向客戶端發送ok,然后開始復制工作。
2、建立套接字鏈接
從服務器根據命令設置的地址和端口,創建鏈接,并且為這個套接字創建一個專門處理復制工作的文件事件處理器。
主服務器也會為套接字創建相應的客戶端狀態,并且把從服務器當作一個客戶端來對待。
3、發送ping命令(檢查)
檢查套接字狀態是否正常
檢查主服務器是否能正確處理請求。(如果不能,就重連)
4、身份認證
5、發送端口信息
從服務器向主服務器發送信息,主服務器記錄。
6、同步
從服務器向主服務器發送psync命令。(主服務器也成為從服務器的客戶端,因為主服務器會發送寫命令給從服務器)
7、命令傳播
完成同步后,進入傳播階段,主服務器一直發送寫命令,從服務器一直接受,保證和主服務器一致。
心跳檢測
默認一秒一次,從服務器向主服務器發送命令:REPLCONF ACK
三個作用:
檢測網絡連接狀態:如果主服務器一秒沒收到命令,就說明出問題了
輔助實現min-slaves配置:min-slaves-to-write 3? ?min-slaves-max-log 10:當從服務器小于3個或延遲都大于10,主服務器拒絕寫命令。
檢測命令丟失:如果命令丟失,主服務器會發現偏移量不一樣,然后它就會根據偏移量,去積壓緩沖區找到缺少的數據并發給從服務器。
4.3、哨兵
4.3.1什么是哨兵機制
Redis的哨兵(sentinel)?系統用于管理/多個?Redis?服務器,該系統執行以下三個任務:
·????????監控:?哨兵(sentinel)?會不斷地檢查你的Master和Slave是否運作正常。
·????????提醒:當被監控的某個?Redis出現問題時,?哨兵(sentinel)?可以通過?API?向管理員或者其他應用程序發送通知。
·????????自動故障遷移:當一個Master不能正常工作時,哨兵(sentinel)?會開始一次自動故障遷移操作,它會將失效Master的其中一個Slave升級為新的Master,?并讓失效Master的其他Slave改為復制新的Master;?當客戶端試圖連接失效的Master時,集群也會向客戶端返回新Master的地址,使得集群可以使用Master代替失效Master。
例如下圖所示:
在Server1 掉線后:
升級Server2 為新的主服務器:
4.3.2、哨兵模式修改配置
實現步驟:
1.拷貝到etc目錄
cp sentinel.conf? /usr/local/redis/etc
2.修改sentinel.conf配置文件
sentinel monitor mymast? 192.168.110.133 6379 1? #主節點 名稱 IP 端口號 選舉次數
sentinel auth-pass mymaster 123456
3. 修改心跳檢測 5000毫秒
sentinel down-after-milliseconds mymaster 5000
4.sentinel parallel-syncs mymaster 2 --- 做多多少合格節點
5. 啟動哨兵模式
./redis-server /usr/local/redis/etc/sentinel.conf --sentinel &
1)Sentinel(哨兵) 進程是用于監控 Redis 集群中 Master 主服務器工作的狀態
2)在 Master 主服務器發生故障的時候,可以實現 Master 和 Slave 服務器的切換,保證系統的高可用(High Availability)
工作方式
1)每個 Sentinel(哨兵)進程以每秒鐘一次的頻率向整個集群中的 Master 主服務器,Slave 從服務器以及其他 Sentinel(哨兵)進程發送一個 PING 命令。
2. 如果一個實例(instance)距離最后一次有效回復 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個實例會被 Sentinel(哨兵)進程標記為主觀下線。
3. 如果一個 Master 主服務器被標記為主觀下線,則正在監視這個 Master 主服務器的所有 Sentinel(哨兵)進程要以每秒一次的頻率確認 Master 主服務器的確進入了主觀下線狀態。
4. 當有足夠數量的 Sentinel(哨兵)進程(大于等于配置文件指定的值)在指定的時間范圍內確認 Master 主服務器進入了主觀下線狀態, 則Master 主服務器會被標記為客觀下線(ODOWN)。
5. 在一般情況下, 每個 Sentinel(哨兵)進程會以每 10 秒一次的頻率向集群中的所有Master 主服務器、Slave 從服務器發送 INFO 命令。
6. 當 Master 主服務器被 Sentinel(哨兵)進程標記為客觀下線時,Sentinel(哨兵)進程向下線的 Master 主服務器的所有 Slave 從服務器發送 INFO 命令的頻率會從 10 秒一次改為每秒一次。
7. 若沒有足夠數量的 Sentinel(哨兵)進程同意 Master 主服務器下線, Master 主服務器的客觀下線狀態就會被移除。若 Master 主服務器重新向 Sentinel(哨兵)進程發送 PING 命令返回有效回復,Master 主服務器的主觀下線狀態就會被移除。
哨兵(sentinel)?的一些設計思路和zookeeper非常類似
我們從啟動并初始化說起
4.3.3啟動并初始化 Sentinel
啟動一個 Sentinel 可以使用命令:
$ redis-sentinel /path/to/your/sentinel.conf
或者命令:
$ redis-server /path/to/your/sentinel.conf --sentinel
當一個 Sentinel 啟動時, 它需要執行以下步驟:
初始化服務器。
首先, 因為 Sentinel 本質上只是一個運行在特殊模式下的 Redis 服務器, 所以啟動 Sentinel 的第一步, 就是初始化一個普通的 Redis 服務器.
不過, 因為 Sentinel 執行的工作和普通 Redis 服務器執行的工作不同, 所以 Sentinel 的初始化過程和普通 Redis 服務器的初始化過程并不完全相同。
比如說, 普通服務器在初始化時會通過載入 RDB 文件或者 AOF 文件來還原數據庫狀態, 但是因為 Sentinel 并不使用數據庫, 所以初始化 Sentinel 時就不會載入 RDB 文件或者 AOF 文件。
將普通 Redis 服務器使用的代碼替換成 Sentinel 專用代碼。
第二個步驟就是將一部分普通 Redis 服務器使用的代碼替換成 Sentinel 專用代碼。
比如說, 普通 Redis 服務器使用?redis.h/REDIS_SERVERPORT?常量的值作為服務器端口:
#define REDIS_SERVERPORT 6379
而 Sentinel 則使用?sentinel.c/REDIS_SENTINEL_PORT?常量的值作為服務器端口:
#define REDIS_SENTINEL_PORT 26379
為什么在 Sentinel 模式下, Redis 服務器不能執行諸如?SET?、?DBSIZE?、?EVAL?等等這些命令 —— 因為服務器根本沒有在命令表中載入這些命令。
初始化 Sentinel 狀態。
在應用了 Sentinel 的專用代碼之后, 接下來, 服務器會初始化一個?sentinel.c/sentinelState?結構(后面簡稱“Sentinel 狀態”), 這個結構保存了服務器中所有和 Sentinel 功能有關的狀態 (服務器的一般狀態仍然由?redis.h/redisServer?結構保存):
struct sentinelState { // 當前紀元,用于實現故障轉移 uint64_t current_epoch; // 保存了所有被這個 sentinel 監視的主服務器 // 字典的鍵是主服務器的名字 // 字典的值則是一個指向 sentinelRedisInstance 結構的指針 dict *masters; // 是否進入了 TILT 模式? int tilt; // 目前正在執行的腳本的數量 int running_scripts; // 進入 TILT 模式的時間 mstime_t tilt_start_time; // 最后一次執行時間處理器的時間 mstime_t previous_time; // 一個 FIFO 隊列,包含了所有需要執行的用戶腳本 list *scripts_queue; } sentinel;
初始化 Sentinel 狀態的?masters?屬性
Sentinel 狀態中的?masters?字典記錄了所有被 Sentinel 監視的主服務器的相關信息:
字典的鍵是被監視主服務器的名字。
而字典的值則是被監視主服務器對應的?sentinel.c/sentinelRedisInstance?結構。
每個?sentinelRedisInstance?結構代表一個被 Sentinel 監視的 Redis 服務器實例(instance), 這個實例可以是主服務器、從服務器、或者另外一個 Sentinel 。
實例結構包含的屬性非常多, 以下代碼展示了一部分屬性
typedef struct sentinelRedisInstance { // 標識值,記錄了實例的類型,以及該實例的當前狀態 int flags; // 實例的名字 // 主服務器的名字由用戶在配置文件中設置 // 從服務器以及 Sentinel 的名字由 Sentinel 自動設置 // 格式為 ip:port ,例如 "127.0.0.1:26379" char *name; // 實例的運行 ID char *runid; // 配置紀元,用于實現故障轉移 uint64_t config_epoch; // 實例的地址 sentinelAddr *addr; // SENTINEL down-after-milliseconds 選項設定的值 // 實例無響應多少毫秒之后才會被判斷為主觀下線(subjectively down) mstime_t down_after_period; // SENTINEL monitor
創建連向主服務器的網絡連接。
Sentinel 將成為主服務器的客戶端, 它可以向主服務器發送命令, 并從命令回復中獲取相關的信息。
對于每個被 Sentinel 監視的主服務器來說, Sentinel 會創建兩個連向主服務器的異步網絡連接:
一個是命令連接, 這個連接專門用于向主服務器發送命令, 并接收命令回復。
另一個是訂閱連接, 這個連接專門用于訂閱主服務器的?__sentinel__:hello?頻道。
為什么有兩個連接? 在 Redis 目前的發布與訂閱功能中, 被發送的信息都不會保存在 Redis 服務器里面, 如果在信息發送時, 想要接收信息的客戶 端不在線或者斷線, 那么這個客戶端就會丟失這條信息。 因此, 為了不丟失 __sentinel__:hello 頻道的任何信息, Sentinel 必須專門用一個訂閱連接來接收該頻道的信息。 而另一方面, 除了訂閱頻道之外, Sentinel 還又必須向主服務 器發送命令, 以此來與主服務器進行通訊, 所以 Sentinel 還 必須向主服務器創建命令連接。 并且因為 Sentinel 需要與多個實例創建多個網絡連接, 所以 Sentinel 使用的是異步連接。
接下來介紹 Sentinel 如何通過命令連接和訂閱連接與被監視主服務器進行通訊。
4.3.4、獲取服務器信息
sentinel默認每十秒鐘發送一次INFO命令給主服務器,并獲取信息:
1)關于主服務器本身的信息
2)主服務器屬下所有從服務器信息
sentinel發現主服務器有新的從服務器時,會創建相應的實例結構和命令連接,訂閱連接
4.3.5、給服務器發送消息
4.3.6、主觀下線
指的是單個Sentinel實例對服務器做出的下線判斷,即單個sentinel認為某個服務下線(有可能是接收不到訂閱,之間的網絡不通等等原因)。
如果服務器在down-after-milliseconds給定的毫秒數之內, 沒有返回 Sentinel 發送的 PING 命令的回復, 或者返回一個錯誤, 那么 Sentinel 將這個服務器標記為主觀下線(SDOWN )。
sentinel會以每秒一次的頻率向所有與其建立了命令連接的實例(master,從服務,其他sentinel)發ping命令,通過判斷ping回復是有效回復,還是無效回復來判斷實例時候在線(對該sentinel來說是“主觀在線”)。
sentinel配置文件中的down-after-milliseconds設置了判斷主觀下線的時間長度,如果實例在down-after-milliseconds毫秒內,返回的都是無效回復,那么sentinel回認為該實例已(主觀)下線,修改其flags狀態為SRI_S_DOWN。如果多個sentinel監視一個服務,有可能存在多個sentinel的down-after-milliseconds配置不同,這個在實際生產中要注意。
4.3.7、客觀下線
客觀下線(Objectively Down, 簡稱 ODOWN)指的是多個 Sentinel 實例在對同一個服務器做出 SDOWN 判斷, 并且通過 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服務器下線判斷,然后開啟failover。
客觀下線就是說只有在足夠數量的 Sentinel 都將一個服務器標記為主觀下線之后, 服務器才會被標記為客觀下線(ODOWN)。
只有當master被認定為客觀下線時,才會發生故障遷移。
當sentinel監視的某個服務主觀下線后,sentinel會詢問其它監視該服務的sentinel,看它們是否也認為該服務主觀下線,接收到足夠數量(這個值可以配置)的sentinel判斷為主觀下線,既任務該服務客觀下線,并對其做故障轉移操作。
sentinel通過發送 SENTINEL is-master-down-by-addr ip port current_epoch runid
(ip:主觀下線的服務id,port:主觀下線的服務端口,current_epoch:sentinel的紀元,runid:*表示檢測服務下線狀態,如果是sentinel 運行id,表示用來選舉領頭sentinel)
來詢問其它sentinel是否同意服務下線。
一個sentinel接收另一個sentinel發來的is-master-down-by-addr后,提取參數,根據ip和端口,檢測該服務時候在該sentinel主觀下線,并且回復is-master-down-by-addr,回復包含三個參數:down_state(1表示已下線,0表示未下線),leader_runid(領頭sentinal id),leader_epoch(領頭sentinel紀元)。
sentinel接收到回復后,根據配置設置的下線最小數量,達到這個值,既認為該服務客觀下線。
客觀下線條件只適用于主服務器: 對于任何其他類型的 Redis 實例, Sentinel 在將它們判斷為下線前不需要進行協商, 所以從服務器或者其他 Sentinel 永遠不會達到客觀下線條件。只要一個 Sentinel 發現某個主服務器進入了客觀下線狀態, 這個 Sentinel 就可能會被其他 Sentinel 推選出, 并對失效的主服務器執行自動故障遷移操作。
4.3.8、選舉大哥sentinel
一個redis服務被判斷為客觀下線時,多個監視該服務的sentinel協商,選舉一個領頭sentinel,對該redis服務進行故障轉移操作。選舉領頭sentinel遵循以下規則:
1)所有的sentinel都有公平被選舉成領頭的資格。
2)所有的sentinel都只有一次將某個sentinel選舉成領頭的機會(在一輪選舉中),一旦選舉,不能更改。
3)先到先得,一旦當前sentinel設置了領頭sentinel,以后要求設置sentinel為領頭請求都會被拒絕。
4)每個發現服務客觀下線的sentinel,都會要求其他sentinel將自己設置成領頭。
5)當一個sentinel(源sentinel)向另一個sentinel(目sentinel)發送is-master-down-by-addr ip port current_epoch runid命令的時候,runid參數不是*,而是sentinel運行id,就表示源sentinel要求目標sentinel選舉其為領頭。
6)源sentinel會檢查目標sentinel對其要求設置成領頭的回復,如果回復的leader_runid和leader_epoch為源sentinel,表示目標sentinel同意將源sentinel設置成領頭。
7)如果某個sentinel被半數以上的sentinel設置成領頭,那么該sentinel既為領頭。
8)如果在限定時間內,沒有選舉出領頭sentinel,暫定一段時間,再選舉。
為什么要選?
簡單來說,就是因為只能有一個sentinel節點去完成故障轉移。
sentinel is-master-down-by-addr這個命令有兩個作用,一是確認下線判定,二是進行領導者選舉。
過程:
1)每個做主觀下線的sentinel節點向其他sentinel節點發送上面那條命令,要求將它設置為領導者。
2)收到命令的sentinel節點如果還沒有同意過其他的sentinel發送的命令(還未投過票),那么就會同意,否則拒絕。
3)如果該sentinel節點發現自己的票數已經過半且達到了quorum的值,就會成為領導者
4)如果這個過程出現多個sentinel成為領導者,則會等待一段時間重新選舉。
4.3.9、轉移
1)挑一個新的主服務器
2)把其它從服務器的主服務器改成新的
3)把之前的主服務器改為新主服務器的從服務器
4.3.10、怎么挑新的主服務器
1)刪除所有下線服務器
2)刪除五秒內沒回復INOF命令的服務器
3)刪除數據舊的服務器(連接斷開超過down-after-millseconds*10)
4)根據優先級,選出最高的。
4.3.11、重點提煉
Sentinel 是一個特殊模式下的 Redis 服務器, 它使用了不同的命令表, 所以 Sentinel 能使用的命令和普通服務器不同。
Sentinel 會讀入用戶指定的配置文件, 為每個要被監視的主服務器創建相應的實例結構, 并創建連向主服務器的命令連接和訂閱連接, 其中命令連接用于向主服務器發送命令請求, 而訂閱連接則用于接收指定頻道的消息。
Sentinel 向主服務器發送?INFO?命令獲得屬下從服務器信息, 為這些從服務器創建實例結構、命令連接和訂閱連接。
默認 Sentinel 十秒一次向被監視的主服務器和從服務器發送?INFO?命令, 當主服務器處于下線狀態, 或者 Sentinel 正在對主服務器進行故障轉移操作時, Sentinel 向從服務器發送?INFO?命令的頻率會改為每秒一次。
對于監視同一個主服務器和從服務器的多個 Sentinel 來說, 它們會以每兩秒一次的頻率, 通過向被監視服務器的?__sentinel__:hello?頻道發送消息來向其他 Sentinel 宣告自己的存在。
每個 Sentinel 也會從?__sentinel__:hello?頻道中接收其他 Sentinel 發來的信息, 并根據這些信息為其他 Sentinel 創建相應的實例結構, 以及命令連接。
Sentinel 只會與主服務器和從服務器創建命令連接和訂閱連接, Sentinel 與 Sentinel 之間則只創建命令連接。
Sentinel 以每秒一次的頻率向實例(包括主服務器、從服務器、其他 Sentinel)發送?PING?命令, 并根據實例對?PING?命令的回復來判斷實例是否在線
當 Sentinel 將一個主服務器判斷為主觀下線時, 它會向同樣監視這個主服務器的其他 Sentinel 進行詢問, 看它們是否同意這個主服務器已經進入主觀下線狀態。
當 Sentinel 收集到足夠多的主觀下線投票之后, 它會將主服務器判斷為客觀下線, 并發起一次針對主服務器的故障轉移操作。
Redis 數據庫
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。