《我們一起進大廠》系列-Redis哨兵、持久化、主從、手撕LRU

敖丙發表於2019-11-07

你知道的越多,你不知道的越多

點贊再看,養成習慣

GitHub上已經開源github.com/JavaFamily,有一線大廠面試點腦圖,歡迎Star和完善

絮叨

寫這期其實比較糾結,我之前的寫的比較通俗易懂,一是我都知道這些點,二是之前我在所在的電商公司對雪崩,擊穿啥的還算有場景去接觸。但是線上的Redis叢集我實際操作經驗很少,總不能在公司線上環境實踐那些操作吧,所以最後看了下官網,還有一些資料(文章後面我都會貼出來),強行懟了這麼篇出來。

最近雙十一小忙,週末雙十一值班目測沒時間寫,那我是暖男呀,我不能鴿啊,就有了這一篇,下一篇遲到你們不要噴我哈,而且下一篇還是Redis的終章還是得構思下,不熟悉的知識點我怕漏洞多,特意讓以前的大牛同事看了下,所以有啥不對的地方大家及時留言Diss我,寫這篇是真的難,諾下面就是我本人某天凌晨兩點的拍的視訊,多動症的仔。

(手機上動圖可能太大載入失敗,點進去這裡可以看看這個圖

之前說過系列第二篇到300讚我就發第三篇

咋樣沒騙你們吧,就很枯竭,不BB了,開搞。

不點個贊對不起我,這次不要白嫖我!


正文

上幾期《吊打面試官》還沒看的小夥伴可以回顧一下(明明就寫了兩期說的好像很多一樣)!

大家都知道一個技術的引入方便了開發,解決了各種問題,但是也會帶來對應的問題,技術是把雙刃劍嘛,叢集的引入也會帶來很多問題,如:叢集的高可用怎麼保證,資料怎麼同步等等,我們話不多說,有請下一位受害者為我們展示。

面試開始

三個大腹便便,穿著格子襯衣的中年男子,拿著三個滿是劃痕的mac向你走來,看著快禿頂的頭髮,心想著肯定是尼瑪頂級架構師吧!而且還是三個,但是還好我看過敖丙寫的《吊打面試官》系列,腹有詩書氣自華,根本虛都不虛好伐。

小夥子你好,之前問過了你基礎知識以及一些快取的常見幾個大問題了,那你能跟我聊聊為啥Redis那麼快麼?

哦,帥氣迷人的面試官您好,我們可以先看一下關係型資料庫跟Redis本質上的區別。

Redis採用的是基於記憶體的採用的是單程式單執行緒模型的 KV 資料庫,由C語言編寫,官方提供的資料是可以達到100000+的QPS(每秒內查詢次數)

  • 完全基於記憶體,絕大部分請求是純粹的記憶體操作,非常快速。它的,資料存在記憶體中,類似於HashMapHashMap的優勢就是查詢和操作的時間複雜度都是O(1);

  • 資料結構簡單,對資料操作也簡單,Redis中的資料結構是專門進行設計的;

  • 採用單執行緒,避免了不必要的上下文切換和競爭條件,也不存在多程式或者多執行緒導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的效能消耗;

  • 使用多路I/O複用模型,非阻塞IO;

  • 使用底層模型不同,它們之間底層實現方式以及與客戶端之間通訊的應用協議不一樣,Redis直接自己構建了VM 機制 ,因為一般的系統呼叫系統函式的話,會浪費一定的時間去移動和請求;

我可以問一下啥是上下文切換麼?

我可以打個比方麼:我記得有過一個小夥伴微信問過我上下文切換是啥,為啥可能會執行緒不安全,我是這麼說的,就好比你看一本英文書,你看到第十頁發現有個單詞不會讀,你加了個書籤,然後去查字典,過了一會你又回來繼續從書籤那裡讀,ok到目前為止沒啥問題。

如果是你一個人讀肯定沒啥問題,但是你去查的時候,別的小夥伴好奇你在看啥他就翻了一下你的書,然後溜了,哦豁,你再看的時候就發現書不是你看的那一頁了。不知道到這裡為止我有沒有解釋清楚,以及為啥會執行緒不安全,就是因為你一個人怎麼看都沒事,但是人多了換來換去的操作一本書資料就亂了。可能我的解釋很粗糙,但是道理應該是一樣的。

那他是單執行緒的,我們現在伺服器都是多核的,那不是很浪費?

是的他是單執行緒的,但是,我們可以通過在單機開多個Redis例項嘛。

既然提到了單機會有瓶頸,那你們是怎麼解決這個瓶頸的?

我們用到了叢集的部署方式也就是Redis cluster,並且是主從同步讀寫分離,類似Mysql的主從同步,Redis cluster 支撐 N 個 Redis master node,每個master node都可以掛載多個 slave node

這樣整個 Redis 就可以橫向擴容了。如果你要支撐更大資料量的快取,那就橫向擴容更多的 master 節點,每個 master 節點就能存放更多的資料了。

哦?那問題就來了,他們之間是怎麼進行資料互動的?以及Redis是怎麼進行持久化的?Redis資料都在記憶體中,一斷電或者重啟不就木有了嘛?

是的,持久化的話是Redis高可用中比較重要的一個環節,因為Redis資料在記憶體的特性,持久化必須得有,我瞭解到的持久化是有兩種方式的。

  • RDB:RDB 持久化機制,是對 Redis 中的資料執行週期性的持久化。
  • AOF:AOF 機制對每條寫入命令作為日誌,以 append-only 的模式寫入一個日誌檔案中,因為這個模式是隻追加的方式,所以沒有任何磁碟定址的開銷,所以很快,有點像Mysql中的binlog

兩種方式都可以把Redis記憶體中的資料持久化到磁碟上,然後再將這些資料備份到別的地方去,RDB更適合做冷備AOF更適合做熱備,比如我杭州的某電商公司有這兩個資料,我備份一份到我杭州的節點,再備份一個到上海的,就算髮生無法避免的自然災害,也不會兩個地方都一起掛吧,這災備也就是異地容災,地球毀滅他沒辦法。

tip:兩種機制全部開啟的時候,Redis在重啟的時候會預設使用AOF去重新構建資料,因為AOF的資料是比RDB更完整的。

那這兩種機制各自優缺點是啥?

我先說RDB

優點:

他會生成多個資料檔案,每個資料檔案分別都代表了某一時刻Redis裡面的資料,這種方式,有沒有覺得很適合做冷備,完整的資料運維設定定時任務,定時同步到遠端的伺服器,比如阿里的雲服務,這樣一旦線上掛了,你想恢復多少分鐘之前的資料,就去遠端拷貝一份之前的資料就好了。

RDBRedis的效能影響非常小,是因為在同步資料的時候他只是fork了一個子程式去做持久化的,而且他在資料恢復的時候速度比AOF來的快。

缺點:

RDB都是快照檔案,都是預設五分鐘甚至更久的時間才會生成一次,這意味著你這次同步到下次同步這中間五分鐘的資料都很可能全部丟失掉。AOF則最多丟一秒的資料,資料完整性上高下立判。

還有就是RDB在生成資料快照的時候,如果檔案很大,客戶端可能會暫停幾毫秒甚至幾秒,你公司在做秒殺的時候他剛好在這個時候fork了一個子程式去生成一個大快照,哦豁,出大問題。

我們再來說說AOF

優點:

上面提到了,RDB五分鐘一次生成快照,但是AOF是一秒一次去通過一個後臺的執行緒fsync操作,那最多丟這一秒的資料。

AOF在對日誌檔案進行操作的時候是以append-only的方式去寫的,他只是追加的方式寫資料,自然就少了很多磁碟定址的開銷了,寫入效能驚人,檔案也不容易破損。

AOF的日誌是通過一個叫非常可讀的方式記錄的,這樣的特性就適合做災難性資料誤刪除的緊急恢復了,比如公司的實習生通過flushall清空了所有的資料,只要這個時候後臺重寫還沒發生,你馬上拷貝一份AOF日誌檔案,把最後一條flushall命令刪了就完事了。

tip:我說的命令你們別真去線上系統操作啊,想試去自己買的伺服器上裝個Redis試,別到時候來說,敖丙真是個渣男,害我把伺服器搞崩了,Redis官網上的命令都去看看,不要亂試!!!

缺點:

一樣的資料,AOF檔案比RDB還要大。

AOF開啟後,Redis支援寫的QPS會比RDB支援寫的要低,他不是每秒都要去非同步重新整理一次日誌嘛fsync,當然即使這樣效能還是很高,我記得ElasticSearch也是這樣的,非同步重新整理快取區的資料去持久化,為啥這麼做呢,不直接來一條懟一條呢,那我會告訴你這樣效能可能低到沒辦法用的,大家可以思考下為啥喲。

那兩者怎麼選擇?

小孩子才做選擇,我全都要,你單獨用RDB你會丟失很多資料,你單獨用AOF,你資料恢復沒RDB來的快,真出什麼時候第一時間用RDB恢復,然後AOF做資料補全,真香!冷備熱備一起上,才是網際網路時代一個高健壯性系統的王道。

看不出來年紀輕輕有點東西的呀,對了我聽你提到了高可用,Redis還有其他保證叢集高可用的方式麼?

!!!暈 自己給自己埋個坑(其實是明早就準備好了,故意丟擲這個詞等他問,就怕他不問)。

假裝思考一會(不要太久,免得以為你真的不會),哦我想起來了,還有哨兵叢集sentinel

哨兵必須用三個例項去保證自己的健壯性的,哨兵+主從並不能保證資料不丟失,但是可以保證叢集的高可用

為啥必須要三個例項呢?我們先看看兩個哨兵會咋樣。

master當機了 s1和s2兩個哨兵只要有一個認為你當機了就切換了,並且會選舉出一個哨兵去執行故障,但是這個時候也需要大多數哨兵都是執行的。

那這樣有啥問題呢?M1當機了,S1沒掛那其實是OK的,但是整個機器都掛了呢?哨兵就只剩下S2個裸屌了,沒有哨兵去允許故障轉移了,雖然另外一個機器上還有R1,但是故障轉移就是不執行。

經典的哨兵叢集是這樣的:

M1所在的機器掛了,哨兵還有兩個,兩個人一看他不是掛了嘛,那我們就選舉一個出來執行故障轉移不就好了。

暖男我,小的總結下哨兵元件的主要功能:

  • 叢集監控:負責監控 Redis master 和 slave 程式是否正常工作。

  • 訊息通知:如果某個 Redis 例項有故障,那麼哨兵負責傳送訊息作為報警通知給管理員。

  • 故障轉移:如果 master node 掛掉了,會自動轉移到 slave node 上。

  • 配置中心:如果故障轉移發生了,通知 client 客戶端新的 master 地址。

我記得你還提到了主從同步,能說一下主從之間的資料怎麼同步的麼?

面試官您的記性可真是一級棒呢,我都要忘了你還記得,我特麼謝謝你,提到這個,就跟我前面提到的資料持久化的RDBAOF有著比密切的關係了。

我先說下為啥要用主從這樣的架構模式,前面提到了單機QPS是有上限的,而且Redis的特性就是必須支撐讀高併發的,那你一臺機器又讀又寫,這誰頂得住啊,不當人啊!但是你讓這個master機器去寫,資料同步給別的slave機器,他們都拿去讀,分發掉大量的請求那是不是好很多,而且擴容的時候還可以輕鬆實現水平擴容。

迴歸正題,他們資料怎麼同步的呢?

你啟動一臺slave 的時候,他會傳送一個psync命令給master ,如果是這個slave第一次連線到master,他會觸發一個全量複製。master就會啟動一個執行緒,生成RDB快照,還會把新的寫請求都快取在記憶體中,RDB檔案生成後,master會將這個RDB傳送給slave的,slave拿到之後做的第一件事情就是寫進本地的磁碟,然後載入進記憶體,然後master會把記憶體裡面快取的那些新命名都發給slave。

資料傳輸的時候斷網了或者伺服器掛了怎麼辦啊?

傳輸過程中有什麼網路問題啥的,會自動重連的,並且連線之後會把缺少的資料補上的。

大家需要記得的就是,RDB快照的資料生成的時候,快取區也必須同時開始接受新請求,不然你舊的資料過去了,你在同步期間的增量資料咋辦?是吧?

那說了這麼多你能說一下他的記憶體淘汰機制麼,來手寫一下LRU程式碼?

手寫LRU?你是不是想直接跳起來說一句:Are U F**k Kidding me?

這個問題是我在螞蟻金服三面的時候親身被問過的問題,不知道大家有沒有被懟到過這個問題。

Redis的過期策略,是有定期刪除+惰性刪除兩種。

定期好理解,預設100ms就隨機抽一些設定了過期時間的key,去檢查是否過期,過期了就刪了。

為啥不掃描全部設定了過期時間的key呢?

假如Redis裡面所有的key都有過期時間,都掃描一遍?那太恐怖了,而且我們線上基本上也都是會設定一定的過期時間的。全掃描跟你去查資料庫不帶where條件不走索引全表掃描一樣,100ms一次,Redis累都累死了。

如果一直沒隨機到很多key,裡面不就存在大量的無效key了?

好問題,惰性刪除,見名知意,惰性嘛,我不主動刪,我懶,我等你來查詢了我看看你過期沒,過期就刪了還不給你返回,沒過期該怎麼樣就怎麼樣。

最後就是如果的如果,定期沒刪,我也沒查詢,那可咋整?

記憶體淘汰機制

官網上給到的記憶體淘汰機制是以下幾個:

  • noeviction:返回錯誤當記憶體限制達到並且客戶端嘗試執行會讓更多記憶體被使用的命令(大部分的寫入指令,但DEL和幾個例外)

  • allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新新增的資料有空間存放。

  • volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限於在過期集合的鍵,使得新新增的資料有空間存放。

  • allkeys-random: 回收隨機的鍵使得新新增的資料有空間存放。

  • volatile-random: 回收隨機的鍵使得新新增的資料有空間存放,但僅限於在過期集合的鍵。

  • volatile-ttl: 回收在過期集合的鍵,並且優先回收存活時間(TTL)較短的鍵,使得新新增的資料有空間存放。

    如果沒有鍵滿足回收的前提條件的話,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。

至於LRU我也簡單提一下,手寫實在是太長了,大家可以去Redis官網看看,我把近視LUR效果給大家看看

tip:Redis為什麼不使用真實的LRU實現是因為這需要太多的記憶體。不過近似的LRU演算法對於應用而言應該是等價的。使用真實的LRU演算法與近似的演算法可以通過下面的影像對比。

LRU comparison
LRU comparison

你可以看到三種點在圖片中, 形成了三種帶.

  • 淺灰色帶是已經被回收的物件。
  • 灰色帶是沒有被回收的物件。
  • 綠色帶是被新增的物件。
  • LRU實現的理論中,我們希望的是,在舊鍵中的第一半將會過期。RedisLRU演算法則是概率的過期舊的鍵。

你可以看到,在都是五個取樣的時候Redis 3.0比Redis 2.8要好,Redis2.8中在最後一次訪問之間的大多數的物件依然保留著。使用10個取樣大小的Redis 3.0的近似值已經非常接近理論的效能。

注意LRU只是個預測鍵將如何被訪問的模型。另外,如果你的資料訪問模式非常接近冪定律,大部分的訪問將集中在一個鍵的集合中,LRU的近似演算法將處理得很好。

其實在大家熟悉的LinkedHashMap中也實現了Lru演算法的,實現如下:

當容量超過100時,開始執行LRU策略:將最近最少未使用的 TimeoutInfoHolder 物件 evict 掉。

真實面試中會讓你寫LUR演算法,你可別搞原始的那個,那真TM多,寫不完的,你要麼懟上面這個,要麼懟下面這個,找一個資料結構實現下Java版本的LRU還是比較容易的,知道啥原理就好了。

面試結束

小夥子,你確實有點東西,HRBP會聯絡你的,請務必保持你的手機暢通好麼?

好的謝謝面試官,面試官真好,我還想再面幾次,噗此。

能回答得這麼全面這麼細節還是忍不住點贊

(暗示點贊,每次都看了不點贊,你們想白嫖我麼?你們好壞喲,不過我好喜歡)

總結

好了,我們玩歸玩,鬧歸鬧,別拿面試開玩笑,我這麼寫是為了節目效果,大家面試請認真對待。

這一期是這期沒前面好理解了對吧,我就在自己的伺服器上啟動了,然後再去官網看看命令一頓瞎操作的,查閱了部分資料,這裡給大家推薦幾本經典的Redis入門的書籍和我參考的資料。

不出意外的話這是Redis的倒數第二期,最後一期不知道寫啥還沒想好,我得好好想想,加上最近不是雙十一嘛得加加班,你看看開頭的我,多可憐,那還不點個贊?買個伺服器?不確定下一期多久出,想早點看到更新的小夥伴可以去公眾號催更,公眾號提前一到兩天更新。

點關注,不迷路

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才

我後面會每週都更新幾篇一線網際網路大廠面試和常用技術棧相關的文章,非常感謝人才們能看到這裡,如果這個文章寫得還不錯,覺得「敖丙」我有點東西的話 求點贊? 求關注❤️ 求分享? 對暖男我來說真的 非常有用!!!

白嫖不好,創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見!

敖丙 | 文 【原創】

如果本篇部落格有任何錯誤,請批評指教,不勝感激 !


文章每週持續更新,可以微信搜尋「 三太子敖丙 」第一時間閱讀和催更(比部落格早一到兩篇喲),本文 GitHub github.com/JavaFamily 已經收錄,有一線大廠面試點思維導圖,也整理了很多我的文件,歡迎Star和完善,大家面試可以參照考點複習,希望我們一起有點東西。

相關文章