不爲技術而技術:Redis 從單點到集羣

如果你看過我的一些文章,你應該知道,我一般不會把知識點給你直接列出來;
這樣的文章網上有一大把,你大可不必來我這看;
如果你要看我的文章,那麼,就請你做好思考的準備,跟着我的思路,去一點一點,把這麼一個知識的歷程,把它研究透徹,你會受益匪淺。

首先,如果要給 Redis 做集羣,那一定是單機存在了某種不足,
要是你的系統,單機已經完全可以滿足需求,那麼,就沒有必要,去折騰這些個集羣。

就是:
不爲技術而技術

單機單實例的不足

既然要牽扯到技術,那麼首先,就必須得涉及到 Redis 的用途,
然後,根據你 redis 的使用目的,纔去選擇合適的技術:
一是緩存、一是數據庫。

假設是緩存,那麼就不必特別在意數據的完整性,有些時候,假設進程掛了,也不會使真正數據庫的數據丟失;
所以一般做緩存,可以用 RDB,做一個持久化,這樣即使掛掉了,也可以自身恢復起來,就帶有一定量的數據,而不會是空的,請求全部壓到數據庫上。

假設是數據庫,那麼就必須用到 AOF,那麼才能保證數據的完整,而且現在版本的 AOF 也內含了 RDB,既保證快,也能保證全。

那麼,既然是牽扯到集羣,那麼一定是單機出現了不足或者問題,那麼纔會考慮到利用集羣,去解決單機的問題。
否則,集羣是沒有意義的,
因爲不但不會提升性能,反而會增加系統的複雜度和維護成本,
這樣,就不會增加項目的經濟效益。

既然要做集羣,那麼,你可以先思考一下,但機單實例的 Redis,存在哪些不足?

  1. 我們首先想到的問題就是單點故障
    假設 Redis 進程掛了,在併發量大的情況下,假如是作爲緩存,那麼這層屏障瞬間消失,所有的請求都會打進數據庫,很有可能導致數據庫直接掛掉;
    假設 Redis 是直接作爲數據庫,那麼就會導致服務直接不可用
  2. 那麼接着想,第二個問題,單實例的容量問題
    假設這個系統的業務,所包含的數據體量非常巨大,那麼一個小小的 Redis,它能存的下嗎?
    是不是容量有限?
  3. 還有,就是 Redis 的壓力問題:
    當所有的服務,都去請求這唯一的 Redis 時,那麼是不是 I/O 壓力就特別大?
    還有,是不是計算的壓力也特別大?

AKF

聊到這些個問題,想要去解決,那麼每個問題,都會有自己獨立的解決方案。
不過,要是想要同時解決這三個問題,有一個組合拳,就是:
AKF。

什麼是 AKF 呢?沒有多麼高深,實際上就是一個對問題思考解決而劃分出的三個維度。

也就是說,想要對一些單結點的應用去進行集羣擴展(不僅僅是對於 Redis),就可以從這三個維度,在高層次的空間和視角,去對集羣進行劃分和擴容。

首先,對於單節點,就會存在單點故障的問題。
雖然說,可以在故障後人工進行恢復,但是,修復的這段時間,尤其是對於一些大型互聯網公司,一點故障時間所造成的損失都是巨大難以想象的。

所以,第一個維度就是 x軸:
水平橫向擴容。

相當於對一個單點進行橫向複製,做該結點的副本。
x
這樣,想一想,可以解決什麼問題?

首先,水平橫向複製,也就是做了 Redis 的副本,就可以成爲主從,那麼,就可以解決單點故障的問題,一個 Redis 掛了,後邊的立刻頂上。

第二,由於創建出了副本,也就是新增結點的數據,和主節點的數據是一樣的,那麼,對於讀的請求,就可以分離到後邊的從結點,就可以實現讀寫分離。
讀寫分離
這樣一來,對於上邊提及的三個問題,這種基於 x 軸水平復制的方式,則可以解決單點故障和一定的壓力問題。

但是,光一個維度,雖然可以解決一定的問題,但是:

  • 首先,它沒法解決容量有限的問題,因爲所有的結點都是對主節點的複製;
  • 其次,假設業務量繼續增大,那麼,光靠主從複製的話,雖然可以靠從節點分離讀請求,
    但是,主節點的寫請求依然無法分離,壓力仍然會成爲瓶頸。

所以,爲了分離主節點的壓力,其次,爲了解決單機容量有限的問題,
則可以進行 y 軸上的擴容,進行業務拆分;
這樣,對於不同的業務,客戶端可以選擇不同的 redis 集羣進行讀取和寫入;

那麼就算總體量很大,那只需要對業務進行拆分,就可以把數據拆分得很小很小。
這樣,就可以一定程度結局容量問題,以及進一步解決壓力問題。
y業務拆分

但是,這樣就足夠了嗎?
對於大部分場景來說,確實已經足夠了。

不過,假設你的業務體量仍在不斷擴大,導致,即使業務拆分也會有很大的併發,以及極多的數據量。
這時,業務已經拆分得很細了,實在不適合再進行拆分了;
所以,這時候,就只能對集羣進行 z 軸的擴容。

也就是將之前的 x、y 軸的整體集羣,做再複製。
可以按照優先級、邏輯等等進行再度劃分。
這樣,就算體量無限增大,那麼就可以對 z 軸無限擴容。
z軸
感覺有點看不清,再畫一張:
z軸
這就是 AKF 所代表的三個維度的解決思想:

  • 水平主從複製
  • 垂直業務拆分
  • 邏輯優先級等再拆分

這樣,通過三個維度的集羣,來達到對結點的無限擴展,以解決不足。

數據一致性

雖然說上面的 AKF 看起來很完美,不過這終究是一種高層次的劃分,沒有涉及到各種下層的實現。

爲什麼一般不能對系統進行過度設計?
最主要的就是,因爲往往一個技術點,爲了解決某個問題,必然會帶來新的問題。

首先,我們先看主從複製,也就是 AKF 的 x 軸。

假設,某一時刻,一個客戶端,對主節點發生了寫操作,比如 set k1 v1,
然後,主節點需要把自己的數據,同步到從結點。
主從
這就立刻涉及到了一個問題,就是數據一致性問題。

最普通的解決辦法就是,客戶端發送寫請求給主節點之後,主節點阻塞不返回,給後邊的從結點寫入數據,直到兩個從結點都寫入完成,主節點纔給客戶端返回寫入成功。

這就是強一致性,讓所有結點阻塞,直到數據全部一致。
不過這是所有企業都追求的,但是無法實現且成本及其高昂的一種一致性。

假設有這麼一種情況,客戶端給主節點寫了一個數據,很快完成了,然後在同步從節點的時候,由於網絡抖動,丟包等現象,導致某個結點寫了很久才寫成功。
我們都知道,客戶端有一個超時的機制,這麼久都不返回,那就直接表示失敗了,
就是客戶端直接認爲這次寫操作失敗了,換言之,寫失敗,就代表着服務不可用,

也就是,強一致性,會降低可用性。
同步阻塞

然後,就可以思考一個問題,爲什麼要把一個 redis,複製出多個 redis。
因爲一個 redis 會單點故障,對不對?
所以採用多個 redis 要解決單點故障,也就是可用性問題,對不對?
但是,追求強一致性,反而喪失了可用性,那是不是就有違我們集羣的初衷!

所以強一致性是有問題的。
所以,我們就必須對強一致性降級。

那麼,我們就可以選擇,容忍一部分數據丟失。
我們讓 redis 異步往從節點更新數據。
假設,客戶端往主節點寫了一個數據,然後就直接返回,對於客戶端來說,就已經表示寫入成功了,
然後,redis 主節點,異步地往從結點去寫數據。
但是,假設從結點掛了,或者,網絡斷了,那麼數據就會寫失敗,從節點的數據就丟了。

所以,用這種異步方式,就需要容忍一部分的數據不一致。
異步

或者,假設我們不希望數據丟失,儘量保證最終一致性,那麼還可以對上邊的異步模式做一個改進。
於是,可以用一個可靠的消息中間件,各種 mq、kafka 集羣等,
寫入主節點之後,不直接將數據同步到從節點,而是先存入 kafka 集羣,然後,再從 kafka 將數據刷到從節點。
這樣,由於 kafka 集羣保證可靠,再加上響應的速度足快,
既可以迅速返回響應,又可以保證數據最終一致性。
最終一致性
現在,我們就可以看出,雖然捨棄了強一致性,可以提高系統的可用性,
但是,不保證強一致,就必然會出現這麼一種情況:

假設,客戶請求了一個隨機的 redis 結點,然後,這個時候,由於數據還沒有被同步過來,就會取到還沒有同步的數據,也就是取到了不一致的數據。
這就是無法避免的一種情況,所以這就需要我們系統架構時的取捨。

主仍然是單點

對於上面提到的,採用主從的這麼一種方式,也就是主可讀寫,從只讀。
還有主備模型,客戶端只操作主,而不能訪問備。

不論是哪一種,都只有一個主,也就是,主,自己就是一個單點,
那麼無論有多少從再後面跟着,從掛了沒事,
但是,主掛了,立刻就是單點故障。

所以,就涉及到,要對主做高可用(HA)。

高可用不是指主不會出問題,而是指,在主掛掉了之後,立刻會有一個節點來頂替它,
對外的表現是,從來沒有出現過問題。

所以,要解決主的單點問題,那麼,就可以在主掛掉之後,人工把一個從、或者一個備,去設置爲主,讓其它節點去追隨它。
但是,很多時候,我們都是希望一切是自動化的,
人畢竟手速有限,當發現主掛了之後,要去親手操作,肯定會花費一定時間,
此外,由於主掛掉的情況,畢竟只是少數,所以大部分的監守都是無效的,只有偶爾,很少的情況,纔會主掛掉,找備頂替。
而且人需要吃飯,上洗手間,睡覺,休息,過成人生活…不可能 24 小時一直盯着機器。
所以人工監控的成本是很高的。

那麼,我們就需要由機器,來自動幫我們完成這件事。
其實,機器和人也很相像,人會休息、睡覺、生病,所以不能 24 小時可靠;
監控程序也會故障,所以也是不可靠的,只要是一個程序,它就會有單點故障的問題;
因此,監控主節點,也需要多個結點,組成一個集羣。
監控集羣

但是,用多個節點去監控一個 redis 進程,是否會存在什麼問題??

假設,我們現在有三個監控程序,去監控一個 redis 進程;
那麼,它們要怎樣才能確定,這個 redis 進程到底是好好的呢?還是已經掛掉了。

要是它們三個全部給出它掛了,它纔是掛了,
那是不是又回到上面說的,這是強一致性啊。

假設,三個結點,有一個結點,網絡延遲,然後給出結論不一樣了,那就是監控程序此刻不可用了。
也就是強一致性,再一次降低了可用性。

那麼,既然如此,不能採取強一致的策略,
我們選擇,讓部分結點給出決策,但是,隨之,又引出了新的問題,
該聽誰的好?

要是有人說,主還活着,
有些人愣是認爲,主已經掛了,必須趕快換一個新的主上去,這該怎麼辦?

先不要急着給出答案,我們來推導,
因爲即使你知道答案,但是一步步發現和解決的問題你不知道,那麼你的整個思維邏輯,也是不健全的。

現在,假設有 3 個結點,那麼,就可以給出兩種情況:
一是,一個人說了就算
二是,兩個人說了纔算
(三個人的話就強一致了,所以就不用考慮)

假設,現在一個人說了算的話,那麼比如 A,說主死了,就要把它踢掉,
這時候,很可能其他節點都和主聯繫正常,主也沒有任何問題,
很可能,只是這個節點,自身的連接出了問題。

那麼,情況就有可能統計不準確。

此外,由於還有另外兩個結點,要是它們也分別給出自己的意見,人人各執己見,那麼這個集羣就亂套了,
人人都能給出決策,那麼就代表人人都無法給出決策。

那麼,假設,現在,我們要求,必須兩個結點達成一致,才能給出決定。
即使有一個結點給出不同的答案,由於另外兩個保持了一致的決策,它們就可以負責,對主的存活情況給出反饋,決定是否要替換主;
而另一個結點,由於沒有達到 2個 這麼一個勢力範圍,所以,它沒有權力給出意見;

因此,整個集羣,給出的意見永遠會是一致的。

也就是,我們的集羣,給出意見,需要過半!

因爲不過半的話,就會有可能不同勢力範圍的結點,給出不同的結論,
從而導致腦裂,
集羣分區。

不過要注意一點,並不是說,腦裂,就一定不好,就一定要避免。
實際上,對於 CAP,有一個 P,叫分區容忍性,
也就是,雖然網絡分區了,但是這種情況也可以接受。

比如,對於一個大片的集羣,此時對於它們的監控,不一定非要用過半來保證數據結論的一致。
就算,產生了腦裂,
也就是客戶訪問一部分程序,得出結果是還有 2 臺機器可用;另一部分程序,給出反饋 4 臺機器可用;
雖然程序給出的結論產生了不一致,但是這不影響具體的使用,
因爲,只要有機器存活,那麼只需要選出一臺機器提供服務就可以了,而不必去糾結到底有多少臺還可用。

所以,對於分區的容忍性,是要看承載的數據是什麼東西。
比如,上面提到的,對於一個集羣有多少可用,就不一定非要數據全部一致;
但是,要是有一個 key,它的 value 是 1,還是 2,這能否允許分區,一般是不會被允許的。

所以,這還得看實際對數據的要求。

所以,對於監控主節點的過半決策,則就是避免了分區導致的結論不一致,
這裏是不容許給出不一致的結論的。
2
所以,我們只需要讓過半給出結論即可。
但是,我再拋出一個問題,結點數量用奇數臺,還是偶數臺?

你可能以前沒有思考過這個問題,
要是,一共有三個結點,那麼就必須保證兩臺結點是好的,那麼,整個集羣纔是可用的,能給出決策的。
也就是允許掛一臺,
只要掛了兩臺,整個集羣就不可用。

繼續,要是整個集羣有 4 臺,
那麼,過半就是 3 臺,
也就是隻允許最多掛掉 1 臺,要是掛掉了 2 臺,就會導致整個集羣不可用。

現在,同樣是只允許掛掉一臺,掛掉兩臺就不可用了,
現在,要是總體是 3 臺,那麼就代表掛掉 66.7% 不可用;
要是總體是 4 臺,那麼就代表掛掉 50% 不可用;
4 臺中,掛掉 2 臺的概率會比 3 臺中掛掉 2 臺的概率更加大,
也就是,採用偶數臺會增加風險,這時絕對不允許的。

數據分片

之前提到的主從複製,也就是基於 AKF 的 x軸,它可以對主做 HA(高可用),
也可以對從節點進行讀寫分離,或者從不去讀,僅僅做一個備機用。

但是,這沒有解決一個問題,就是容量有限的問題。

一般我們的 redis 進程,我們都只會給它分配幾個 G 的大小,
即便我們的一臺服務器可能有上百 G 的內存,但是我們也不可能真的給我們的 redis 分配一百多 G 的內存,
因爲這個持久化所消耗的時間會很大。
所以我們一般會控制我們的 redis 實例,讓它只吃幾個 G 的內存,這樣它就會更輕盈,效率也會更高。

所以,一但數據量很大的時候,我們就需要對 redis 進行縱向集羣擴容。

首先,我們可以參考上面提到的 AKF 的 y軸 做縱向業務拆分,
這樣把存儲的數據,在客戶端層面就決定好,哪一部分的數據應該存到哪,
這樣,使得每一部分的 redis 集羣,只需要負責存儲一部分的數據即可。

但是,這有一個問題,就是所有的業務拆分需要寫在客戶端的代碼中,這就會造成客戶端代碼複雜化,
每個 redis 還是一樣,它們是不用關心自己存儲了哪一部分的數據。
客戶端拆分業務
業務拆分確實可以解決部分問題,但是,如果數據量很大,光拆分了業務之後,還是每個業務擁有大量數據;
其次,有些情況下,業務是很難拆分的,或者業務已經拆分得足夠細小了,無法再進一步拆分了。

這樣的情況下,就無法再對業務進行拆分了,那我們就得用其他方式,來從非業務的角度,來拆分數據。

於是就進入 sharding 分片的模式。

首先,最容易想到的就是哈希+取模,大部分程序員對哈希都是比較熟悉的。
這樣,我們只需要在客戶端的代碼中融入哈希取模的方式,來進行存取數據,就可以實現數據的分片。

但是,這種方式最爲簡單,但是,問題也很顯然:
就是取模的數值,是固定的,假設原本有 3 臺機器,那麼取模之後假設是第一臺,
如果公司業務發展,數據量增大,想要增加集羣數目,那麼如果添加一臺,那麼就很可能導致,原先取模之後本來存儲在第一臺上的數據,現在取模之後,結果到第二臺了,那麼就會使得數據大片失效。

所以,這種哈希取模的方式,會影響分佈式系統的擴展性。
哈希取模
那我們繼續探討,假設,不用哈希取模,那麼用隨機的策略。
這樣的話,客戶端自己都不知道自己把數據塞哪了,那要怎麼去取?

所以,這個方式的適用場景比較窄。
一般,只有不要求取指定單獨數據的話,可以做一個消息隊列的集羣,
一端隨機往 redis 中不斷存儲數據,另一端隨機從 redis 中取數據,這樣就不用太在意數據被存到了哪,只要最終被消費掉即可。
消息隊列
還有一種方式,叫做一致性哈希算法。
聽名字好像和第一種很像,都有個哈希在裏面,不過它的區別在於,不用對哈希值進行取模。

說白了,哈希算法,也就是映射算法,將一堆各種各樣的數據,映射出一堆等寬的數據,
比如 crc16、crc32、fnv、md5 等等。

那麼,我們在對這些哈希處理的時候,更傾向於作爲一個環,哈希環。
一致性哈希算法
我在這裏不會對各種算法做詳細的解釋,你更多需要的,是理解這些分佈式的各種解決方案。
對於這個哈希環,它需要的不僅僅是對 key 進行哈希,也需要對各個 redis 結點進行哈希運算。

比如現在,有一個大的環,上面分成了 2的32次方 個點,分別代表了 0~2的32次方-1 的所有數值,
然後,我們的所有 redis 結點,我們都可以給它一個唯一的 id 號,
這樣,我們的結點,就可以經過一個固定的哈希算法,得到一個值,然後就可以映射到哈希環的某個點上。
一致性哈希環
於是我們的 redis 結點,就可以分佈到哈希環上。
這時,如果想要存儲數據,我們也可以對數據進行哈希,得到一個值,映射到哈希環上。

這樣,由於多個結點,將環分割,所以數據存儲的時候,在每兩個結點之間的那段數據,就歸一個節點所有,又一段數據,歸另一個結點所有。
一致性哈希環
爲什麼會搞出這麼一個東西出來,那麼你可以去想,現在這個哈希環上有三臺結點,
假設我們現在想要增加一臺,且這個節點哈希過後映射到哈希環上,恰巧是這個位置:

那麼,我們會發現,現在,按照哈希映射,原本由之前的一個節點存儲的數據,部分映射到了新增的結點上,
這樣,就會導致,在讀取數據時,會讀取不到數據,導致數據丟失。
也就是,新增一個節點,不會影響前面一個節點的數據,但會導致後一個節點的部分數據丟失。
一致性哈希環新增結點
不過,我們看問題也得有兩面性。
爲什麼會出現這個哈希環,其實也是因爲它的優點:
就是,在新增結點的時候,的確可以分擔其他節點的壓力,也就不會造成全局洗牌。
(之前哈希取模的時候,增加結點,會導致全部數據重新分配)

不過,它也不是完美的,也就是,在新增結點之後,會導致後邊一個節點的部分無法被命中。

所以,如果 redis 是作爲緩存,那麼就會導致擊穿,請求直接壓到後邊的 mysql 上。
那麼,也可以增加解決方案,就是取數據的時候,如果前一個結點取不到數據,
就額外再到後邊那一個結點取一次數據。

雖然可以使得數據又被取到,但是這樣也增加了業務邏輯複雜度,也增加了網絡的開銷,降低了一部分效率。

其實這裏還有一個小問題,就是數據在結點的分佈不均勻的問題:
假設,有兩個結點,但是在哈希環上的映射分佈不均勻,
這樣的話,大量數據都會壓到一個 redis 節點上,而另一個 redis 結點,則只承擔少量數據。
這樣則會使資源分配不合理,一個 redis 壓力大,一個 redis 資源浪費。
數據傾斜
所以,這就引申出虛擬結點這麼一個概念:
我們的一個 redis,不僅僅用一個點去表示它,
我們可以通過多個哈希函數,得到多個結果值,將一個節點,分佈在環的多個位置上,
這樣,就可以使得數據的分配,更爲均衡。
虛擬結點

所以,這些個方案由於這些種種問題,存在數據的丟失,所以更傾向於 redis 作爲緩存的時候去使用,
而不是作爲數據庫去使用。

所以,技術的選擇取決於業務的需求,也取決於人的判斷,沒有技術是一成不變的,也沒有什麼技術是完美無缺的。

成本問題

如果我們繼續探究的話,還能發現問題,

首先,無論採取什麼實現,客戶端都要爲此融入邏輯代碼來進行處理;

還有就是對於我們的 redis 結點,可能只有那麼幾個,但是用來連接 redis 的客戶端,數量非常多,
而且,並不是一個客戶端就是一個連接,我們的客戶端往往會有一個連接池,一個客戶端就建立了很多很多連接,需要連接的時候就直接從連接處取出一個。

所以,對於 redis,它的連接成本很高。
連接壓力
有此,我們可以想到,Nginx 是什麼,它可以作爲反向代理服務器,還有負載均衡。
也就是它自己不處理業務,不存儲數據,只負責接收來自客戶端的請求,把它代理到後端的服務器上。

這樣,redis 服務端的連接壓力就減輕了,
我們就只需要關注代理層的性能。

此外,客戶端的邏輯也可以遷移,由代理層來負責實現,
這樣,客戶端就可以很輕盈。

其次,還可以帶出來的一個詞,就叫無狀態。
代理層由於不需要存儲數據,不牽扯到任何客戶端的狀態,所以本身是可以很輕易的動態增刪的,
一個代理掛了,可以立刻添加上一個,
代理不夠了,可以再往裏面追加,所以本身是非常靈活的。
所以,一個東西,只要能做成無狀態,就可以很容易的一變多。

由此,我們又可以推導出下面這個模型:
proxy
要是連代理層 Nginx 都扛不住怎麼辦,那就可以對代理層再做一次集羣,其中的每一部分客戶端都只連接一個代理;
或者,我們也可以再統一接入一個負載均衡 LVS。

然後,就需要對負載做一個高可用,也就是創建一個備用機,用 keepalived 進行管理。
同時,也可以對後端的代理層監控健康狀態。

這樣,無論後端的技術有多麼複雜,對於客戶端來說都是透明的,因此,客戶端的 API 就可以極其簡單。
keepalived

數據預分區

上面探討了三種分片算法,哈希取模,隨機,一致性哈希。
看起來應該是一致性哈希最好對不對。

但是,不知道細心的你有沒有發現一個問題,就是好像這種方式,都只能讓 redis 在緩存的情況下纔可以使用,
也就是不適用於 redis 做數據庫的場景。

假設,現在有兩臺 redis 服務器,但是,不代表,過一年還是兩臺 redis 服務器。
那麼,不論分片邏輯是寫在客戶端裏,還是寫在代理層裏,
那麼,對分片算法,都是一個挑戰。

之前探討的哈希取模,以及一致性哈希,再此時,都會因爲其不足而放棄使用。
預分區
那麼,我們現在可以想,既然曾經是兩個結點,我們要取模的話就是 %2,
那麼,我們爲什麼不能直接在當初,就直接當成 3 個結點來進行取模分配?

所以,假設以後還會有很多次擴容,那麼,這次就直接乾脆點,一次性取模取 10 個,
這樣,現在的兩個結點,未來就可以直接增加到 10 個節點。

那這樣,我們就只需要在中間加一層 mapping,讓其中 0-4 這5份數據存到 1 結點,
然後讓 5-9 這部分數據,存到 2 結點。
預分配
這樣,比如,當增加第三個結點的時候,
只需要,比如,把 1 號的 3、4 槽位以及數據分給它,把 2 號的 8、9 槽位和數據分給它,
這樣,就可以繼續保持每個節點,持有固定槽位的數據,而不會產生數據丟失。
預分配
不過,這仍然要涉及數據遷移,可能有人會認爲這樣和上面所說的三種分片方式並沒有本質的區別。

實際上不是的,對於之前的哈希取模,或者還是一致性哈希環,它們在新增結點的時候,都需要對數據進行重哈希,重新定位新的數據應存儲在哪個結點。

而在預分配處理之後,只需要對每個 redis 加上一個哈希槽位的概念,
這樣,在進行新增結點的時候,不需要對數據進行重哈希,而只是把分配過去的槽位的數據進行遷移。

有人又會問,數據遷移會不會有什麼問題?
實際上,這就需要你對 redis 數據保存的知識的瞭解。

首先,在數據傳輸的過程中,肯定是先 RDB 把該傳的數據全部傳過去,
此外,由於服務不能停止中斷,所以,可能就會有新的數據增加或修改,因此,就需要 AOF 傳輸少量變化的數據,
這樣,它們就能將數據傳輸無誤。

其實,redis 還有做的很好地一點,就是連代理層都不用。

在取數據的時候,客戶端想連誰就連誰,
假設,客戶端要 get k1,然後,連接上了 1 號 redis;
然而此時,由於 k1 取模之後的結果是 4,也就是存在了 3 號 redis 中。

那麼,客戶端在連接 redis 之後,redis 就需要自身去判斷,這個 key 是不是應該存在我這個結點中的,
所以,redis 服務端就必須繼承一個算法,對 key 進行哈希取模,
然後,也必須知曉,其它 redis 中所擁有的槽位(也就是了解整個 redis 集羣中的槽位信息),

這樣,1 號 redis 就會告訴客戶端,這個 k1 不是存在我這裏,你要去 3 號 redis 去取,
然後,客戶端就可以再通過一次連接,取到正確的數據。
獲取數據

但是,數據分治,必然會帶來一個問題,就是
聚合操作很難實現,以及事務很難實現,
比如數據要取交集等等…

雖然 redis 作者可以給你實現,但是 redis 作者沒有給你實現,
這時爲什麼?
因爲這裏邊有數據移動的過程。

其實 redis 作者很細膩,它一般會讓計算向數據移動,而不是移動數據。

而且 redis 的最大特徵就是快,所以 redis 的作者一直在做一個取捨,就是把一部分功能抹殺掉。

所以 redis 自己不去實現這個東西。
不過沒關係,我們人可以實現;
所以這個鍋 redis 不背,但是要由人來背。

這個鍋就叫:hash tag

因爲數據一但被分開,就很難再被合併處理;
換言之,如果數據不被分開,那就可以進行事務。

假設有這麼兩個 key:
一個是:abc-k1,一個是 abc-k2;
那麼,我們需要讓這兩個 key 存到一起,那麼就可以只對這個 abc 取模,
而不是對整個 key 取模。

這樣,redis 不背這個鍋,我不幫你移動數據;
但是,你也不能就把這個鍋背上,起碼你得把這幾個 key 給放到一起去。

這樣,就依然可以執行事務,因爲需要在一個事務的數據都在一個 redis 裏。

綜上

其實,你們應該也可以理解 redis 的作者,在涉及 redis 時候,他的一個出發點,
就是追求一個字:
快!

所以,我們很多時候,應該儘可能地,去利用 redis 的特性,來增強我們的系統。

而不是因爲技術而技術!

你想,redis 連多線程這塊領域都沒有去觸碰,足以說明,因爲簡單方而高效。

很多時候,我們過度的設計和包裝,都會導致原有的高性能程序,喪失了它原有的優勢。

所以更多時候,我們都會讓 redis,去儘可能只是做一個緩存,而非數據庫使用;
更多時候,不去追求數據的強一致性,而是允許一部分數據丟失;

所以,我們最需要的,是利用 redis 原有的優勢,而不是通過技術,去強加一些 redis 本不具備的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章