前面咱們介紹了國人本身開發的Redis集羣方案——Codis,Codis友好的管理界面以及強大的自動平衡槽位的功能深受廣大開發者的喜好。今天咱們一塊兒來聊一聊Redis做者本身提供的集羣方案——Cluster。但願讀完這篇文章,你可以充分了解Codis和Cluster各自的優缺點,面對不一樣的應用場景能夠從容的作出選擇。html
Redis Cluster是去中心化的,這點與Codis有着本質的不一樣,Redis Cluster劃分了16384個slots,每一個節點負責其中的一部分數據。slot的信息存儲在每一個節點中,節點會將slot信息持久化到配置文件中,所以須要保證配置文件是可寫的。當客戶端鏈接時,會得到一份slot的信息。這樣當客戶端須要訪問某個key時,就能夠直接根據緩存在本地的slot信息來定位節點。這樣就會存在客戶端緩存的slot信息和服務器的slot信息不一致的問題,這個問題具體怎麼解決呢?這裏先賣個關子,後面會作解釋。node
首先咱們來看下官方對Redis Cluster的介紹。es6
是否是不(kan)想(bu)看(dong)?不要緊,我來給你掰開了揉碎瞭解釋一下。redis
Redis Cluster使用異步的主從同步方式,只能保證最終一致性。因此會引發一些寫入數據丟失的問題,在繼續閱讀以前,能夠先本身思考一下在什麼狀況下寫入的數據會丟失。算法
先來看一種比較常見的寫丟失的狀況:windows
client向一個master發送一個寫請求,master寫成功並通知client。在同步到slave以前,這個master掛了,它的slave代替它成爲了新的master。這時前面寫入的數據就丟失了。緩存
此外,還有一種狀況。安全
master節點與大多數節點沒法通訊,一段時間後,這個master被認爲已經下線,而且被它的slave頂替,又過了一段時間,原來的master節點重寫恢復了鏈接。這時若是一個client存有過時的路由表,它就會把寫請求發送的這個舊的master節點(已經變成slave了)上,從而致使寫數據丟失。bash
不過,這種狀況通常不會發生,由於當一個master失去鏈接足夠長時間而被認爲已經下線時,就會開始拒絕寫請求。當它恢復以後,仍然會有一小段時間是拒絕寫請求的,這段時間是爲了讓其餘節點更新本身的路由表中的配置信息。服務器
爲了儘量保證寫安全性,Redis Cluster在發生分區時,會盡可能使客戶端鏈接到多數節點的那一部分,由於若是鏈接到少數部分,當master被替換時,會由於多數master不可達而拒絕全部的寫請求,這樣損失的數據要增大不少。
Redis Cluster維護了一個NODE_TIMEOUT變量,若是上述狀況中,master在NODE_TIMEOUT時間內恢復鏈接,就不會有數據丟失。
若是集羣的大部分master可達,而且每一個不可達的master至少有一個slave,在NODE_TIMEOUT時間後,就會開始進行故障轉移(通常1到2秒),故障轉移完成後的集羣仍然可用。
若是集羣中得N個master節點都有1個slave,當有一個節點掛掉時,集羣必定是可用的,若是有2個節點掛掉,那麼就會有1/(N*2-1)的機率致使集羣不可用。
Redis Cluster爲了提升可用性,新增了一個新的feature,叫作replicas migration(副本遷移,ps:我本身翻譯的),這個feature其實就是在每次故障以後,從新佈局集羣的slave,給沒有slave的master配備上slave,以此來更好的應對下次故障。
Redis Cluster不提供代理,而是讓client直接重定向到正確的節點。
client中會保存一份集羣狀態的副本,通常狀況下就會直接鏈接到正確的節點。
因爲Redis Cluster是異步備份的,因此節點不須要等待其餘節點確認寫成功就能夠直接返回,除非顯式的使用了WAIT命令。
對於操做多個key的命令,所操做的key必須是在同一節點上的,由於數據是不會移動的。(除非是resharding)
Redis Cluster設計的主要目標是提升性能和擴展性,只提供弱的數據安全性和可用性(可是要合理)。
Redis Cluster共劃分爲16384個槽位。這也意味着一個集羣最多能夠有16384個master,不過官方建議master的最大數量是1000個。
若是Cluster不處於從新配置過程,那麼就會達到一種穩定狀態。在穩定狀態下,一個槽位只由一個master提供服務,不過一個master節點會有一個或多個slave,這些slave能夠提供緩解master的讀請求的壓力。
Redis Cluster會對key使用CRC16算法進行hash,而後對16384取模來肯定key所屬的槽位(hash tag會打破這種規則)。
標籤是破壞上述計算規則的實現,Hash tag是一種保證多個鍵被分配到同一個槽位的方法。
hash tag的計算規則是:取一對大括號{}之間的字符進行計算,若是key存在多對大括號,那麼就取第一個左括號和第一個右括號之間的字符。若是大括號以前沒有字符,則會對整個字符串進行計算。
說了這個多,可能你仍是一頭霧水。別急,咱們來吃幾個栗子。
前面聊性能的時候咱們提到過,Redis Cluster爲了提升性能,不會提供代理,而是使用重定向的方式讓client鏈接到正確的節點。下面咱們來詳細說明一下Redis Cluster是如何進行重定向的。
Redis客戶端能夠向集羣的任意一個節點發送查詢請求,節點接收到請求後會對其進行解析,若是是操做單個key的命令或者是包含多個在相同槽位key的命令,那麼該節點就會去查找這個key是屬於哪一個槽位的。
若是key所屬的槽位由該節點提供服務,那麼就直接返回結果。不然就會返回一個MOVED錯誤:
GET x
-MOVED 3999 127.0.0.1:6381
複製代碼
這個錯誤包括了對應的key屬於哪一個槽位(3999)以及該槽位所在的節點的IP地址和端口號。client收到這個錯誤信息後,就將這些信息存儲起來以即可以更準確的找到正確的節點。
當客戶端收到MOVED錯誤後,可使用CLUSTER NODES或CLUSTER SLOTS命令來更新整個集羣的信息,由於當重定向發生時,不多會是單個槽位的變動,通常都會是多個槽位一塊兒更新。所以,在收到MOVED錯誤時,客戶端應該儘早更新集羣的分佈信息。當集羣達到穩定狀態時,客戶端保存的槽位和節點的對應信息都是正確的,cluster的性能也會達到很是高效的狀態。
除了MOVED重定向以外,一個完整的集羣還應該支持ASK重定向。
對於Redis Cluster來說,MOVED重定向意味着請求的slot永遠由另外一個node提供服務,而ASK重定向僅表明下一個請求須要發送到指定的節點。在Redis Cluster遷移的時候會用到ASK重定向,那Redis Cluster遷移的過程到底是怎樣的呢?
Redis Cluster的遷移是以槽位單位的,遷移過程總共分3步(相似於把大象裝進冰箱),咱們來舉個栗子,看一下一個槽位從節點A遷移到節點B須要通過哪些步驟:
有同窗會問了,說好的用到ASK重定向呢?上面咱們所描述的只是遷移的過程,在遷移過程當中,Redis仍是要對外提供服務的。試想一下,若是在遷移過程當中,我向A節點請求查詢x的值,A說:我這沒有啊,我也不知道是傳到B那去了仍是我一直就沒有存,你仍是先問問B吧。而後返回給咱們一個-ASK targetNodeAddr的錯誤,讓咱們去問B。而這時若是咱們直接去問B,B確定會直接說:這個不歸我管,你得去問A。(-MOVED重定向)。由於這時候遷移尚未完成,因此B也沒說錯,這時候x真的不歸它管。可是咱們不能讓它倆來回踢皮球啊,因此在問B以前,咱們先給B發一個asking指令,告訴B:下面我問你一個key的值,你得當成是本身的key來處理,不能說不知道。這樣若是x已經遷移到B,就會直接返回結果,若是B也查不到x的下落,說明x不存在。
瞭解了Redis Cluster的重定向操做以後,咱們再來聊一聊Redis Cluster的容錯機制,Redis Cluster和大多數集羣同樣,是經過心跳來判斷一個節點是否存活的。
集羣中的節點會不停的互相交換ping pong包,ping pong包具備相同的結構,只是類型不一樣,ping pong包合在一塊兒叫作心跳包。
一般節點會發送ping包並接收接收者返回的pong包,不過這也不是絕對,節點也有可能只發送pong包,而不須要讓接收者發送返回包,這種操做一般用於廣播一個新的配置信息。
節點會每一個幾秒鐘就發送必定數量的ping包。若是一個節點超過二分之一NODE_TIME時間沒有收到來自某個節點ping或pong包,那麼就會在NODE_TIMEOUT以前像該節點發送ping包,在NODE_TIMEOUT以前,節點會嘗試TCP重連,避免因爲TCP鏈接問題而誤覺得節點不可達。
前面咱們說了,ping和pong包的結構是相同的,下面就來具體看一下包的內容。
ping和pong包的內容能夠分爲header和gossip消息兩部分,其中header包含如下信息:
gossip包含了該節點認爲的其餘節點的狀態,不過不是集羣的所有節點。具體有如下信息:
gossip消息在錯誤檢測和節點發現中起着重要的做用。
錯誤檢測用於識別集羣中的不可達節點是否已下線,若是一個master下線,會將它的slave提高爲master。若是沒法提高,則集羣會處於錯誤狀態。在gossip消息中,NODE flags的值包括兩種PFAIL和FAIL。
若是一個節點發現另一個節點不可達的時間超過NODE_TIMEOUT ,則會將這個節點標記爲PFAIL,也就是Possible failure(可能下線)。節點不可達是說一個節點發送了ping包,可是等待了超過NODE_TIMEOUT時間仍然沒有收到迴應。這也就意味着,NODE_TIMEOUT必須大於一個網絡包來回的時間。
PFAIL標誌只是一個節點本地的信息,爲了使slave提高爲master,須要將PFAIL升級爲FAIL。PFAIL升級爲FAIL須要知足一些條件:
若是知足以上條件,A節點會將B節點標識爲FAIL而且向全部節點發送B節點FAIL的消息。收到消息的節點也都會將B標爲FAIL。
FAIL狀態是單向的,只能從PFAIL升級爲FAIL,而不能從FAIL降爲PFAIL。不過存在一些清除FAIL狀態的狀況:
PFAIL提高到FAIL使用的是一種弱協議:
因爲是弱協議,Redis Cluster只要求全部節點對某個節點的狀態最終保持一致。若是大部分master認爲某個節點FAIL,那麼最終全部節點都會將其標爲FAIL。而若是隻有一小部分master節點認爲某個節點FAIL,slave並不會被提高爲master,所以,FAIL狀態將會被清除。
原理說了這麼多,咱們必定要來親自動手搭建一個Redis Cluster,下面演示一個在一臺機器上模擬搭建3主3從的Redis Cluster。固然,若是你想了解更多Redis Cluster的其餘原理,能夠查看官網的介紹。
首先要搭建起咱們須要的Redis環境,這裏啓動6個Redis實例,端口號分別是637九、6380、647九、6480、657九、6580
拷貝6份Redis配置文件並進行以下修改(以6379爲例,端口號和配置文件根據須要修改):
port 6379
cluster-enabled yes
cluster-config-file nodes6379.conf
appendonly yes
複製代碼
配置文件的名稱也須要修改,修改完成後,分別啓動6個實例(圖片中有一個端口號改錯了……)。
實例啓動完成後,就能夠建立Redis Cluster了,若是Redis的版本是3.x或4.x,須要使用一個叫作redis-trib的工具,而對於Redis5.0以後的版本,Redis Cluster的命令已經集成到了redis-cli中了。這裏我用的是Redis5,因此沒有再單獨安裝redis-trib工具。
接下來執行命令
redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6479 127.0.0.1:6480 127.0.0.1:6579 127.0.0.1:6580 --cluster-replicas 1
複製代碼
當你看到輸出了
[OK] All 16384 slots covered
複製代碼
就表示Redis Cluster已經建立成功了。
此時咱們使用cluster nodes 命令就可查看Redis Cluster的節點信息了。
能夠看到,637九、6380和6479三個節點被配置爲master節點。
接下來咱們再來嘗試一下reshard操做
如圖,輸入命令
redis-cli --cluster reshard 127.0.0.1:6380
複製代碼
Redis Cluster會問你要移動多少個槽位,這裏咱們移動1000個,接着會詢問你要移動到哪一個節點,這裏咱們輸入6479的NODE ID
reshard完成後,能夠輸入命令查看節點的狀況
redis-cli --cluster check 127.0.0.1:6480
複製代碼
能夠看到6479節點已經多了1000個槽位了,分別是0-498和5461-5961。
咱們也能夠用add-node命令新增slave節點,只不過須要加上--cluster-slave參數,而且使用--cluster-master-id指明新增的slave屬於哪一個master。
最後來總結一下,咱們介紹了
Redis Cluster的特性:寫安全、可用性、性能
Key分配模型:使用CRC16算法,若是須要分配到相同的slot,可使用tag
兩種重定向:MOVED和ASK
容錯機制:PFAIL和FAIL兩種狀態
最後又動手搭建了一個實驗的Redis Cluster。