您現在的位置是:首頁 > 籃球

深入淺出帶你走進Redis!

  • 由 赫爾岑岑 發表于 籃球
  • 2022-10-16
簡介如果從庫的例項過多,對於主庫來說有一定的壓力,主庫會頻繁fork子程序以生成RDB檔案,fork這個操作會阻塞主執行緒處理正常請求,導致響應變慢,Redis採用了主-從-從的模式,可以手動選擇一個從庫,用來同步其他從庫的資料,以減少主庫生成

管槽這兩個字怎麼讀

什麼是Redis

Redis是一個開源、基於記憶體、使用C語言編寫的key-value資料庫,並提供了多種語言的API。它的資料結構十分豐富,基礎資料型別包括:string(字串)、list(列表,雙向連結串列)、hash(雜湊,鍵值對集合)、set(集合,不重複)和sorted set(有序集合)。主要可以用於資料庫、快取、分散式鎖、訊息佇列等。。。

以上的資料型別是Redis鍵值的資料型別,其實就是資料的儲存形式,但是資料型別的底層實現是最重要的,底層的資料結構主要分為6種,分別是 簡單動態字串、雙向連結串列、壓縮連結串列、雜湊表、跳錶和整數陣列 。各個資料型別和底層結構的對應關係如下:

深入淺出帶你走進Redis!

各個底層實現的時間複雜度如下:

深入淺出帶你走進Redis!

可以看出除了string型別的底層實現只有一種資料結構,其他四種均有兩種底層實現,這四種類型為集合型別,其中一個鍵對應了一個集合的資料。

(一)Redis鍵值是如何儲存的呢?

Redis為了快速訪問鍵值對,採用了 雜湊表 來儲存所有的鍵值對,一個雜湊表對應了多個 雜湊桶 ,所謂的雜湊桶是指雜湊表陣列中的每一個元素,當然雜湊表中儲存的不是值本身,是指向值的指標,如下圖。

其中雜湊桶中的entry元素中儲存了*key和*value指標,分別指向了實際的鍵和值。透過Redis可以在O(1)的時間內找到鍵值對,只需要計算key的雜湊值就可以定位位置,但從下圖可以看出,在4號位置出現了衝突,兩個key對映到了同一個位置,這就產生了雜湊衝突,會導致雜湊表的操作變慢。雖然Redis透過鏈式衝突解決該問題,但如果資料持續增多,產生的雜湊衝突也會越來越多,會加重Redis的查詢時間。

深入淺出帶你走進Redis!

Redis儲存資料示意圖

為了解決上述的雜湊衝突問題,Redis會對雜湊表進行 rehash 操作,也就是增加目前的雜湊桶數量,使得key更加分散,進而減少雜湊衝突的問題,主要流程如下:

採用兩個hash表進行操作,當雜湊表A需要進行擴容時,給雜湊表B分配兩倍的空間。

將雜湊表A的資料重新對映並複製給雜湊表B。

釋放A的空間。

上述的步驟可能會存在一個問題,當雜湊表A向B複製的時候,是需要一定的時間的,可能會造成Redis的執行緒阻塞,就無法服務其他的請求了。

針對上述問題,Redis採用了 漸進式rehash ,主要的流程是:Redis還是繼續處理客戶端的請求,每次處理一個請求的時候,就會將該位置所有的entry都複製到雜湊表B中,當然也會存在某個位置一直沒有被請求。Redis也考慮了這個問題,透過設定一個定時任務進行rehash,在一些鍵值對一直沒有操作的時候,會週期性的搬移一些資料到雜湊表B中,進而縮短rehash的過程。

(二)Redis為什麼採用單執行緒呢?

首先要明確的是Redis單執行緒指的是 網路IO 和 鍵值 對讀寫 是由一個執行緒來完成的,但Redis持久化、叢集資料等是由額外的執行緒執行的。瞭解Redis使用單執行緒之前可以先了解一下多執行緒的開銷。

通常情況下,使用多執行緒可以增加系統吞吐率或者可以增加系統擴充套件性,但多執行緒通常會存在同時訪問某些共享資源,為了保證訪問共享資源的正確性,就需要有額外的機制進行保證,這個機制首先會帶來一定的開銷。其實對於多執行緒併發訪問的控制一直是一個難點問題,如果沒有精細的設計,比如說,只是簡單地採用一個粗粒度互斥鎖,就會出現不理想的結果。即使增加了執行緒,大部分執行緒也在等待獲取訪問共享資源的互斥鎖,並行變序列,系統吞吐率並沒有隨著執行緒的增加而增加。

這也是Redis使用單執行緒的主要原因。

值得注意的是在Redis6。0中引入了多執行緒 。在Redis6。0之前,從網路IO處理到實際的讀寫命令處理都是由單個執行緒完成的,但隨著網路硬體的效能提升,Redis的效能瓶頸有可能會出現在網路IO的處理上,也就是說 單個主執行緒處理網路請求的速度跟不上底層網路硬體的速度 。針對此問題,Redis採用多個IO執行緒來處理網路請求,提高網路請求處理的並行度,但多IO執行緒只用於處理網路請求, 對於讀寫命令,Redis仍然使用單執行緒處理 !

(三)Redis單執行緒為什麼還這麼快?

IO多路複用機制:使其在網路IO操作中能併發處理大量的客戶端請求從而實現高吞吐率 。

IO多路複用機制是指一個執行緒處理多個IO流,也就是常說的select/epoll機制。在Redis執行單執行緒的情況下,該機制允許核心中同時存在多個監聽套接字和已連線套接字。核心會一直監聽這些套接字上的連線請求或資料請求。一旦有請求到達,就會交給Redis執行緒處理,這就實現了一個Redis執行緒處理多個IO流的效果,進而提升併發性。

Redis是基於記憶體的,絕大部分請求都是記憶體操作,十分的迅速 。

Redis具有高效的底層資料結構,為最佳化記憶體,對每種型別基本都有兩種底層實現方式 。

主要執行過程是單執行緒,避免了不必要的上下文切換和資源競爭,不存在多執行緒導致的CPU切換和鎖的問題。

Redis資料丟失問題

由上一小節我們大概瞭解了 Redis的儲存和快的主要原因,通常情況下我們會把Redis當作快取使用,將後端資料庫中的資料儲存在記憶體中,然後從記憶體中直接讀取資料,響應速度會非常快。但是如果伺服器宕機了,記憶體中的資料也就會丟失,當然我們可以重新從後端資料庫中恢復這些快取資料,但是頻繁訪問資料庫,會給資料庫帶來一定的壓力;另一方面資料是從慢速的資料庫中讀取的,效能肯定比不上Redis,也會導致這些資料的應用程式響應變慢。

所以對Redis來說,實現資料的持久化,避免從後端恢復資料是至關重要的,目前Redis持久化主要有兩大機制,分別是 AOF(Append Only File)日誌和RDB快照 。

(一)AOF日誌

AOF日誌是寫後日志,也就是Redis先執行命令,然後將資料寫入記憶體,最後才記錄日誌,如下圖:

深入淺出帶你走進Redis!

Redis AOF操作過程

AOF日誌中記錄的是Redis收到的每一條命令,這些命令都是以文字的形式儲存的,例如我們以Redis收到set key value命令後記錄的日誌為例,AOF檔案中儲存的資料如下圖所示,其中*3代表當前命令分為三部分,每部分都是透過$+數字開頭,其中數字表示該部分的命令、鍵、值一共有多少位元組。

深入淺出帶你走進Redis!

Redis AOF日誌內容

AOF為了避免額外的檢查開銷,並不會檢查命令的正確性,如果先記錄日誌再執行命令,就有可能記錄錯誤的命令,再透過AOF日誌恢復資料的時候,就有可能出錯,而且在執行完命令後記錄日誌也不會阻塞當前的寫操作。但是AOF是存在一定的風險的,首先是如果剛執行一個命令,但是AOF檔案中還沒來得及儲存就宕機了,那麼這個命令和資料就會有丟失的風險,另外AOF雖然可以避免對當前命令的阻塞(因為是先寫入再記錄日誌),但有可能會對下一次操作帶來阻塞風險(可能存在寫入磁碟較慢的情況)。這兩種情況都在於AOF什麼時候寫入磁碟,對於這個問題AOF機制提供了三種選擇(appendfsync的三個可選值),分別是 Always、Everysec、No 具體如下:

深入淺出帶你走進Redis!

我們可以根據不同的場景來選擇不同的方式:

Always可靠性較高,資料基本不丟失,但是對效能的影響較大。

Everysec效能適中,即使宕機也只會丟失1秒的資料。

No效能好,但是如果宕機丟失的資料較多。

雖然有一定的寫回策略,但畢竟AOF是透過檔案的形式記錄所有的寫命令,但如果指令越來越多的時候,AOF檔案就會越來越大,可能會超出檔案大小的限制;另外,如果檔案過大再次寫入指令的話效率也會變低;如果發生宕機,需要把AOF所有的命令重新執行,以用於故障恢復,資料過大的話這個恢復過程越漫長,也會影響Redis的使用。

此時, AOF重寫機制 就來了:

AOF重寫就是根據所有的鍵值對建立一個新的AOF檔案,可以減少大量的檔案空間,減少的原因是:AOF對於命令的新增是追加的方式,逐一記錄命令,但有可能存在某個鍵值被反覆更改,產生了一些冗餘資料,這樣在重寫的時候 就可以過濾掉這些指令,從而更新當前的最新狀態。

AOF重寫的過程是透過主執行緒fork後臺的bgrewriteaof子程序來實現的,可以避免阻塞主程序導致效能下降,整個過程如下:

AOF每次重寫,fork過程會把主執行緒的記憶體複製一份bgrewriteaof子程序,裡面包含了資料庫的資料,複製的是父程序的頁表,可以在不影響主程序的情況下逐一把複製的資料記入重寫日誌;

因為主執行緒沒有阻塞,仍然可以處理新來的操作,如果這時候存在寫操作,會先把操作先放入緩衝區,對於正在使用的日誌,如果宕機了這個日誌也是齊全的,可以用於恢復;對於正在更新的日誌,也不會丟失新的操作,等到資料複製完成,就可以將緩衝區的資料寫入到新的檔案中,保證資料庫的最新狀態。

(二)RDB快照

上一小節裡瞭解了避免Redis資料丟失的AOF方法,這個方法記錄的是操作命令,而不是實際的資料,如果日誌非常多的話,Redis恢復的就很緩慢,會影響到正常的使用。

這一小節主要是講述的另一種Redis資料持久化的方式: 記憶體快照 。即記錄記憶體中的資料在某一時刻的狀態,並以檔案的形式寫到磁碟上,即使伺服器宕機,快照檔案也不會丟失,資料的可靠性也就得到了保證,這個檔案稱為RDB(Redis DataBase)檔案。可以看出RDB記錄的是某一時刻的資料,和AOF不同,所以在資料恢復的時候只需要將RDB檔案讀入到記憶體,就可以完成資料恢復。但為了RDB資料恢復的可靠性,在進行快照的時候是全量快照,會將記憶體中所有的資料都記錄到磁碟中,這就有可能會阻塞主執行緒的執行。Redis提供了兩個命令來生成RDB檔案,分別是 save 和 bgsave :

save:在主執行緒中執行,會導致阻塞;

bgsave:會建立一個子程序,該程序專門用於寫入RDB檔案,可以避免主執行緒的阻塞,也是預設的方式。

我們可以採用bgsave的命令來執行全量快照,提供了資料的可靠性保證,也避免了對Redis的效能影響。執行快照期間資料能不能修改呢?如果不能修改,快照過程中如果有新的寫操作,資料就會不一致,這肯定是不符合預期的。Redis借用了作業系統的 寫時複製 ,在執行快照的期間,正常處理寫操作。

主要流程為:

bgsave子程序是由主執行緒fork出來的,可以共享主執行緒的所有記憶體資料。

bgsave子程序執行後,開始讀取主執行緒的記憶體資料,並把它們寫入RDB檔案中。

如果主執行緒對這些資料都是讀操作,例如A,那麼主執行緒和bgsave子程序互不影響。

如果主執行緒需要修改一塊資料,如C,這塊資料會被複制一份,生成資料的副本,然主執行緒在這個副本上進行修改;bgsave子程序可以把原來的資料C寫入RDB檔案。

深入淺出帶你走進Redis!

寫時複製機制保證快照期間資料可修改

透過上述方法就可以保證快照的完整性,也可以允許主執行緒處理寫操作,可以避免對業務的影響。 那多久進行一次快照呢 ?

理論上來說快照時間間隔越短越好,可以減少資料的丟失,畢竟fork的子程序不會阻塞主執行緒,但是頻繁的將資料寫入磁碟,會給磁碟帶來很多壓力,也可能會存在多個快照競爭磁碟頻寬(當前快照沒結束,下一個就開始了)。另一方面,雖然fork出的子程序不會阻塞,但fork這個建立過程是會阻塞主執行緒的,當主執行緒需要的記憶體越大,阻塞時間越長。

針對上面的問題,Redis採用了 增量快照 ,在做一次全量快照後,後續的快照只對修改的資料進行記錄,需要記住哪些資料被修改了,可以避免全量快照帶來的開銷。

(三)混合使用AOF日誌和RDB快照

雖然跟AOF相比,RDB快照的恢復速度快,但快照的頻率不好把握,如果頻率太低,兩次快照間一旦宕機,就可能有比較多的資料丟失。如果頻率太高,又會產生額外開銷,那麼,還有什麼方法既能利用 RDB 的快速恢復,又能以較小的開銷做到儘量少丟資料呢?

在Redis4。0提出了 混合使用AOF和RDB快照 的方法,也就是兩次RDB快照期間的所有命令操作由AOF日誌檔案進行記錄。這樣的好處是RDB快照不需要很頻繁的執行,可以避免頻繁fork對主執行緒的影響,而且AOF日誌也只記錄兩次快照期間的操作,不用記錄所有操作,也不會出現檔案過大的情況,避免了重寫開銷。

透過上述方法既可以享受RDB快速恢復的好處,也可以享受AOF記錄簡單命令的優勢。

對於AOF和RDB的選擇問題 :

資料不能丟失時,記憶體快照和AOF的混合使用是一個很好的選擇。

如果允許分鐘級別的資料丟失,可以只使用RDB。

如果只用AOF,優先使用everysec的配置選項,因為它在可靠性和效能之間取了一個平衡。

Redis資料同步

當Redis發生宕機的時候,可以透過AOF和RDB檔案的方式恢復資料,從而保證資料的丟失從而提高穩定性。但如果Redis例項宕機了,在恢復期間就無法服務新來的資料請求;AOF和RDB雖然可以保證資料儘量的少丟失,但無法保證服務儘量少中斷,這就會影響業務的使用,不能保證Redis的高可靠性。

Redis其實採用了主從庫的模式,以保證資料副本的一致性,主從庫採用讀寫分離的方式:從庫和主庫都可以接受讀操作;對於寫操作,首先要到主庫執行,然後主庫再將寫操作同步到從庫。

只有主庫接收寫操作可以避免客戶端將資料修改到不同的Redis例項上,其他

客戶端進行讀取時可能就會讀取到舊的值;當然,如果非要所有的庫都可以進行寫操作,就要涉及到鎖、例項間協商是否完成修改等一系列操作,會帶來額外的開銷。

(一)主從庫如何進行第一次資料同步

當存在多個Redis例項的時候,可以透過replicaof命令形成主庫和從庫的關係,在從庫中輸入: replicaof主庫ip 6379 就可以在主庫中複製資料,具體有三個階段:

首先是主從庫建立連線、協商同步的過程,具體的從庫向主庫傳送psync命令,代表要進行資料同步;psync中包含了主庫的runID(Redis啟動時生成的隨機ID,初始值為:?)和複製進度offset(設為-1,代表第一次複製)兩個引數,主庫接收到psync命令,會用FULLRESYNC命令返回給從庫,包含兩個引數:主庫runID和複製進度offset;其中FULLRESYNC代表的全量複製,會將主庫所有的資料都複製給從庫。

待從庫接收到資料後,在本地完成資料載入,具體的主庫執行bgsave命令,生成RDB檔案,然後將檔案發給從庫,從庫接收到RDB檔案後,首先清空當前資料,然後再載入RDB檔案;這個過程主庫不會被阻塞,仍然可以接受請求,如果存在寫操作,剛剛生成的RDB檔案中是不包含這些新資料的,此時主庫會在記憶體中用專門的replication buffer記錄RDB檔案生成後所有的寫操作。

最後,主庫會把replication buffer中的修改操作發給從庫,從庫重新執行這些操作,就可以實現主從庫同步了。

如果從庫的例項過多,對於主庫來說有一定的壓力,主庫會頻繁fork子程序以生成RDB檔案,fork這個操作會阻塞主執行緒處理正常請求,導致響應變慢,Redis採用了主-從-從的模式,可以手動選擇一個從庫,用來同步其他從庫的資料,以減少主庫生成RDB檔案和傳輸RDB檔案的壓力;如下圖:

深入淺出帶你走進Redis!

級聯的“主-從-從”模式

這樣從庫就可以知道在進行資料同步的時候,不需要和主庫直接互動,只需要和選擇的從庫進行寫操作同步就可以了,從而減少主庫的壓力。

(二)主庫如果掛了呢?

Redis採用主從庫的模式保證資料副本的一致性,在這個模式下如果從庫發生故障,客戶端可以向其他主庫或者從庫傳送請求,但如果主庫掛了,客戶端就沒法進行寫操作了,也無法對從庫進行相應的資料複製操作。

不管是寫服務中斷還是從庫無法進行資料同步,都是不能接受的,所以當主庫掛了以後,需要一個新的主庫來代替掛掉的主庫,這樣就就會產生三個問題:

怎麼判斷主庫是真的掛了,而不是網路異常?

主庫如果掛了,該選擇哪個從庫作為新的主庫?

怎麼把新主庫的相關資訊通知給從庫和客戶端?

Redis採用了 哨兵機制 應對這些問題,哨兵機制是實現主從庫自動切換的關鍵機制,在主從庫執行的同時,它也在進行 監控、選擇主庫和通知 的操作。

監控。 哨兵在執行時,週期性 地 給所有的主從庫傳送PING命令,檢測是否仍在執行。 如果 從 庫沒有響應哨兵的PING命令,哨兵就會將它標記為下線狀態; 如果主庫沒有在規定時間內響應哨兵的PING命令,哨兵也會判斷主庫下限,然後開始自動切換主庫的流程。

選主。主庫掛了之後,哨兵需要按照一定的規則選擇一個從庫,並將他作為新的主庫。

通知。選取了新的主庫後,哨兵會把新主庫的連線資訊發給其他從庫,讓它們執行replicaof命令和新主庫建立連線,並進行資料複製;同時哨兵也會將新主庫的訊息發給客戶端。

下圖展示了哨兵的幾個操作的任務:

深入淺出帶你走進Redis!

哨兵機制的三項任務與目標

但這樣也會存在一個問題,哨兵判斷主從庫是否下線如果出現失誤呢?

對於 從 庫,下線影響不大,叢集的對外服務也不會間斷。 但是如果哨兵誤判主庫下線,可能是因為網路擁塞或者主庫壓力大的情況,這時候也就需要進行選主並讓從庫和新的主庫進行資料同步,這個過程是有一定的開銷的,所以我們要儘可能地避免誤判的情況。 哨兵機制也考慮了這一點,它通常採用多實 例 組成的叢集模式進行部署,也被稱為哨兵叢集; 透過引入多個哨兵例項一起判斷,就可以儘可能 地 避免單個哨兵產生的誤判問題。 這時候判斷主庫是否下線不是由一個哨兵決定的,只有大多數哨兵認為該主庫下線,主庫才會標記為“客觀下線”。

簡單的來說”客觀下線“的標準是當N個哨兵例項,有N/2+1個例項認為該主庫為下線狀態,該主庫就會被認定為“客觀下線”。這樣就可以儘量的避免單個哨兵產生的誤判問題(N/2+1這個值也可以透過引數改變);

如果判斷了主庫為主觀下線,怎麼選取新的主庫呢?

上面有說 到 ,這一部分也是由哨兵機制來完成的,選取主庫的過程分為“ 篩選 和 打分 ”。 主要是按照一定的規則過濾掉不符合的從庫,再按照一定的規則給其餘的從庫打分,將最高分的從庫作為新的主庫。

篩選。首先從庫一定是正在執行的,還要判斷從庫之前的網路連線狀態,如果總是斷連並且超過了一定的閾值,哨兵會認為該從庫的網路不好,也會 將其篩掉。

打分。哨兵機制根據三個規則依次進行打分: 從庫優先順序、從庫複製進度以及從庫ID號 ;在某一輪有從庫得分最高,那麼它就是新的主庫了,選主 過程結束。如果該輪沒有出現最高的,繼續下一輪。

優先順序最高的從庫。 使用者可以透過slave-priority配置項,給不同的從庫設定優先順序。 選主庫的時候哨兵會給優先順序高的從庫打高分,如果一個從庫優先順序高,那麼就是新主庫。

從庫複製進度最接近。 主庫的slave_repl_offset和從庫master_repl_offset越接近,得分越高。

ID小的從庫得分高。 如果上面兩輪也沒有選出新主庫,就會根據從庫例項的ID來判斷,ID越小的從庫得分越高。

由此哨兵可以選擇出一個新的主庫。

由哪個哨兵來執行主從庫切換呢?

這個過程和判斷主庫“客觀下線”類似,也是一個投票的過程。如果某個哨兵判斷了主庫為下線狀態,就會給其他的哨兵例項傳送is-master-down-by-addr的命令,其他例項會根據自己和主庫的連線狀態作出Y或N的響應,Y相當於贊成票,N為反對票。一個哨兵獲得一定的票數後,就可以標記主庫為“客觀下線”,這個票數是由引數quorum設定的。如下圖:

深入淺出帶你走進Redis!

例如:現在有3個哨兵,quorum配置的是2,那麼,一個哨兵需要2張贊成票,就可以標記主庫為“客觀下線”了。這2張贊成票包括哨兵自己的一張贊成票和另外兩個哨兵的贊成票。

這個時候哨兵就可以給其他哨兵傳送訊息,表示希望自己來執行主從切換,並讓所有的哨兵進行投票,這個過程稱為“Leader選舉”,進行主從切換的哨兵稱為Leader。任何一個想成為Leader的哨兵都需要滿足兩個條件:

拿到半數以上的哨兵贊成票。

拿到的票數需要大於等於quorum的值。

以上就可以選出Leader然後進行主從庫切換了。

Redis叢集

(一 )資料量過多如何處理?

當資料量過多的情況下,一種簡單的方式是升級Redis例項的資源配置,包括增加記憶體容量、磁碟容量、更好配置的CPU等,但這種情況下Redis使用RDB進行持久化的時候響應會變慢,Redis透過fork子程序來完成資料持久化,但fork在執行時會阻塞主執行緒,資料量越大,fork的阻塞時間就越長,從而導致Redis響應變慢。

Redis的切片叢集 可以解決這個問題,也就是啟動多個Redis例項來組成一個叢集,再按照一定的規則把資料劃分為多份,每一份用一個例項來儲存,這樣客戶端只需要訪問對應的例項就可以獲取資料。在這種情況下fork子程序一般不會給主執行緒帶來較長時間的阻塞,如下圖:

深入淺出帶你走進Redis!

切片叢集架構圖

將20GB的資料分為4分,每份包含5GB資料,客戶端只需要找到對應的例項就可以獲取資料,從而減少主執行緒阻塞的時間。

當資料量過多的時候,可以透過升級Redis例項的資源配置或者透過切片叢集的方式。前者實現起來簡單粗暴,但這資料量增加的時候,需要的記憶體也在不斷增加,主執行緒fork子程序就有可能會阻塞,而且該方案受到硬體和成本的限制。相比之下第二種方案是一種擴充套件性更好的方案,如果想儲存更多的資料,僅需要增加Redis例項的個數,不用擔心單個例項的硬體和成本限制。 在面向百萬、千萬級別的使用者規模時,橫向擴充套件的 Redis 切片叢集會是一個非常好的選擇 。

選擇切片叢集也是需要解決一些問題的:

資料切片後,在多個例項之間怎麼分佈?

客戶端怎麼確定想要訪問的例項是哪一個?

Redis採用了Redis Cluster的方案來實現切片叢集,具體的Redis Cluster採用了雜湊槽(Hash Slot)來處理資料和例項之間的對映關係。在Redis Cluster中,一個切片叢集共有16384個雜湊槽( 為什麼Hash Slot的個數是16384 ),這些雜湊槽類似於資料的分割槽,每個鍵值對都會根據自己的key被影射到一個雜湊槽中,對映步驟如下:

根據鍵值對key,按照CRC16演算法計算一個16bit的值。

用計算的值對16384取模,得到0~16383範圍內的模數,每個模數對應一個雜湊槽。

這時候可以得到一個key對應的雜湊槽了,雜湊槽又是如何找到對應的例項的呢?

在部署Redis Cluster的時候,可以透過cluster create命令建立叢集,此時Redis會自動把這些槽分佈在叢集例項上,例如一共有N個例項,那麼每個例項包含的槽個數就為16384/N。當然可能存在Redis例項中記憶體大小配置不一的問題,記憶體大的例項具有更大的容量。這種情況下可以透過cluster addslots命令手動分配雜湊槽。

redis-cli

-h

33

。33

。33

。3

p

6379

cluster

addslots

0,1

redis-cli

-h

33

。33

。33

。4

p

6379

cluster

addslots

2,3

redis-cli

-h

33

。33

。33

。5

p

6379

cluster

addslots

4

要注意的是,如果採用cluster addslots的方式手動分配雜湊槽,需要將16384個槽全部分配完,否則Redis叢集無法正常工作。現在透過雜湊槽,切片叢集就實現了資料到雜湊槽、雜湊槽到例項的對應關係,那麼客戶端如何確定需要訪問的例項是哪一個呢?

(二)客戶端定位叢集中的資料

客戶端請求的key可以透過CRC16演算法計算得到,但客戶端還需要知道雜湊槽分佈在哪個例項上。在最開始客戶端和叢集例項建立連線後,例項就會把雜湊槽的分配資訊發給客戶端,例項之間會把自己的雜湊槽資訊發給和它相連的例項,完成雜湊槽的擴散。這樣客戶端訪問任何一個例項的時候,都能獲取所有的雜湊槽資訊。當客戶端收到雜湊槽的資訊後會把雜湊槽對應的資訊快取在本地,當客戶端傳送請求的時候,會先找到key對應的雜湊槽,然後就可以給對應的例項傳送請求了。

但是,雜湊槽和例項的對應關係不是一成不變的,可能會存在新增或者刪除的情況,這時候就需要重新分配雜湊槽;也可能為了負載均衡,Redis需要把所有的例項重新分佈。

雖然例項之間可以互相傳遞訊息以獲取最新的雜湊槽分配資訊,但是客戶端無法感知這個變化,就會導致客戶端訪問的例項可能不是自己所需要的了。

Redis Cluster提供了重定向的機制,當客戶端給例項傳送資料讀寫操作的時候,如果這個例項上沒有找到對應的資料,此時這個例項就會給客戶端返回MOVED命令的相應結果,這個結果中包含了新例項的訪問地址,此時客戶端需要再給新例項傳送操作命令以進行讀寫操作,MOVED命令如下:

GET

hello

:key

error

MOVED

33

。33

。33

。33

:6379

返回的資訊代表客戶端請求的key所在的雜湊槽為3333,實際是在33。33。33。33這個例項上,此時客戶端只需要向33。33。33。33這個例項傳送請求就可以了。

此時也存在一個小問題,雜湊槽中對應的資料過多,導致還沒有遷移到其他例項,此時客戶端就發起了請求,在這種情況下,客戶端就對例項發起了請求,如果資料還在對應的例項中,會給客戶端返回資料;如果請求的資料已經被轉移到其他例項上,客戶端就會收到例項返回的ASK命令,該命令表示:雜湊槽中資料還在前一種、ASK命令把客戶端需要訪問的新例項返回了。此時客戶端需要給新例項傳送ASKING命令以進行請求操作。

值得注意的是ASK資訊和MOVED資訊不一樣, ASK資訊並不會更新客戶端本地的快取的雜湊槽分配資訊 ,也就是說如果客戶端再次訪問該雜湊槽還是會請求之前的例項,直到資料遷移完成。

Top