Redis-0-目錄

羊37發表於2024-06-09

0.背景

本文,參考B站博主軒轅的程式設計宇宙-趣話Redis系列進行整理

由於最近複習了Redis相關內容,自己整理了筆記,所以剛好結合博主聊到的內容串一下。

字幕,藉助: Greasy Fork中的Bilibili CC字幕工具整理

內容段落整合,由GPT完成。

1.內容

1.1 Redis快取管理機制

你好,我是REDIS。一個叫Antirez的男人把我帶到了這個世界上。

說起我的誕生,跟關聯式資料庫MYSQL還挺有淵源的。

在我還沒來到這個世界上的時候,MYSQL過得很辛苦。

網際網路發展的越來越快,它容納的資料也越來越多,使用者請求也隨之暴漲。

而每一個使用者請求都變成了對他的一個又一個讀寫操作,MYSQL是苦不堪言。

尤其是像618這種全民購物狂歡的日子,都是MYSQL受苦受難的日子。

據後來他告訴我說,其實有一大半的使用者請求都是獨操作,而且經常都是重複查詢一個東西,浪費他很多時間去進行磁碟IO。

後來有人就琢磨是不是可以學學CPU,給資料庫也加一個快取呢?

Redis-6-三種快取讀寫策略

於是我就誕生了。

出生不久,我就和MYSQL成為了好朋友。

我們倆常常攜手出現在後端伺服器中。

應用程式們從MYSQL查詢到的資料,在我這裡登記一下,後面在需要用到的時候就先找我要,我這裡沒有再找MYSQL。

要為了方便使用,我支援好幾種資料結構的儲存。

因為我把登記的資料都記錄在記憶體中,不用去執行慢如蝸牛的IO操作,所以找我要比找MYSQL要省去了不少的時間。

可別小瞧這簡單的一個改變,我可謂MYSQL減輕了不小的負擔。

Redis-1-底層資料結構、為什麼快

Redis-2-基本資料型別

隨著程式的執行,我快取的資料越來越多,有相當部分時間我都給他擋住了使用者請求。

不過很快我發現事情不妙了,我快取的資料都是在記憶體中,不能無節制的這麼存下去,我得想個辦法,不然吃棗藥丸。

不久我想到了一個辦法,給快取內容設定一個超時時間,具體設定多少時間,我不管交給應用程式自己來。

過期時間淘汰策略

我要做的就是把過期了的內容從我裡面刪除掉,其實騰出空間就行了。

我決定100ms就做一次,一秒鐘就是十次。

我清理的時候也不能一口氣把所有過期的都給刪除掉,我這裡記憶體了大量的資料,要全面掃一遍的話,那不知道要花多久時間,會嚴重影響我接待新的客戶請求的時間緊任務重,我只好隨機選擇一部分能緩解記憶體壓力就行了。

就這樣過了一段日子,我發現有些個鍵值運氣比較好,每次都沒有被我的隨機演算法選中,這個不行。

於是在原來定期刪除的基礎上又加了一招,那些原來逃脫我隨機選擇演算法的鍵值,一旦遇到查詢請求被我發現已經超期了,那我就絕不客氣,立即刪除。

這種方式,因為是被動式觸發的,不查詢就不會發生,所以也叫惰性刪除。

記憶體淘汰策略

可是還是有部分兼職既逃脫了我的隨機選擇演算法,又一直沒有被查詢,導致他們一直逍遙法外,而與此同時,可以使用的記憶體空間卻越來越少。

而且就算退一步講,我能夠把過期的資料都刪除掉,那萬一過期時間設定的很長,還沒等到我去清理記憶體,就吃滿了,一樣要吃棗藥丸。

所以我還得想個辦法,我苦思良久,終於憋出了個大招,記憶體淘汰策略。

這一次我要徹底解決問題,我提供了八種策略供應用程式選擇,用於我遇到記憶體不足時該如何決策。

有了上面幾套組合拳,我再也不用擔心過期資料多了,把空間充滿的問題了。

Redis-3-過期時間淘汰策略與記憶體淘汰策略

我的日子過得還挺舒坦,不過MYSQL大哥就沒我這麼舒坦了。

有時候遇到些煩人的請求,查詢的資料不存在,MYSQL就要白忙活一場。

不僅如此,因為不存在,我也沒法快取啊,導致同樣的請求來了,每次都要去讓MYSQL白忙活一場,我作為快取的價值就沒得到體現了,這就是人們常說的快取穿透。

有一次MYSQL那傢伙正悠哉悠哉的摸魚,突然一大堆請求給他懟了過去,給他打了一個措手不及。

一陣忙活之後,MYSQL怒氣衝衝的找到了我兄弟,咋回事啊,怎麼一下子來得這麼猛?

我檢視了日誌,趕緊解釋道:大哥實在不好意思,剛剛有一個熱點資料到了過期時間被我刪掉了,不巧的是,隨後就有對這個資料的大量查詢請求來了,我這裡已經刪了,所以請求都發到你那裡來了。

你這蛋子叫啥事,下次注意點啊。MYSQL大哥一臉不高興的離開了。

這一件小事,我也沒怎麼放在心上,隨後就拋之腦後了。

卻沒曾想幾天之後竟捅了更大的簍子。那一天又出現了大量的網路請求,發到了MYSQL那邊。上一次的規模大得多,MYSQL大哥一會功夫就給單趴下了好幾次,等了好半天,這一波流量才算過去,MYSQL才緩過神來。

老弟這次又是什麼原因?MYSQL大哥累得沒了力氣,這一次比上一次更不巧,這一次是一大批資料幾乎同時過了有效期,然後又發生了很多對這些資料的請求。

所以比起上一次這規模更大了。

MYSQL大哥聽了眉頭一皺,那你倒是想個辦法呀,三天兩頭折磨我,這誰頂得住啊。

其實我也很無奈,這個時間也不是我設定的,要不我去找應用程式說說,讓他把快取過期時間設定的均勻一些,至少別讓大量資料集體失效。

走咱倆一起去。

後來我倆去找應用程式商量了,不僅把兼職的過期時間隨機了一下,還設定了熱點資料永不過期,這個問題緩解了不少。

我們終於又過上了舒坦的日子。

哦對了,我們還把這兩次發生的問題分別取了個名字:快取擊穿和快取雪崩。

Redis_快取穿透、雪崩以及擊穿

有一天我正在努力工作中,不小心出了錯,整個程序都崩潰了。

當我再次啟動後,之前快取的資料全都沒了,暴風雨式的請求再一次全都對到了MYSQL大哥那裡,被他噴了個狗血淋頭。

唉要是我能夠記住崩潰前快取的內容就好了。

1.2 Redis持久化儲存機制

Redis-4-持久化

你好,我是REDIS。一個叫安特瑞斯的男人把我帶到了這個世界上。

上回說到有一次我不小心崩潰了。等我重新啟動後,MYSQL大哥已經急得團團轉了。

"你總算起來了,剛才咋回事啊?又一大堆查詢請求發到我這來了!"

"唉,別提了,我剛才執行觸發了bug,整個程序都崩潰了。"

"你這也太不靠譜了,趕緊起來幹活吧!"

"糟了,我之前快取的資料全都不見了。"

"什麼?你的資料沒有做持久化儲存嗎?"

MYSQL大哥一聽,臉色都變了。我尷尬地搖了搖頭。

"我都是儲存在記憶體中的,所以才那麼快啊。"

"那也可以在硬碟上儲存一下,遇到這種情況,全部從頭再來建立快取,這不浪費時間嗎?"

我點了點頭,讓我琢磨一下,看看怎麼做這個持久化。

沒幾天,我就拿出了一套方案,RDB。

既然我的資料都在記憶體中存放著,最簡單的就是遍歷一遍,把它們全都寫入檔案中。為了節約空間,我定義了一個二進位制的格式,把資料一條一條碼在一起,生成了一個RDB檔案。不過我的資料量有點大,要是全部備份一次得花不少時間,所以不能太頻繁地去做這事,要不然我不用幹正事了,光花時間去備份了。還有啊,要是一直沒有寫入操作,都是讀取操作,那我也不用重複備份,浪費時間。思來想去,我決定提供一個配置引數,既可以支援週期性備份,也可以避免做無用功。就像這樣,多個條件可以組合使用。後來我又想了一下,這樣還是不行,我得建立一個子程序去做這件事,不能浪費我的時間。

有了備份檔案,下次我再遇到崩潰退出,甚至伺服器斷電罷工了,只要我的備份檔案還在,我就能在啟動的時候讀取,快速恢復之前的狀態了。我帶著這套方案興沖沖地拿給了MYSQL大哥看了,期待他給我一些鼓勵。

"老弟你這個方案有點問題啊。"

沒想到他竟給我澆了一盆冷水。

"問題?有什麼問題?"

"你看啊,你這個週期性備份週期還是分鐘級別的。你可知道咱們這服務每秒鐘都要響應多少請求,像你這樣不得丟失多少資料。"

MYSQL語重心長地說道。我一下有些氣短了。

"可是這個備份一次要遍歷全部資料,開銷還是挺大的,不是個高頻執行啊。"

"誰叫你一次遍歷全部資料了,來來來,我給你看個東西。"

MYSQL大哥把我帶到了一個檔案目錄下。

"看,這些是我的二進位制日誌(binlog),你猜猜看裡面都裝了些什麼?"

MYSQL大哥指著這一堆檔案說道。

我看了一眼,全是一堆二進位制資料,這哪看得懂,我搖了搖頭。

"這裡面記錄了我對資料執行更改的所有操作,像是insert、update、delete等等動作。等我要進行資料恢復的時候,就可以派上大用場了。"

聽他這麼一說,我一下來了靈感。告別了MYSQL大哥,回去研究起新的方案來了。你們也知道我也是基於命令式的,每天的工作就是響應業務程式發來的命令請求。回來以後,我決定照葫蘆畫瓢,學著MYSQL大哥的樣子,把我執行的所有寫入命令都記錄下來,專門寫入了一個檔案,並給這種持久化方式也取了一個名字AOF。

不過我遇到了RDB方案同樣的問題,我該多久寫一次檔案呢?我肯定不能每執行一條寫入命令,就記錄到檔案中,那會嚴重拖垮我的效能。我決定準備一個緩衝區,然後把要記錄的命令先臨時儲存在這裡,然後再擇機寫入檔案。我把這個臨時緩衝區叫做AOF buffer。

這一次我不像之前那麼衝動,我決定先試執行一段時間,再去告訴MYSQL大哥,免得又被他戳到軟肋。試用了一段時間,各方面都執行良好。不過我發現隨著時間的推移,我寫的這個AOF備份檔案越來越大,不僅非常佔硬碟空間,複製移動載入分析都非常的麻煩耗時。我得想個辦法把檔案給壓縮一下,我把這個過程叫做AOF重寫。

一開始我打算去分析原來的AOF檔案,然後將其中的冗餘指令去掉,來給AOF檔案瘦瘦身。不過我很快放棄了這個想法,這工作量實在太大了,分析起來也頗為麻煩,浪費很多精力和時間。原來的一條條記錄,這種方式實在是太笨了,資料改來改去,有很多中間狀態都沒用。我何不就把最終的資料狀態記錄下來就好了,比如這三條指令可以合併成一條。搞定AOF檔案重寫的思路我是有的,不過這件事看起來還是很耗時間,我決定和RDB方式一樣,fork出一個子程序來做這件事情。

謹慎如我,發現這樣做之後,子程序在重寫期間,我要是修改了資料,就會出現和重寫的內容不一致的情況。MYSQL大哥肯定會挑刺,我還得把這個漏洞給補上。於是我在之前的AOF buffer之外,又準備了一個緩衝區,AOF重寫緩衝區。從建立重寫子程序開始的那一刻起,我把後面來的寫入命令也copy一份,寫到這個重寫緩衝區中。等到子程序重寫檔案結束之後,我再把這個緩衝區中的命令寫入到新的AOF檔案中,最後再重新命名新的檔案,替換掉原來的那個臃腫不堪的大檔案。終於大功告成,再三確定我的思路沒有問題之後,我帶著新的方案再次找到了MYSQL大哥。

我都做到這份上了,這次想必他應該無話可說了吧。MYSQL大哥看了我的方案,露出了滿意的笑容,只是問了一個問題:

"這AOF方案這麼好了,RDB方案是不是可以不要了呢?"

萬萬沒想到他居然問我這個問題,我竟陷入了沉思。你覺得我該怎麼回答好呢?

那天我不小心又掛掉了,MYSQL大哥找到了我。

"你怎麼又崩潰了?"

"不好意思,又遇到bug了。不過不用擔心,我現在可以快速恢復資料了。"

"那老崩潰也不是事,你只有一個例項,太不可靠了,去找幾個幫手吧。"

預知後事如何,請關注我,第一時間瞭解後續故事哦。

1.3 Redis哨兵與高可用原理

Redis-5-高可用

你好,我是REDIS。一個叫安特瑞斯的男人把我帶到了這個世界上。前兩個影片給大家講述了我的快取管理機制和持久化儲存原理,還沒看過的朋友可以點這個複習一下哦。

那天REDIS叢集裡許久未見的大白髮來了一條訊息。

"兄弟們最近工作咋樣?"

"大白,好久不見啊!"

"是啊,大白在哪發財呢?"

"還行吧,就是日常被MYSQL大哥DISS嫌我沒辦法高可用。我也被DISS了,我就一個伺服器咋可能高可用嗎?"

"我有一個想法,要不要兄弟們一起幹,票大的。"

"你想幹啥?"

"咱們組個團,用主從模式。主節點主要負責寫資料,從節點主要負責讀資料,然後做好資料同步讀寫分離,提高效能。另外主節點崩潰了,從節點就頂上去,還能實現高可用。這個主意怎麼樣?"

"大白牛逼!願意加入的回覆一。咱們拉個新的工作群。"

於是大白拉了一個新的群。

"歡迎兩位老哥,大家團結協作,一定能幹出一番大事。"

"嗯嗯,我就先來當這個主節點了。主節點任務重要,負責資料寫入和同步。大家先當一段時間從節點,熟悉熟悉工作節奏。"

"好的好的。以後的日子中,咱們哥仨相互配合,日常工作中最多的就是資料同步了。"

"來,我剛剛把資料寫成了一個RDB檔案,來同步一下。"

"收到收到,繼續。還有一個命令列表。"

"這又是個啥玩意兒?"

"這是我剛剛生成RDB檔案期間,又收到的幾條資料修改命令。我快取起來了,現在一併發給你們。你們載入完RDB檔案後,記得也要執行一下這些命令,這樣我們的資料才能一致。如果主節點有資料寫入、刪除、修改命令,也會把這些命令挨個通知到從節點。我們把這叫做命令傳播。透過這樣的方式,我們主節點與從節點之間資料就能保持同步了。"

有一次我不小心掉線了。

"不好意思,各位,我這網路抽風,剛才不小心掉線了。"

"沒關係,我把最新的資料寫到RDB檔案發給你,同步一下就好了。"

"先別急,我只是掉線了一小會兒,沒必要把全部資料都發給我吧,太浪費時間了。"

"那咋整呢?我也不知道你那裡差了哪些資料。"

"我有個建議,主節點大白,你內部準備一個緩衝區,後面傳播命令的時候,除了同步給我們從節點,也往緩衝區寫一份。下次我們再掉線了,你就把最近的命令發給我們就好了,就不用全部從頭再來了。"

"好主意,在讓我想想啊,我怎麼知道我快取的和你們缺失的能不能對得上呢?又怎麼知道該發快取中的哪些給你們呢?"

"要不咱們弄個遊標吧,叫做複製偏移量。最開始從零開始,隨著資料複製和同步,大家一起更新。後面只需要比較各自的偏移量,就能知道缺失哪些資料了。"

我們用上了新的資料同步策略,效率高了不少。就算偶爾掉線,也能很快把缺失的資料給補上。

就這樣過了一段時間,咱們雖然有主從複製,但主節點要是掛了,還是需要程式設計師們來手動選擇從節點升級為主節點來提供寫入服務,感覺不夠智慧啊。

"確實是有這個問題。要不這樣,咱們選一個人出來當管理員,不用負責資料的讀寫,專門來統籌協調,誰要是掉線了,就在從節點裡面選擇一個出來頂上。我看行,就把這個管理員叫哨兵吧。"

"好主意。不過一個管理員怕是不夠,萬一管理員掛了,那不完蛋了。"

"得多找幾個管理員。可是咱們人手有限,都做管理員,誰幹活呀?"

"之前基友群有幾個小弟加我了,都想跟我混。我拉進來,讓他們去做資料讀寫,咱們仨都來做管理員吧。"

"嗯嗯嗯,歡迎新朋友,歡迎歡迎。"

"R1、R2、R3,你們三位以後就負責資料讀寫了。R1你先來當主節點,我和小黑、小R3負責監控各位的工作情況。以後工作上的事情,我們主要和主節點R1來溝通了。辛苦大家了。為了及時獲得和更新主從節點的資訊,咱們哨兵每隔十秒鐘就要用INFO命令去問候一下主節點,主節點會告訴我他有哪些從節點。為了更加及時知道大家是否掉線,咱們哨兵每隔一秒都要用PING命令問候一下群裡的各個小夥伴。如果在設定的時間裡沒有收到回覆,我就知道這傢伙多半是掛了,就該啟動故障轉移了。"

"不過這只是我的主觀意見,光我一個人說了不算。為了防止誤判,我還得去管理員小群裡徵求一下大家的意見。"

"咋又拉一個群?"

"這是咱們管理員哨兵群,以後有什麼事就在這裡溝通吧。"

"可以可以。老鐵們,我發現R1這個節點好像掛掉了。"

"沒有吧,我剛還跟他聯絡過呢。"

"那到底是掉沒掉線?這不行啊,以誰說的為準呢?"

"咱們得定個規矩吧。我有個提議,管理員發現主節點掉線後,這時候判定為主觀下線,然後來這個群確認一下。如果有多個管理員都判定為下線,才能認定為客觀下線。具體需要幾個管理員同時認定,大家可以自己定義。"

"同意。"

"沒問題。"

"咦,我發現我好像也聯絡不上R1了。"

"我看看,還真是。看來R1真的掛了。現在總該啟動故障轉移了吧。知道怎麼做故障轉移嗎?"

"知道,簡單得很。第一步,選個新主節點;第二步,讓其他從節點從新的主節點那裡同步資料;第三步,把原來舊的主節點改成從節點。加油兄弟們,現在還有R2和R3兩個節點,該扶誰上位啊?看來還得定一套選擇新的主節點的規則才行。大家可以想想思路。我先提一個,可以給不同節點設定優先順序,硬體配置高的優先順序越高,挑選的時候參考這個優先順序來。"

"那我也提一個,可以優先選擇跟主節點斷開連線最短的節點,這樣它的資料會新一點。還可以參考一下複製偏移量,複製偏移量越大的資料應該越全。"

"可以,那就選擇優先順序更高,複製偏移量最大的節點。"

"同意。"

"同意。"

經過一番努力,我終於完成了故障轉移。以上就是我們的日常工作了。透過咱們幾個小夥伴的齊心協力,構成了一個高可用的快取服務。我高興地把這個訊息告訴了MYSQL大哥,沒想到又一次被DISS。

"難道不同節點還可以儲存不同的資料?"

想知道後面的故事,記得幫我三連支援一下,下一期告訴你我們Redis叢集的工作原理。

1.4 Redis叢集是如何工作的

Redis-5-高可用

你好,我是REDIS。一個叫安特瑞斯的男人把我帶到了這個世界上。自從上次被拉入群聊之後,我就從一個人單打獨鬥變成了團隊合作。在小夥伴們的共同努力下,不僅有儲存複製、可以備份資料,還有哨兵節點負責監控管理。我現在也可以拍拍胸脯說我們是高可用服務了。

但是幸福的日子沒過太久,我們就笑不起來了。隨著業務的發展,資料量越來越大,我們承受了巨大的壓力。雖然有主從複製加哨兵,但只能解決高可用的問題,解決不了資料量大的問題。因為我們看起來人手多,但都是儲存的全量資料,所以對於資料容量提升並沒有什麼幫助。

這一天我找到了大白和小黑,咱們仨合計了一下,一個節點的力量不足,但眾人划槳可以開大船啊。我們決定把三個人的記憶體空間拼起來,每個人負責一部分資料,合體進化成一個大的快取伺服器,變成一個叢集。

既然是叢集,首要問題當然是團隊建設了。我們得想一套辦法來組建團隊,還要考慮到以後可能會擴容,會有新的夥伴加入我們。我們仨憋了半天,抄襲人家TCP的三次握手,也搞了一個握手協議出來。想要加入叢集,得有一個介紹人才行,透過團隊裡的任何一個成員都行。

比如我吧,只要告訴我IP和埠,我就給他傳送一個meet資訊,發起握手。對方得回我一個pong資訊,同意入夥。最後我再回他一個ping資訊,三次握手就完成了。然後我再把這件事告訴團隊中其他成員,新的夥伴就算正式成為我們的一份子了。

第二件很重要的事情就是要解決資料儲存的公平問題,不能旱的旱死,澇的澇死。我們爭論了很久,最後決定學習人家雜湊表的方法。我們總共劃分了16384個雜湊槽位,我們把它叫做槽位(Slot)。程式設計師可以按照我們的能力大小,給我們各自分配一部分槽位。

比如我們團隊,我比較菜,只分到了4000個槽位,小黑老哥最辛苦,要負責7000多個槽位。正所謂能力越大,責任越大,誰叫他記憶體空間最大呢。資料讀寫的時候,對鍵值進行雜湊計算,對映到哪個槽,就由誰負責。

為了讓大家的資訊達成一致,啟動的時候,每個人都得把自己負責的槽位資訊告訴其他夥伴。我們準備了一個超大的陣列來儲存每個槽由哪個節點負責。這樣一來,遇到資料訪問的時候,我們就能快速知道這個資料是由誰來負責了。

對了,這16384個槽位必須都得有人來負責,我們整個叢集才算是正常工作,處於上線狀態。否則就是下線狀態。你想啊,萬一哪個鍵值雜湊對映後的槽位沒人負責,那該從哪裡讀,又該寫到哪裡去呢?

資料分配的問題解決了,我們團隊總算可以正式上線工作了。和原來不同的是,資料讀寫的時候多了一個步驟,得先檢查資料是不是由自己負責。如果是自己負責,那就進行處理,不然的話就要返回一個MOVED錯誤給請求端,同時把槽位、IP和埠告訴他,讓他知道該去找誰處理。

不過程式設計師們是感知不到的,他們都是用封裝好的庫來操作,才不會親自寫程式碼來跟我通訊呢。經過一段時間的磨合,我們叢集小分隊配合得越來越默契。

不過光靠咱們仨還是不行,萬一哪天有人掛了,整個叢集就得下線了。咱們三個每人至少得有一個backup才行。於是我找到了原來的一幫小弟,讓他們也加入我們,繼續給我們當起了從節點。平時當我們的backup從我們這裡複製資料,一旦我們遇到故障,他們就能快速頂上。

有了叢集工作加主從複製,我們現在不僅高可用,資料容量也大大提升了。就算以後不夠用了,也有辦法擴容。我們又過上了舒服的日子。

我是Redis,我的故事到這裡就告一段落了。

相關文章