轉自:https://www.zhihu.com/question/354518943
鏈接:https://www.zhihu.com/question/354518943/answer/888891865
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
前言
Redis在互聯網技術存儲方面使用如此廣泛,幾乎所有的后端技術面試官都要在Redis的使用和原理方面對小伙伴們進行360°的刁難。作為一個在互聯網公司面一次拿一次offer的面霸(請允許我使用一下誇張的修辭手法),打敗了無數競爭對手,每次都只能看到無數落寞的身影失望的離開,略感愧疚,在一個寂寞難耐的夜晚,我痛定思痛,決定開始寫《吊打面試官》系列,希望能幫助各位讀者以后面試勢如破竹,對面試官進行360°的反擊,吊打問你的面試官,讓一同面試的同僚瞠目結舌,瘋狂收割大廠Offer!
絮叨
寫這期其實比較糾結,我之前的寫的比較通俗易懂,一是我都知道這些點,二是之前我在所在的電商公司對雪崩,擊穿啥的還算有場景去接觸。但是線上的Redis集群我實際操作經驗很少,總不能在公司線上環境實踐那些操作吧,所以最后看了下官網,還有一些資料(文章后面我都會貼出來),強行懟了這么篇出來。
最近雙十一小忙,周末雙十一值班目測沒時間寫,那我是暖男呀,我不能鴿啊,就有了這一篇,下一篇遲到你們不要噴我哈,而且下一篇還是Redis的終章還是得構思下,不熟悉的知識點我怕漏洞多,特意讓以前的大牛同事看了下,所以有啥不對的地方大家及時留言Diss我,寫這篇是真的難,諾下面就是我本人某天凌晨兩點的拍的視頻,多動症的仔。

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


咋樣沒騙你們吧,就很枯竭,不BB了,開搞。
不點個贊對不起我,這次不要白嫖我!
正文
上幾期《吊打面試官》還沒看的小伙伴可以回顧一下(明明就寫了兩期說的好像很多一樣)!
- 《吊打面試官》系列-Redis基礎
- 《吊打面試官》系列-緩存雪崩、擊穿、穿透
大家都知道一個技術的引入方便了開發,解決了各種問題,但是也會帶來對應的問題,技術是把雙刃劍嘛,集群的引入也會帶來很多問題,如:集群的高可用怎么保證,數據怎么同步等等,我們話不多說,有請下一位受害者為我們展示。
面試開始
三個大腹便便,穿着格子襯衣的中年男子,拿着三個滿是划痕的mac向你走來,看着快禿頂的頭發,心想着肯定是尼瑪頂級架構師吧!而且還是三個,但是還好我看過敖丙寫的《吊打面試官》系列,腹有詩書氣自華,根本虛都不虛好伐。

小伙子你好,之前問過了你基礎知識以及一些緩存的常見幾個大問題了,那你能跟我聊聊為啥Redis那么快么?
哦,帥氣迷人的面試官您好,我們可以先看一下關系型數據庫跟Redis本質上的區別。

Redis采用的是基於內存的采用的是單進程單線程模型的 KV 數據庫,由C語言編寫,官方提供的數據是可以達到100000+的QPS(每秒內查詢次數)。
- 完全基於內存,絕大部分請求是純粹的內存操作,非常快速。它的,數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間復雜度都是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里面的數據,這種方式,有沒有覺得很適合做冷備,完整的數據運維設置定時任務,定時同步到遠端的服務器,比如阿里的雲服務,這樣一旦線上掛了,你想恢復多少分鍾之前的數據,就去遠端拷貝一份之前的數據就好了。
RDB對Redis的性能影響非常小,是因為在同步數據的時候他只是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 地址。
我記得你還提到了主從同步,能說一下主從之間的數據怎么同步的么?
面試官您的記性可真是一級棒呢,我都要忘了你還記得,我特么謝謝你,提到這個,就跟我前面提到的數據持久化的RDB和AOF有着比密切的關系了。
我先說下為啥要用主從這樣的架構模式,前面提到了單機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的過期策略,是有定期刪除+惰性刪除兩種。
定期好理解,默認100s就隨機抽一些設置了過期時間的key,去檢查是否過期,過期了就刪了。
為啥不掃描全部設置了過期時間的key呢?
假如Redis里面所有的key都有過期時間,都掃描一遍?那太恐怖了,而且我們線上基本上也都是會設置一定的過期時間的。全掃描跟你去查數據庫不帶where條件不走索引全表掃描一樣,100s一次,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實現的理論中,我們希望的是,在舊鍵中的第一半將會過期。Redis的LRU算法則是概率的過期舊的鍵。
你可以看到,在都是五個采樣的時候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中文官網
- 《Redis入門指南(第2版)》
- 《Redis實戰》
- 《Redis設計與實現》
- 《大型網站技術架構——李智慧》
- 《Redis 設計與實現——黃健宏》
- 《Redis 深度歷險——錢文品》
- 《億級流量網站架構核心技術——張開濤》
- 《中華石杉——石杉》