Redis原理(Replication、Sentinel、Twemproxy、RedisCluster)
Redis的主從複製
Redis複製工作原理:
1.如果設定了一個Slave,無論是第一次連線還是重新連線到Master,它都會發出一個SYNC命令;設定Slave可以是在配置檔案新增slaveof 主IP 埠,然後帶上配置檔案啟動server,還可以是啟動服務後通過命令的方式設定,slaveof 主IP 埠來實現。
2.當Master收到SYNC命令後,會做兩件事:
a)Master執行BGSAVE,即在後臺儲存資料到磁碟(rdb快照檔案);是通過子程序進行的
b)Master同時將新收到的寫入和修改資料集的命令存入緩衝區(非查詢類);
3.當Master在後臺把資料儲存到快照檔案完成後,Master會把這個快照檔案傳送給Slave,而Slave則把記憶體清空後,載入該檔案到記憶體中;
4.而Master也會把此前收集到緩衝區的命令,通過Redis命令協議形式轉發給Slave,Slave執行這些命令,實現和Master的同步;
5.Master/Slave此後會不斷通過非同步方式進行命令的同步,達到最終資料的同步一致;
6.需要注意的是Master和Slave之間一旦發生重連都會引發全量同步操作,也就是命令緩衝也會重新建立。但在2.8版本以後,也可能是部分同步操作。
部分複製。2.8開始,當Master和Slave之間的連線斷開後,他們之間可以採用持續複製處理方式代替全量同步。Master端為複製流維護一個記憶體緩衝區(in-memory backlog),記錄最近傳送的複製流命令;同時,Master和Slave之間都維護一個複製偏移量(replication offset)和當前Master伺服器ID(Master run id)。當網路斷開,Slave嘗試重連時:
a.如果MasterID相同(即仍然是斷網前的Master伺服器),並且從斷開時到當前時刻的歷史命令依然在Master的記憶體緩衝區中存在,則Master會將缺失的這段時間的所有命令傳送給Slave執行,然後複製工作就可以繼續執行了;
b.否則,依然需要全量複製操作;
Redis2.8的這個部分重同步特性會用到一個新增的PSYNC內部命令,而Redis2.8以前的舊版本只有SYNC命令,不過,只要從伺服器是Redis2.8或以上版本,它就會根據主伺服器的版本決定到底是使用PSYNC還是SYNC。
Redis複製機制
Slave端主要經歷四個階段:
第一階段:與master建立連線
第二階段:向master發起同步請求(SYNC)
第三階段:接收master發來的RDB資料
第四階段:載入RDB檔案
下面我們就通過一張圖來概述每一個階段中,slave究竟做了什麼:
Redis接收到slaveof master_host master_port 命令後並沒有馬上與master建立連線,而是當執行伺服器例行任務serverCron,發現自己正處於REDIS_REPL_CONNECT狀態,這時才真正的向master發起連線。
Master端主要做以下三件事:
1.傳送RDB檔案
當master接收到slave傳送的sync同步命令後的執行流程如下圖:
上圖看似分支複雜,但我們抓住以下幾點即可:
a.儲存RDB檔案是在一個子程序中進行的;
b.如果master已經在儲存RDB檔案,但是沒有客戶端正在等待這次BGSAVE,新新增的slave需要等到下次BGSAVE,而不能直接使用這次生成的RDB檔案;
c.master會定期檢查RDB檔案是否儲存完畢(時間事件serverCron);
接下來看一下master是如何給每一個slave傳送RDB檔案的:
master會為每個slave維護下列資訊:
a.repldboff:當前傳送RDB檔案的偏移
b.repldbsize:需要傳送的RDB檔案大小
c.replstate:當前slave狀態(等待發送資料?正在傳送資料?已經發送完資料?)
RDB檔案分兩部分發送:
a.檔案頭(用來告訴slave客戶端,你應該接收多少資料)
b.檔案體(RDB資料)
當向某個客戶端傳送完RDB檔案後,該客戶端狀態從REDIS_REPL_SEND_BULK狀態變為REDIS_REPL_ONLINE。
2.傳送保活命令PINGNG
3.傳送變更命令
master儲存RDB檔案是通過一個子程序進行的,所以master依然可以處理客戶端請求而不被阻塞,但是這也導致了在儲存RDB檔案期間,“鍵空間”可能發生變化,因此為了保證資料同步的一致性,master會在儲存RDB檔案期間,把接收到的這些可能變更資料庫的“鍵空間”的命令儲存下來,然後放到每個slave的回覆列表中,當RDB檔案傳送完master會發送這些回覆列表中的內容,並且在這之後,如果資料庫發生變更,master依然會把變更的命令追加到回覆列表傳送給slave,這樣就保證master和slave資料的一致性。由於在傳送完RDB檔案後,master會不定時的給slave傳送”變更“命令,可能過1s,也可能過1小時,所以為了防止slave無意義等待,master需要定時傳送”保活“命令PING,以此告訴slave,我還活著,不要中斷連線。
至此我們已經分析了主從複製過程,總結一下大致流程:
Redis-Sentinel
Redis-Sentinel是官方推薦的高可用(HA)解決方案,當用Redis做Master-slave的高可用方案時,加入Master宕機了,Redis本身(包括他的很多客戶端)都沒有實現自動進行主備切換,而Redis-Sentinel本身也是一個獨立執行的程序,它能夠監控多個master-slave叢集,發現master宕機後能進行自動切換。
它的主要功能有以下幾點:
- 不時地監控redis是否按照預期良好的執行;
- 如果發現某個redis節點執行出現狀況,能夠通知另外一個程序(例如它的客戶端);
- 能夠進行自動切換。當一個master節點不可用時,能夠選舉出master的多個slave(如果有超過一個slave的話)中的一個來作為新的master,其它的slave節點會將它所追隨的master的地址改為被提升為master的slave的新地址。
sentinel支援叢集
很顯然,只使用單個sentinel程序來監控redis叢集是不可靠的,當sentinel程序宕掉後(sentinel本身也是有單點問題,single-point-of-failure)整個集群系統將無法按照預期的方式執行。所以有必要將sentinel叢集化,這樣有幾個好處:- 即使有些sentinel程序宕掉了,依然可以進行redis叢集的主備切換;
- 如果只有一個sentinel程序,如果這個程序執行出錯,揮著是網路堵塞,那麼將無法實現redis叢集的主備切換;
- 如果有多個sentinel-redis的客戶端可以隨意的連線任何一個sentinel來獲得關於redis叢集的資訊;
Sentinel版本
Sentinel當前最新的穩定版本稱為Sentinel2.隨著redis2.8的安裝包一起發行。因為Sentinel1有很多Bug,所以強烈建議使用sentinel2。執行Sentinel
執行sentinel有兩種方式:- 第一種:redis-sentinel /path/to/sentinel.conf
- 第二種:redis-server /path/to/sentinel.conf --sentinel
Sentinel的配置
典型的配置項如下: sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 60000 sentinel failover-timeout mymaster 180000 sentinel parallels-syncs mymaster 1 sentinel monitor resque 192.168.1.3 6380 4 sentinel down-after-milliseconds resque 10000 sentinel failover-timeout resque 180000 sentinel parallels-syncs resque 5 上面的配置項配置了兩個名字分別為mymaster和resque的master,配置檔案只需要配置master的資訊就好了,不用配置slave的資訊,因為slave能夠自動檢測到(master節點會有關於slave的訊息)。需要注意的是,配置檔案在sentinel執行期間是會被動態修改的,例如當發生主備切換時,配置檔案的master會被修改為另一個slave。這樣,如果重啟,可以根據這個配置來恢復之前監控的redis叢集狀態。 sentinel monitor mymaster 127.0.0.1 6379 2 這一行代表sentinel監控的master叫做mymaster,地址為127.0.0.1:6379,行尾最後的一個2代表什麼意思呢?我們知道,網路是不可靠的,有時候一個sentinel會因為網路堵塞而誤以為一個master redis已經死掉了,當sentinel叢集模式,解決這個問題的方法就變得很簡單,只需要多個sentinel互相溝通來確認某個master是否真的死了,這個2代表,當叢集中有2個sentinel認為master死了,才能真正認為該master已經不可用了。(sentinel叢集中各個sentinel也有互相通訊,通過gossip協議)。 除了第一行的配置,剩下有一個統一模式: sentinel <option_name> <master_name> <option_value> 根據option_name解釋上面配置項:- down-after-milliseconds:sentinel會向master傳送心跳PING來確認master是否存活,如果master在“一定時間範圍”內不迴應PONG或者是回覆了一個錯誤訊息,那麼這個Sentinel會主管地認為這個master已經不可用了(subjectively down,簡稱SDOWN)。這個配置項就是配置這個時間,單位毫秒。不過需要注意的是這個時候sentinel並不會馬上進行failover主備切換,這個sentinel還需要參照叢集中其他的sentinel意見,當叢集中超過之前配置的數量的sentinel都認為這個master不可達,那麼才會客觀認為這個master死了(Objectively down,簡稱ODOWN)
- parallel-syncs:在發生failover主備切換時,這個選項用來指定最多可以有多少個slave同時對新的master進行同步,這個數字越小,完成failover時間越長,越大則意味著越多slave因為replication不可用。
Sentinel的“仲裁會”
前面我們談到,當一個master被sentinel叢集監控時,需要制定一個引數,指定當需要判決master不可用,並且進行failover時,所需要的sentinel數量,即票數。但是當failover被觸發時,failover並不會馬上進行,還需要sentinel中大多數sentinel授權後才可以進行failover,若是票數比大多數還要大,則詢問更多的sentinel。配置版本號
當一個sentinel被授權後,它將會獲得宕掉的master的一份最新的配置版本號,當failover執行結束以後,這個版本號將會被用於最新的配置。因為大多數sentinel都已經知道該版本號已經被要執行failover的sentinel拿走了,所以其他的sentinel都不能再去使用這個版本號。這意味著,每次failover都會附帶有一個獨一無二的版本號。而且sentinel叢集都會遵守一個規則:如果sentinel A推薦Sentinel B去執行failover,B會等待一段時間後,自行再去對同一個master執行failover,這個等待時間是通過failover-timeout配置項配置。那麼sentinel叢集中sentinel不會再同一時刻併發去failover同一個master,第一個如果失敗了,另外一個將會在一定時間內進行重新failover。配置傳播
一旦一個sentinel成功地對一個master進行了failover,它將會把關於master的最新配置通過廣播形式通知其他sentinel,更新配置。一個failover要想被成功執行,sentinel必須能夠向選為master的slave傳送SLAVE OF NO ONE命令,然後能夠通過INFO命令看到新master資訊。 新配在叢集中相互傳播的方式,就是為什麼我們需要當一個sentinel進行failover時必須被授權一個版本號的原因。每個sentinel使用##釋出/訂閱##方式持續的傳播master的配置版本資訊,配置傳播的##釋出/訂閱##管道是:__sentinel__:hello。SDOWN和ODOWN的更多細節
sentinel對於不可用有兩種不同看法,一個叫做主管不可用(ODOWN),另外一個叫做客官不可用(ODOWN)。SDOWN是sentinel自己主觀上檢測的關於master的狀態,ODOWN需要一定數量的sentinel達成一致意見才能認為一個master客觀上已經宕掉,各個sentinel之間通過命令SENTINEL is_master_down_by_addr來獲得其他sentinel對master的檢測結果。 從sentinel的角度來看,如果傳送了PING心跳後,在一定的時間內沒有收到合法的回覆,就達到了SDOWN的條件。這個時間通過is-master-down-after-milliseconds配置。 從SDOWN切換到ODOWN不需要任何一致性演算法,只需要一個gossip協議:如果一個sentinel收到了足夠多的sentinel發來訊息告訴他某個master已經down掉了,SDOWN狀態就會變成ODOWN。 ODOWN狀態只適用master。Sentinel之間和Slaves之間的自動發現機制
雖然sentinel叢集中各個sentinel都互相連線彼此來檢查對方的可用性以及互相傳送訊息。但是你不用在任何一個sentinel配置任何其他的sentinel的節點。因為sentinel利用了master的釋出/訂閱機制去自動發現其它也監控了統一master的sentinel節點。 通過向名為__sentinel__:hello的管道中傳送訊息來實現。 同樣,你也不需要在sentinel中配置某個master的所有slave地址,sentinel會通過詢問master來得到這些slave的地址。 每個sentinel通過向每個master和slave的釋出/訂閱頻道__sentinel__:hello每秒傳送一次訊息,來宣佈它的存在。每個sentinel也訂閱了每個master和slave的頻道__sentinel__:hello的內容,來發現未知的sentinel,當檢測到了新的sentinel,則將其加入到自身維護的master監控列表中。每個sentinel傳送的訊息中也包含了其當前維護的最新的master配置,如果發現自己的配置版本低於接收到的配置版本,則會用新的配置版本更新。網路隔離時的一致性
當某個master網路斷開,重新選主,此時sentinel對與這個redis的訪問會怎麼樣?不成功?還是拒絕? 當客戶端所連線的master被隔開了,但是它仍然訪問成功了,那麼就會出現錯誤,如何避免這種錯誤呢? 可以配置如下: min-slaves-to-write 1 min-slaves-max-lag 10 通過上面配置,當一個redis是master時,如果它不能向至少一個slave寫資料,它將會拒絕接受客戶端的寫請求。由於複製是非同步的,master無法向slave寫資料意味著slave要麼斷開了連線,要麼不在指定時間內向master傳送同步資料請求了,第二項指定了這個時間。Sentinel狀態持久化
sentinel的狀態會被持久化地寫入sentinel的配置檔案中,每次當收到一個新的配置時,或者新建立一個配置時,配置會被持久化到硬碟中,並帶上配置的版本戳。這意味著,可以安全的停止和重啟sentinel程序。無failover時的配置糾正
即使當前沒有failover正在進行,sentinel依然會使用當前的配置去設定監控的master。特別是:- 根據最新配置確認為slaves的節點卻聲稱自己是master(比如宕掉的master重新上線),這時他們會被重新配置為slave。
- 如果slaves連線了一個錯誤的master,將會被改正過來。
Slave選舉與優先順序
當一個sentinel準備好了要進行failover,並且收到了其他sentinel的授權,那麼就需要選舉出一個合適的slave來做為新的master。slave的選舉主要評估slave的以下幾個方面:
- 與master斷開連線的次數
- Slave的優先順序
- 資料複製的下標
- 程序ID
- sentinel首先會根據slaves的優先順序來進行排序,優先順序越小的排名越靠前。
- 如果優先順序相同,則檢視複製的下標,那個從master接收的複製數最多,哪個就靠前。
- 如果優先順序和下標都相同,就選擇程序ID較小的那個。
Sentinel和Redis身份驗證
當一個master配置為需要密碼才能連線時,客戶端和slave在連線時都需要提供密碼。 master通過requirepass設定自身的密碼,不提供密碼無法連線。slave通過masterauth來設定訪問master時的密碼。但是當使用了sentinel時,由於一個master可能會變成slave,一個slave也可能會變成master,所以需要同時設定上述兩項。Sentinel API
Sentinel預設執行在26379埠上。sentinel支援redis協議,所以可以使用redis-cli客戶端或者其他可用的客戶端來與sentinel通訊。 有兩種方式能夠與sentinel通訊:- 一種是直接使用客戶端向它發訊息
- 另外一種是使用釋出/訂閱sentinel事件,比如說failover,或者某個redis例項執行錯誤,等等。
Sentinel命令
sentinel支援合法的命令如下:- PING sentinel回覆PONG。
- SENTINEL masters顯示被監控的所有master以及他們的狀態
- SENTINEL master <master name>顯示指定master的資訊和狀態
- SENTINEL slaves <master name>顯示指定master的所有slave以及他們的狀態
- SENTINEL get-master-addr-by-name <master name>返回指定master的ip和埠,如果正在進行failover或者failover已經完成,將會顯示被提升為master的slave的IP和埠。
- SENTINEL reset <pattern> 重置名字匹配該正則表示式的所有的master的狀態資訊,清除其前面的狀態資訊,以及slaves資訊。
- SENTINEL failover <master name>強制sentinel執行failover,並且不需要得到其他sentinel的同意。但是failover後將最新的配置傳送給其他的sentinel。
動態修改Sentinel配置
從redis2.8.4開始,sentinel提供了一組API用來新增,刪除,修改master的配置。需要注意的是,如果你通過API修改了一個sentinel的配置,sentinel不會把修改的配置告訴其他sentinel。你需要自己手動地對多個sentinel傳送修改配置的命令。 以下是一些修改sentinel配置的命令:- SENTINEL MONITOR <name> <ip> <port> <quorum>這個命令告訴sentinel去監聽一個新的master
- SENTINEL REMOVE <name> 命令sentinel放棄對某個master的監聽
- SENTINEL SET <name> <option> <value> 這個命令很像Redis的CONFIG SET命令,用來改變指定master的配置。支援多個<option> <value>。例如以下例項:
- SENTINEL SET objects-chche-master down-after-milliseconds 1000
增加或刪除sentinel
由於sentinel自動發現機制,所以新增一個sentinel到你的叢集非常容易,你所需要做的只是監控到某個master上,然後新新增的sentinel就能獲得其他sentinel的資訊以及master所有的slave。如果你需要新增多個sentinel,建議一個接著一個新增,可以預防網路隔離帶來的問題。你可以每隔30秒新增一個sentinel。最後用SENTINEL MASTER mastername來檢查一下是否所有的sentinel都監控到了。
刪除一個sentinel就有點複雜了,因為sentinel永遠不會刪除一個已經存在過的sentinel,即使它已經與組織失去聯絡很久了。刪除一個sentinel,遵循以下步驟:
- 停止所需要刪除的sentinel
- 傳送一個SENTINEL RESET * 命令給所有的sentinel例項,如果你想要重置指定master上面的sentinel,只需要把*改為特定名字,注意,需要一個接著一個發,每次間隔不低於30秒。
刪除舊master或者不可達slave
sentinel永遠會記錄好一個master的slaves,即使slave已經與組織失聯好久了。這是很有用的,因為sentinel叢集必須有能力把一個恢復可用的slave進行重新配置。並且,failover後,失效的master將會被標記為新的master的一個slave,這樣的話,當它變得可用,就會從新的master上覆制資料。 有時候你想永久的刪除一個slave,你只需要傳送一個SENTINEL RESET master命令給所有的sentinels,他們會更新列表裡能夠正確複製master的slave。釋出/訂閱
客戶端可以向一個sentinel傳送訂閱某個頻道的事件的命令,當有特定的事件發生時,sentinel會通知所有訂閱的客戶端。需要注意的是客戶端只能訂閱,不能釋出。訂閱頻道的名字與事件的名字一致。例如,頻道名為sdown將會發布所有與SDOWN相關的訊息給訂閱者。如果想要訂閱所有訊息,只需要簡單的使用PSUBSCRIBE *。 以下是你可以收到的訊息的訊息格式,如果你訂閱了所有訊息的話。第一個單詞是頻道的名字,其他是資料的格式。 instance details格式為: <instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port> 如果這個redis是一個master,那麼@之後的訊息不會顯示。 +reset-master <instance details> -- 當master被重置時。 +slave <instance details> -- 當檢測到一個slave並新增進slave列表時。 +failover-state-reconf-slaves <instance details> -- Failover狀態變為reconf-slaves狀態時 +failover-detected <instance details> -- 當failover發生時 +slave-reconf-sent <instance details> -- sentinel傳送SLAVEOF命令把它重新配置時 +slave-reconf-inprog <instance details> -- slave被重新配置為另外一個master的slave,但資料複製還未發生時。 +slave-reconf-done <instance details> -- slave被重新配置為另外一個master的slave並且資料複製已經與master同步時。 -dup-sentinel <instance details> -- 刪除指定master上的冗餘sentinel時(當sentinel重新啟動時,可能會發生) +sentinel <instance details> -- 當master增加了一個sentinel時。 +sdown <instance details> -- 進入SDOWN狀態時。 -sdown <instance details> -- 離開SDOWN狀態時。 +odown <instance details> -- 進入ODOWN狀態時。 -odown <instance details> -- 離開ODOWN狀態時。 +new-epoch <instance details> -- 當前配置版本更新時。 +try-failover <instance details> -- 達到failover條件,正等待其他sentinel的選舉。 +elected-leader <instance details> -- 被選舉為去執行failover的時候。 +failover-state-select-slave <instance details> -- 開始要選擇一個slave當選新master時。 no-good-slave <instance details> -- 沒有合適的slave來擔當新master的時候。 selected-slave <instance details> -- 找到一個合適的slave當選新的master。 failover-state-send-slaveof-noone <instance details> -- 當把選擇為新的master的slave的身份進行切換的時候。 failover-end-for-timeout <instance details> -- failover由於超時而失敗時。 failover-end <instance details> -- failover成功完成時。 switch-master <master name> <oldip> <oldport> <newip> <newport> -- 當master的地址發生變化時,通常是客戶端最感興趣的訊息了。 +tilt -- 進入Tilt模式 -tilt -- 退出Tilt模式TILT模式
redis sentinel非常依賴系統時間,例如他會使用系統時間來判斷一個PING恢復用了多久。然而假如系統時間被修改了,或者是系統十分繁忙,或者是程序堵塞了。sentinel可能會出現執行不正常的情況。當系統的穩定性下降時,TILT模式是sentinel可以進入的一種保護模式。當進入TILT模式時,sentinel會繼續監控工作,但是它不會有任何其他動作,他不會迴應is-master-down-by-addr這樣的命令了,因為在TILT模式下,檢測失效節點的能力已經變得讓人不可信任。如果恢復正常,持續30秒,sentinel就會推出TILT模式。-BUSY狀態
該功能還未實現。當一個指令碼執行的時間超過配置的執行時間時,sentinel會返回一個-BUSY錯誤訊號。如果這件事發生在觸發一個failover之前,sentinel將會發送一個SCRIPT KILL命令,如果script是隻讀的話,就能成功執行。‘Twemproxy
Twemproxy簡介
twemproxy(又稱nutcracker)是一個輕量級的Redis和Memcached代理,主要用來減少對後端快取伺服器的連線數。另一方面它也是目前Redis分片管理的最好方案,屬於中間層代理進行分片管理的方案。它主要通過事件驅動模型來達到高併發,每收到一個請求,通過解析請求,傳送到後端服務,再等待迴應,傳送回請求方。主要涉及三個重要的結構:server,connection,message。 每一個server其實就是一個後端的快取服務程式,Twemproxy可以預先連線每個server或者不,根據接收到的請求具體分析出key,然後根據key來選擇適當的server,具體演算法可以根據配置檔案選擇。connection在在Twemproxy中非常重要,它分為三種類型的connection:proxy,client和server,也就是監聽的socket,客戶端連線的socket和連線後端的socket,其中proxy型別的工作比較簡單,就是接收到請求,然後產生一個client connection或者是server connection。message是連線建立後的訊息內容傳送載體,它支援pipeline效果,多個message屬於同一個conn,conn通過接收到的內容解析來發現幾個不同的message。 Twemproxy有以下幾個特點:- 真正實現了多階段處理多請求,採用單執行緒收發包,基於epoll事件驅動模型。
- 減少與redis的直接連線數量,自己建立並維護和後端server的長連線,保證長連線對於來自不同的client但去向同一server的複用。
- 支援redis pipelining request,將多個連線請求組成redis pipelining統一向redis請求。
- 自帶一致性hash演算法,能夠將資料自動分片到後端多個redis例項上;支援多種hash演算法,可以設定後端例項的權重,目前redis支援的hash演算法有:one_at_a_time、md5、crc16、crc32、fnv1_64、fnv1a_64、fnv1_32、fnv1a_32、hsieh、murmur、jenkins。
- 支援設定HashTag;通過設定HashTag可以自己設定將同一型別的key對映到同一例項上去。
- 支援失敗節點自動刪除;可以設定重新連線該節點的時間,還可以設定連線多少次之後刪除該節點;如果此server恢復正常,twemproxy又能識別此server,並恢復正常訪問。
- 支援大部分的redis命令,redis客戶端可以像訪問正常redis例項一樣訪問Twemproxy。但是不支援對多個值的操作,比如取sets的子交併補等,也不支援事務操作。
- 高效;對連線的管理採用epoll機制,內部資料傳輸採用“Zero Copy”技術,以提高執行效率。
- 支援狀態監控;可設定狀態監控IP和埠,訪問IP和埠可以得到一個json格式的狀態資訊串,可以設定監控資訊重新整理間隔時間。
Twemproxy的配置詳解
接下來通過講解配置檔案來進一步瞭解他的工作機制。 listen twemproxy的監聽埠,可以以ip:port或name:port的形式來書寫。 hash 可以選擇的key值的hash演算法:one_at_a_time、md5、crc16、crc32、fnv1_64、fnv1a_64、fnv1_32、fnv1a_32、hsieh、murmur、jenkins。 hash_tag 允許根據key的一個部分來計算key的hash值。hash_tag由兩個字元組成,一個是hash_tag的開始,另外一個是hash_tag的結束,在hash_tag的開始和結束之間是將用於計算hash值的部分,計算結果將會用於選擇伺服器。 distribution 存在三種可選的配置: ketama:ketama一致性hash演算法,會根據伺服器構造出一個hash ring,併為ring上的節點分配hash範圍。ketama的優勢在於單個節點新增、刪除之後,會很大程度上保持整個叢集快取的key值可以被重用。 modula:modula非常簡單,就是根據key值的hash值取模,根據取模的結果選擇對應的伺服器。 random:random是無論key值是什麼,都隨機的選擇一個伺服器作為key值操作的目標。 timeout 單位是毫秒,是連線到server的超時值,預設是永久等待。 backlog 監聽TCP的backlog(連線等待佇列)的長度,預設是512。 preconnect 是一個boolean值,指示twemproxy是否應該預連線pool中的server。預設為false。 redis 是一個Boolean值,用來識別到伺服器的通訊協議是redis還是memcached,預設是false。 server_connections 每個server可以被開啟的連線數,預設每個伺服器開一個連線。 auto_eject_hosts 是一個boolean值,用於控制twemproxy是否根據server的連線狀態重建群集。這個連線狀態是由server_failure_limit閾值來控制。預設false。 server_retry_timeout 單位是毫秒,控制伺服器連線的時間間隔,在auto_eject_host被設定為true時產生作用。預設是30000毫秒。 server_failure_limit 控制連線伺服器的次數,在auto_eject_host被設定為true的時候產生作用,預設是2。 servers 一個pool中的伺服器的地址、埠和權重的列表,包括一個可選的伺服器名字,如果提供伺服器名字,將會使用它決定server的次序。從而提供對應的一致性hash的hash ring。否則,將使用server被定義的次序。效能測試總結
以下對幾個要點進行了測試: 1)選擇合適的hash演算法,可以比較均衡的分配資料。 2)與直接訪問redis相比效率損失只有10%左右。 3)如果同時部署多個Twemproxy,配置檔案一致,則可以從任意一個讀取。 4)若是auto_eject_host設定為true,後端一臺Redis掛掉後,Twemproxy能夠自動摘除。恢復後,Twemproxy能夠自動識別、恢復並重新加入到Redis組中重新使用。但是twemproxy的每次群集重建有可能使得原來的資料hash不正確,造成訪問不到key值情況,只能人工重新分配。一致性hash演算法ketama所造成的錯誤率最低。 5)Redis掛掉後,後端資料是否丟失與Twemproxy無關。 6)如果要新增一臺Redis,Twemproxy需要重新啟動才能生效,則增加後資料分佈與原來的redis分佈無關,並且資料不會自動重新Reblance,需要人工單獨實現。 對於後面三點其實一句話總結就是如果節點無效,會自動刪除節點,重新分片;若新增節點必須重啟服務,重新分片;不管增還是刪,twemproxy都不支援對原有的資料進行hash遷移,如果專案需要保證資料完整,那麼就需要人工單獨實現。Redis Cluster
設計原則和初衷
1.效能:這是Redis賴以生存的看家本領,增加叢集功能後當然不能對效能產生太大影響,所以Redis採取了P2P而非Proxy、非同步複製、客戶端重定向等設計,而犧牲了部分一致性、實用性。 2.水平擴充套件:叢集的最重要的能力當然是擴充套件,文件中稱可以線性擴充套件到1000節點。 3.可用性:在Cluster推出之前,可用性要靠Sentinel保證。有了叢集之後也自動具有了Sentinel的監控和自動Failover能力。內部資料結構
Redis Cluster功能涉及三個核心的資料結構clusterState、clusterNode、clusterLink。這三個資料結構中最重要的屬性就是:clusterState.slots、clusterState.slots_to_keys和clusterNode.slots了,它們儲存了三種對映關係: clusterState:叢集狀態。- nodes:所有結點
- migrating_slots_to:遷出中的槽
- importing_slots_from:匯入中的槽
- slots_to_keys:槽中包含的所有key,用於遷移slot時獲得其包含的key
- slots:Slot所屬的結點,用於處理請求時判斷key所在的Slot是否自己負責
- slots:結點負責的所有slot,用於傳送Gossip訊息通知其他結點自己負責的slot。通過點陣圖方式儲存節省空間,16384/8恰好是2048位元組,所以槽總數16384不是隨意定的。
叢集連線
Redis叢集是一個網狀結構,每個節點都通過TCP連線跟其他每個節點連線。在一個有N個節點的叢集中,每個節點都有N-1個流出的TCP連線和N-1個流入的連線,這些TCP連線會永久保持,並不是按需建立的。該TCP連線使用二進位制協議進行通訊。節點之間使用Gossip協議來進行以下工作:a)傳播(propagate)關於叢集的資訊,以此來發現新的節點。 b)向其他節點發送PING資料包,以此來檢查目標節點是否正常運作。 c)在特定事件發生時,傳送叢集資訊。 除此之外,叢集連線還用於在叢集中釋出或訂閱資訊。
節點握手
節點總是在叢集連線埠接收連線,甚至會回覆收到的ping包,即使傳送ping包的節點是不可信的。然而如果某個節點不被認為是在叢集中,那麼所有你它發出的資料包都會被丟棄。只有在兩種方式下,一個節點才會認為另一個節點是叢集中的一部分:- 當一個節點使用MEET訊息介紹自己。一個meet訊息跟一個PING訊息完全一樣,但它會強制讓接收者接受傳送者為叢集中的一部分。只有在系統管理員使用CLUSTER MEET ip port命令時,節點才會傳送MEET訊息給其他節點。
- 一個已經被信任的節點能夠通過傳播gossip訊息讓另外一個節點被註冊為叢集中的一部分。也就是說A知道B,B知道C,那麼B會向A傳送C的gossip訊息。A就會把C當做叢集中一部分。這表示叢集能夠自動發現其他節點,但前提是有一個由系統管理員強制建立的信任關係。
叢集資訊
每個節點在叢集中由一個獨一無二的ID標識,該ID是一個十六進位制表示的160位隨機數,在節點第一次啟動時由/dev/urandom生成。節點會將它的ID儲存到配置檔案,只要這個配置檔案不被刪除,節點就會一直沿用這個ID。一個節點可以改變它的IP和埠號,而不改變節點ID。叢集可以自動識別出IP/埠號的變化,並將這一資訊通過Gossip協議廣播給其他節點知道。下面是每個節點的關聯資訊,並且節點會將這些資訊傳送給其他節點: a)節點所使用的IP地址和TCP埠號。 b)節點的標誌(flags)。 c)節點負責處理的雜湊槽。 d)節點最新一次使用叢集連線傳送PING資料包(packet)的時間。 e)節點最近一次在回覆中接收到PONG資料包的時間。 f)叢集將該節點標記為下線的時間。 g)該節點的從節點數量。 如果該節點是從節點的話,那麼它會記錄主節點的節點ID。如果這是一個主節點的話,那麼主節點ID這一欄的值為0000000。失效檢測
Redis叢集失效檢測是用來識別出大多數節點何時無法訪問某一個主節點或從節點。 每個節點都有一份跟其他已知節點相關的標識列表。其中有兩個標誌是用於失效檢測,分別是PFAIL和FAIL。PFAIL表示可能失效,這是一個非公認的失效型別。FAIL表示一個節點已經失效,而這個情況已經被大多數節點在,某段時間內確認過了。 PFAIL標識: 當一個節點在超過NODE_TIMEOUT時間後仍然無法訪問某個節點(傳送一個ping包已經等待了超過NODE_TIMEOUT時間,若是經過一半NODE_TIMEOUT時間還沒收到回覆,嘗試重新連線),那麼它會用PFAIL來標識這個不可達的節點。無論節點型別是什麼,主節點和從節點都能標識其他節點為PFAIL。 FAIL標識: 單獨一個PFAIL標識只是每個節點的一些關於其他節點的本地資訊,它不是為了起作用而使用的,也不足夠觸發從節點的提升。要讓一個節點被認為失效了,那需要讓PFAIL上升為FAIL狀態。前面提到過節點之間通過gossip訊息來互動隨機的已知節點的狀態資訊。最中每個節點都能收到一份其他每個節點標識。當下麵條件滿足時,PFAIL狀態升級為FAIL:- 某個節點A,標記另一個節點B為PFAIL。
- 節點A通過gossip欄位收集到的叢集中大部分主節點標識的B的狀態資訊。
- 大部分主節點標記B為PFAIL狀態,或者在NODE_TIMEOUT*FAIL_REPORT_VALIDITY_MULT這個時間內是處於PFAIL狀態。
- 標記節點B為FAIL。
- 向所有節點發送一個FAIL訊息。
- 節點已經恢復可達,並且它是一個從節點。在這種情況下,FAIL標識可以清除掉,因為從節點並沒有被故障轉移。
- 節點已經恢復可達,而且他是一個主節點,但經過了很長時間(N*NODE_TIMEOUT)後也沒有檢查到任何從節點被提升了。
叢集階段(Cluster epoch)
Redis叢集使用一個類似於木筏演算法(Raft algorithm)“術語”的概念。在Redis叢集中這個術語叫做階段(epoch),它是用來記錄事件的版本號,所以當有多個節點提供了衝突資訊的時候,另外的節點就可以通過這個狀態來了解哪個是最新的。currentEpoch是一個64bit的unsigned數,叢集中每個節點都在建立的時候設定了currentEpoch為0。當節點接收到來自其他的ping包和pong包的時候,如果傳送者的epoch大於該節點的epoch,那麼更新發送者的epoch為currentEpoch,所以最終所有節點都會支援最大的epoch。這個資訊在此處是用於,當一個節點的狀態發生變化的時候為了執行一些動作尋求其他節點的同意。配置階段(Configure epoch)
每一個主節點總是通過傳送ping包和pong包向別人宣傳他的configEpoch和一份表示他負責的雜湊槽的點陣圖。當一個新節點被建立時,主節點的configEpoch設為零。 從節點由於故障轉移事件被提升為主節點,為了取代它那失效的主節點,會把configEpoch設定為他贏得選舉時候的configEpoch值。configEpoch用於在不同節點提出不同配置資訊的時候解決衝突。從節點的選舉與提升
從節點的選舉與提升都是由從節點處理的,主節點會投票要提升哪個從節點。一個從節點的選舉是在主節點被至少一個具有稱為主節點必備條件的從節點標記為FAIL的狀態的時候發起的。 當滿足以下條件時,一個從節點可以發起選舉:- 該從節點的主節點處於FAIL狀態。
- 這個主節點負責的雜湊槽數目不為零。
- 從節點和主節點之間重複連線斷線不超過一段給定時間,這是為了保證資料的可靠性。
- 一個從節點想要被推選出來,那麼第一步應該是提高他的currentEpoch計數,並且向主節點們請求投票。
主節點回復從節點的投票請求
主節點接收來自於從節點的投票請求,要授予投票,必須滿足:- 在一個給定的時段裡(epoch),一個主節點只能投一次票,並且拒絕給以前時段投票:每個主節點都有一個lastVoteEpoch域,一旦認證請求資料包裡的currentEpoch小於它就拒絕投票。當響應請求,則更新值。
- 一個主節點投票給某個從節點當且僅當該從節點的主節點被標記為FAIL。
- 如果認證請求裡的currentEpoch小於主節點裡的currentEpoch,忽視。
- 主節點若已經為某個失效主節點的一個從節點投票後,在經過NODE_TIMEOUT*2時間之前不會為同一個失效的主節點的另一個從節點投票。這並不是嚴格要求的,因為兩個從節點用同個epoch來贏得選舉的可能性很低,不過在實際中,系統確保正常情況當一個從節點被選舉上,那麼他有足夠時間來通知其他節點,以避免另外一個從節點發起另一個新的選舉。
- 主節點不會用任何方式來嘗試選出最好的從節點,只要從節點的主節點處於FAIL狀態並且投票主節點在這一輪中還沒投票,則會積極投票。
- 主節點拒絕投票,則不會給任何負面迴應,忽略即可。
- 主節點不會授予投票給那些configEpoch值比主節點雜湊槽表裡configEpoch更小的從節點。
鍵分佈模型
鍵空間被分割為16384槽,事實上叢集的最大節點數量是16384個。所有的主節點都負責16384個雜湊槽中的一部分。當叢集處於穩定狀態時,叢集沒有在執行重配置操作,每個雜湊槽都只由一個節點進行處理。以下是用來把鍵對映到雜湊槽的演算法: HASH_SLOT = CRC16(key)mod 16384 CRC16能相當好的把不同的鍵均勻分配到16384個槽中。 在叢集剛建立時,可以用工具自動分配槽,也可以自己手動分配槽。在叢集執行過程中,也可以手動對槽的分佈進行重新配置,若要保證資料不丟失,還要進行槽資料遷移後方可進行更換。鍵雜湊標籤(keys hash tags)
計算雜湊槽可以實現雜湊標籤,是確保兩個鍵都在同一個雜湊槽裡的一種方式。將來也許在允許多鍵操作中使用的到。 為了實現雜湊標籤,使用另一種方式計算雜湊槽: 如果鍵包含一個{字元,那麼在{的右邊就會有一個},在{和}之間會有一個或多個字元,第一個}一定是出現在第一個{之後,然後不是直接計算鍵的雜湊,只有在第一個{和它右邊第一個}之前的內容繪本用來計算雜湊值。MOVED重定向
一個Redis客戶端可以自由的向叢集中的任意節點發送查詢,接收到的節點會分析查詢,如果這個命令是叢集可以執行的,那麼節點會找這個鍵所屬的雜湊槽對應的節點。如果剛好這個節點就是對應這個雜湊槽,那麼這個查詢就直接備節點處理。否則這個節點會檢視它內部的雜湊槽->節點ID對映,然後給客戶端返回一個MOVED錯誤。在Redis叢集中的節點並不是把命令轉發到管理所給出的鍵值的正確節點上,而是把客戶端重定向到服務一定範圍內的鍵值的節點上,叢集節點不能代理請求,所以客戶端在接收到重定向錯誤-MOVED和-ASK的時候,將命令重定向到其他節點上。叢集線上重配置(live reconfiguration)
Redis叢集支援在叢集執行過程中新增(剛新增的節點是沒有槽位的)或者刪除(刪除一組master-slave後,它所分配的槽就空缺了)節點。實際上,新增或移除節點都被抽象為同一個操作,那就是把雜湊槽從一個節點移到另一個節點。 向叢集中新增一個新的節點,就是把一個空節點加入到叢集中並把某些雜湊槽從已經存在的節點移到新節點上。 從叢集中移除一個節點,就是把該節點上的雜湊槽移到其他節點上。 所以實現這個的核心是把雜湊槽移來移去。從實際角度看,雜湊槽就是一堆鍵,所以Redis叢集在重組碎片時做的就是把鍵從一個節點移到另一節點。 為了理解這是怎麼工作的。我們需要介紹CLUSTER的子命令,這些子命令是用來操作Redis叢集節點上的雜湊槽轉換表。- CLUSTER ADDSLOTS slot1[slot2]...[slotn]
- CLUSTER DELSLOTS slot1[slot2]...[slotn]
- CLUSTER SETSLOT slot NODE node
- CLUSTER SETSLOT slot MIGRATING node
- CLUSTER SETSLOT slot IMPORTING node
- 當一個槽被設定為MIGRATING,原來持有該雜湊槽的節點仍會接收所有跟這個雜湊槽有關的請求,但只有當查詢的鍵還存在原節點時,原節點會處理請求,否則這個查詢會通過一個-ASK重定向轉發到遷移的目標節點。
- 當一個槽被設定為IMPORTING,只有在接收到ASKING命令之後節點才會接收所有查詢這個雜湊槽的請求。如果客戶端一直沒有傳送ASKING命令,那麼查詢都會通過-MOVED重定向錯誤轉發到真正處理這個雜湊槽的節點那裡。
ASK重定向
在前面的章節中,我們簡短的提到ASK重定向,為什麼我們不能單純地使用MOVED重定向呢?因為當我們使用MOVED的時候,意味著我們認為雜湊槽永久的被另一個節點處理,並且希望接下來的所有查詢都嘗試發到這個指定節點上去。而ASK意味著我們只要下一個查詢發到指定節點上去(一般客戶端會記住槽點的分佈情況)。這是必要的,因為下一個查詢的鍵或許還在舊的節點上。然而我們需要強制客戶端的行為,以確保客戶端會在嘗試A中查詢後再查詢B,如果客戶端在傳送查詢前傳送了ASKING命令,那麼節點B只會接收被設為IMPORTING的槽的查詢,也就是ASKING在客戶端設定了一個一次性標識,強制一個節點可以執行一次關於帶有IMPORTING狀態的槽的查詢。從客戶端看來,ASK重定向完整語義:- 如果接收到ASK重定向,那麼把查詢的物件調整為指定的節點。
- 先發送ASKING命令,再開始傳送查詢。
- 現在不要更新本地客戶端對映表。
RedisCluster處理流程
- 檢查Key所在的Slot是否屬於當前Node,1)計算crc16(key)%16384得到Slot。2)查詢clusterState.slots負責的節點指標。3)與myself指標比較。
- 若不屬於,則響應MOVED錯誤重定向客戶端
- 若屬於且Key存在,則直接操作,返回結果給客戶端。
- 若Key不存在,檢查該Slot是否遷出中?
- 若Slot遷出中,返回ASK錯誤重定向客戶端到遷移的目的伺服器上。
- 若Slot未遷出,檢查Slot是否匯入中?
- 若Slot匯入中且有ASKING標記,則直接操作
- 否則響應MOVED錯誤重定向客戶端。