使用Redis——拳打南山敬老院,腳踩北斗幼兒園

Code2020發表於2020-05-20

拳打南山敬老院,腳踩北斗幼兒園

Redis 你說你用過對吧,你們怎麼用的?

面試官您好,因為傳統的關係型資料庫如Mysql已經不能適用所有的場景了,比如秒殺的庫存扣減,APP首頁的訪問流量高峰等等,都很容易把資料庫打崩,所以引入了快取中介軟體,目前市面上比較常用的快取中介軟體有RedisMemcached不過中和考慮了他們的優缺點,最後選擇了Redis

Redis有哪些資料結構?

StringHashListSetSortedSet

當然為了加分,可以扯一下,用過BloomFilter布隆過濾器

這玩意的使用場景是真的多,而且用起來是真的香,原理也好理解

如果有大量的key需要設定同一時間過期,一般需要注意什麼?

如果有大量的Key集中過期,Redis可能會出現短暫的卡頓現象。嚴重的話會出現快取雪崩,我們一般需要在時間上加一個隨機值,使得過期時間分散一些。

那你使用過Redis分散式鎖麼,它是什麼回事?

先拿setnx來爭搶鎖,搶到之後,再用expire給鎖加一個過期時間防止鎖忘記了釋放

假如Redis裡面有1億個key,其中有10w個key是以某個固定的已知的字首開頭的,如何將它們全部找出來?

使用keys指令可以掃出指定模式的key列表

如果這個redis正在給線上的業務提供服務,那使用keys指令會有什麼問題?

Redis的單執行緒的。keys指令會導致執行緒阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key列表,但是會有一定的重複概率,在客戶端做一次去重就可以了,但是整體所花費的時間會比直接用keys指令長。

如果對方接著追問能不能生產一次消費多次呢

使用pub/sub主題訂閱者模式,可以實現 1:N 的訊息佇列。

如果對方繼續追問 pub/su b有什麼缺點?

在消費者下線的情況下,生產的訊息會丟失,得使用專業的訊息佇列如RocketMQ

Redis是怎麼持久化的?服務主從資料怎麼互動的?

RDB做映象全量持久化,AOF做增量持久化。因為RDB會耗費較長時間,不夠實時,在停機的時候會導致大量丟失資料,所以需要AOF來配合使用。在redis例項重啟時,會使用RDB持久化檔案重新構建記憶體,再使用AOF重放近期的操作指令來實現完整恢復重啟之前的狀態。

這裡很好理解,把RDB理解為一整個表全量的資料,AOF理解為每次操作的日誌就好了,伺服器重啟的時候先把表的資料全部搞進去,但是他可能不完整,你再回放一下日誌,資料不就完整了嘛。不過Redis本身的機制是 AOF持久化開啟且存在AOF檔案時,優先載入AOF檔案;AOF關閉或者AOF檔案不存在時,載入RDB檔案;載入AOF/RDB檔案城後,Redis啟動成功;AOF/RDB檔案存在錯誤時,Redis啟動失敗並列印錯誤資訊

對方追問那如果突然機器掉電會怎樣?

取決於AOF日誌sync屬性的配置,如果不要求效能,在每條寫指令時都sync一下磁碟,就不會丟失資料。但是在高效能的要求下每次都sync是不現實的,一般都使用定時sync,比如1s1次,這個時候最多就會丟失1s的資料。

對方追問RDB的原理是什麼?

你給出兩個詞彙就可以了,fork和cow。fork是指redis通過建立子程式來進行RDB操作,cow指的是copy on write,子程式建立後,父子程式共享資料段,父程式繼續提供讀寫服務,寫髒的頁面資料會逐漸和子程式分離開來。

注:回答這個問題的時候,如果你還能說出AOF和RDB的優缺點,我覺得我是面試官在這個問題上我會給你點贊,兩者其實區別還是很大的,而且涉及到Redis叢集的資料同步問題等等。想了解的夥伴也可以留言,我會專門寫一篇來介紹的。

Redis的同步機制瞭解麼?

Redis可以使用主從同步,從從同步。第一次同步時,主節點做一次bgsave,並同時將後續修改操作記錄到記憶體buffer,待完成後將RDB檔案全量同步到複製節點,複製節點接受完成後將RDB映象載入到記憶體。載入完成後,再通知主節點將期間修改的操作記錄同步到複製節點進行重放就完成了同步過程。後續的增量資料通過AOF日誌同步即可,有點類似資料庫的binlog。

還有一點就是我問你為啥用Redis你不要一上來就直接回答問題了,你可以這樣回答:

帥氣的面試官您好,首先我們的專案DB遇到了瓶頸,特別是秒殺和熱點資料這樣的場景DB基本上就扛不住了,那就需要快取中介軟體的加入了,目前市面上有的快取中介軟體有 RedisMemcached ,他們的優缺點……,綜合這些然後再結合我們專案特點,最後我們在技術選型的時候選了誰

 

 

你要知道你現在想要的生活

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

img

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

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

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

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

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

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

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

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

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

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

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

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

Redis Cluster是Redis官方提供的Redis叢集功能

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

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

為什麼要實現Redis Cluster

1.主從複製不能實現高可用 2.隨著公司發展,使用者數量增多,併發越來越多,業務需要更高的QPS,而主從複製中單機的QPS可能無法滿足業務需求 3.資料量的考慮,現有伺服器記憶體不能滿足業務資料的需要時,單純向伺服器新增記憶體不能達到要求,此時需要考慮分散式需求,把資料分佈到不同伺服器上 4.網路流量需求:業務的流量已經超過伺服器的網路卡的上限值,可以考慮使用分散式來進行分流 5.離線計算,需要中間環節緩衝等別的需求

哦?那問題就來了,他們之間是怎麼進行資料互動的?以及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還有其他保證叢集高可用的方式麼?

哦我想起來了,還有哨兵叢集,

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

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

img

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

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

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

img

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

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

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

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

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

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

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

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

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

img

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

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

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

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

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

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

記憶體淘汰機制

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

  • 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 comparisonLRU comparison

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

  • 淺灰色帶是已經被回收的物件。

  • 灰色帶是沒有被回收的物件。

  • 綠色帶是被新增的物件。

  • LRU實現的理論中,我們希望的是,在舊鍵中的第一半將會過期。RedisLRU演算法則是概率的過期舊的鍵。

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

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

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

img

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

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

img

 

相關文章