Redis——面試總結

博文來源于:https://juejin.cn/post/6844903982209449991

Redis 和 Memcached 的區(qū)別在哪里?

Redis 支持服務器端的數(shù)據(jù)操作:Redis 相比 Memcached 來說,擁有更多的數(shù)據(jù)結構和并支持更豐富的數(shù)據(jù)操作。

Redis 雖然是基于內(nèi)存的存儲系統(tǒng),但是它本身是支持內(nèi)存數(shù)據(jù)的持久化的,而且提供兩種主要的持久化策略:RDB 快照和 AOF 日志。而 memcach

Memcached 本身并不支持分布式,Memcached 是不支持數(shù)據(jù)持久化操作的。Redis支持分布式的操作。

使用簡單的 key-value 存儲的話,Memcached 的內(nèi)存利用率更高,而如果 Redis 采用 hash 結構來做 key-value 存儲,由于其組合式的壓縮,其內(nèi)存利用率會高于 Memcached。

Redis-避免緩存穿透的利器之BloomFilter

布隆過濾器的原理是,當一個元素被加入集合時,通過K個散列函數(shù)將這個元素映射成一個位數(shù)組中的K個點,把它們置為1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。

Bloom Filter跟單哈希函數(shù)Bit-Map不同之處在于:Bloom Filter使用了k個哈希函數(shù),每個字符串跟k個bit對應。從而降低了沖突的概率。

Bloom Filter原理

緩存穿透


緩存穿透原理

Bloom Filter的缺點

bloom filter之所以能做到在時間和空間上的效率比較高,是因為犧牲了判斷的準確率、刪除的便利性

存在誤判,可能要查到的元素并沒有在容器中,但是hash之后得到的k個位置上值都是1。如果bloom filter中存儲的是黑名單,那么可以通過建立一個白名單來存儲可能會誤判的元素。

刪除困難。一個放入容器的元素映射到bit數(shù)組的k個位置上是1,刪除的時候不能簡單的直接置為0,可能會影響其他元素的判斷??梢圆捎?a target="_blank">Counting Bloom Filte

布隆過濾器有許多實現(xiàn)與優(yōu)化,Guava中就提供了一種Bloom Filter的實現(xiàn)。在使用bloom filter時,繞不過的兩點是預估數(shù)據(jù)量n以及期望的誤判率fpp,在實現(xiàn)bloom filter時,繞不過的兩點就是hash函數(shù)的選取以及bit數(shù)組的大小。對于一個確定的場景,我們預估要存的數(shù)據(jù)量為n,期望的誤判率為fpp,然后需要計算我們需要的Bit數(shù)組的大小m,以及hash函數(shù)的個數(shù)k,并選擇hash函數(shù)。

關系型數(shù)據(jù)庫跟Redis本質(zhì)上的區(qū)別

Redis采用的是基于內(nèi)存的采用的是單進程單線程模型的 KV 數(shù)據(jù)庫,由C語言編寫,官方提供的數(shù)據(jù)是可以達到100000+的QPS(每秒內(nèi)查詢次數(shù))。

完全基于內(nèi)存,絕大部分請求是純粹的內(nèi)存操作,非??焖佟K?,數(shù)據(jù)存在內(nèi)存中,類似于HashMap,HashMap的優(yōu)勢就是查找和操作的時間復雜度都是O(1);

數(shù)據(jù)結構簡單,對數(shù)據(jù)操作也簡單,Redis中的數(shù)據(jù)結構是專門進行設計的;

采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現(xiàn)死鎖而導致的性能消耗;

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

使用底層模型不同,它們之間底層實現(xiàn)方式以及與客戶端之間通信的應用協(xié)議不一樣,Redis直接自己構建了VM 機制 ,因為一般的系統(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會浪費一定的時間去移動和請求;

啥是上下文切換么?

線程執(zhí)行的時候是需要加載上下文資源的。計算機的執(zhí)行也是是很快的。所以線程切換的是會出現(xiàn)資源的加載。

那他是單線程的,我們現(xiàn)在服務器都是多核的,那不是很浪費?

是的他是單線程的,但是,我們可以通過在單機開多個Redis實例嘛。

單機會有瓶頸,那你們是怎么解決這個瓶頸的?

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

這樣整個Redis就可以橫向擴容了。如果你要支撐更大數(shù)據(jù)量的緩存,那就橫向擴容更多的master節(jié)點,每個master節(jié)點就能存放更多的數(shù)據(jù)了。

Redis還有其他保證集群高可用的方式么?

哨兵必須用三個實例去保證自己的健壯性的,哨兵+主從并不能保證數(shù)據(jù)不丟失,但是可以保證集群的高可用。為啥必須要三個實例呢?因為集群出現(xiàn)的宕機的時候會發(fā)生選舉。如果是偶數(shù)有可能選擇不出主節(jié)點。但是集群不能提供服務。

主從之間的數(shù)據(jù)怎么同步的么?

你啟動一臺slave 的時候,他會發(fā)送一個psync命令給master ,如果是這個slave第一次連接到master,他會觸發(fā)一個全量復制。master就會啟動一個線程,生成RDB快照,還會把新的寫請求都緩存在內(nèi)存中,RDB文件生成后,master會將這個RDB發(fā)送給slave的,slave拿到之后做的第一件事情就是寫進本地的磁盤,然后加載進內(nèi)存,然后master會把內(nèi)存里面緩存的那些新命名都發(fā)給slave。

數(shù)據(jù)傳輸?shù)臅r候斷網(wǎng)了或者服務器掛了怎么辦???

傳輸過程中有什么網(wǎng)絡問題啥的,會自動重連的,并且連接之后會把缺少的數(shù)據(jù)補上的。

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

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

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

官網(wǎng)上給到的內(nèi)存淘汰機制是以下幾個:

noeviction:返回錯誤當內(nèi)存限制達到并且客戶端嘗試執(zhí)行會讓更多內(nèi)存被使用的命令(大部分的寫入指令,但DEL和幾個例外)

allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的數(shù)據(jù)有空間存放。

volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限于在過期集合的鍵,使得新添加的數(shù)據(jù)有空間存放。

allkeys-random: 回收隨機的鍵使得新添加的數(shù)據(jù)有空間存放。

volatile-random: 回收隨機的鍵使得新添加的數(shù)據(jù)有空間存放,但僅限于在過期集合的鍵。

volatile-ttl: 回收在過期集合的鍵,并且優(yōu)先回收存活時間(TTL)較短的鍵,使得新添加的數(shù)據(jù)有空間存放。

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

Redis有幾種基礎數(shù)據(jù)類型么?和應用場景?

String的實際應用場景比較廣泛的有:

緩存功能:String字符串是最常用的數(shù)據(jù)類型,不僅僅是Redis,各個語言都是最基本類型,因此,利用Redis作為緩存,配合其它數(shù)據(jù)庫作為存儲層,利用Redis支持高并發(fā)的特點,可以大大加快系統(tǒng)的讀寫速度、以及降低后端數(shù)據(jù)庫的壓力。

計數(shù)器:許多系統(tǒng)都會使用Redis作為系統(tǒng)的實時計數(shù)器,可以快速實現(xiàn)計數(shù)和查詢的功能。而且最終的數(shù)據(jù)結果可以按照特定的時間落地到數(shù)據(jù)庫或者其它存儲介質(zhì)當中進行永久保存。

共享用戶Session:用戶重新刷新一次界面,可能需要訪問一下數(shù)據(jù)進行重新登錄,或者訪問頁面緩存Cookie,但是可以利用Redis將用戶的Session集中管理,在這種模式只需要保證Redis的高可用,每次用戶Session的更新和獲取都可以快速完成。大大提高效率。

Hash:

這個是類似Map的一種結構,這個一般就是可以將結構化的數(shù)據(jù),比如一個對象(前提是這個對象沒嵌套其他的對象)給緩存在Redis里,然后每次讀寫緩存的時候,可以就操作Hash里的某個字段

但是這個的場景其實還是多少單一了一些,因為現(xiàn)在很多對象都是比較復雜的,比如你的商品對象可能里面就包含了很多屬性,其中也有對象。我自己使用的場景用得不是那么多。

List:List是有序列表,這個還是可以玩兒出很多花樣的。

比如可以通過List存儲一些列表型的數(shù)據(jù)結構,類似粉絲列表、文章的評論列表之類的東西。

比如可以通過lrange命令,讀取某個閉區(qū)間內(nèi)的元素,可以基于List實現(xiàn)分頁查詢,這個是很棒的一個功能,基于Redis實現(xiàn)簡單的高性能分頁,可以做類似微博那種下拉不斷分頁的東西,性能高,就一頁一頁走。

比如可以搞個簡單的消息隊列,從List頭懟進去,從List屁股那里弄出來。

List本身就是我們在開發(fā)過程中比較常用的數(shù)據(jù)結構了,熱點數(shù)據(jù)更不用說了。

消息隊列:Redis的鏈表結構,可以輕松實現(xiàn)阻塞隊列,可以使用左進右出的命令組成來完成隊列的設計。比如:數(shù)據(jù)的生產(chǎn)者可以通過Lpush命令從左邊插入數(shù)據(jù),多個數(shù)據(jù)消費者,可以使用BRpop命令阻塞的“搶”列表尾部的數(shù)據(jù)。

文章列表或者數(shù)據(jù)分頁展示的應用。比如,我們常用的博客網(wǎng)站的文章列表,當用戶量越來越多時,而且每一個用戶都有自己的文章列表,而且當文章多時,都需要分頁展示,這時可以考慮使用Redis的列表,列表不但有序同時還支持按照范圍內(nèi)獲取元素,可以完美解決分頁查詢功能。大大提高查詢效率。

Set:Set是無序集合,會自動去重的那種。

直接基于Set將系統(tǒng)里需要去重的數(shù)據(jù)扔進去,自動就給去重了,如果你需要對一些數(shù)據(jù)進行快速的全局去重,你當然也可以基于JVM內(nèi)存里的HashSet進行去重,但是如果你的某個系統(tǒng)部署在多臺機器上呢?得基于Redis進行全局的Set去重??梢曰?b>Set玩兒交集、并集、差集的操作,比如交集吧,我們可以把兩個人的好友列表整一個交集,看看倆人的共同好友是誰?對吧。反正這些場景比較多,因為對比很快,操作也簡單,兩個查詢一個Set搞定。

Sorted Set:Sorted set是排序的Set,去重但可以排序,寫進去的時候給一個分數(shù),自動根據(jù)分數(shù)排序。

有序集合的使用場景與集合類似,但是set集合不是自動有序的,而Sorted set可以利用分數(shù)進行成員間的排序,而且是插入時就排序好。所以當你需要一個有序且不重復的集合列表時,就可以選擇Sorted set數(shù)據(jù)結構作為選擇方案。

排行榜:有序集合經(jīng)典使用場景。例如視頻網(wǎng)站需要對用戶上傳的視頻做排行榜,榜單維護可能是多方面:按照時間、按照播放量、按照獲得的贊數(shù)等。

Sorted Sets來做帶權重的隊列,比如普通消息的score為1,重要消息的score為2,然后工作線程可以選擇按score的倒序來獲取工作任務。讓重要的任務優(yōu)先執(zhí)行

如果你多個系統(tǒng)同時操作(并發(fā))Redis帶來的數(shù)據(jù)問題?

多系統(tǒng)并發(fā)操作問題

系統(tǒng)A、B、C三個系統(tǒng),分別去操作Redis的同一個Key,本來順序是1,2,3是正常的,但是因為系統(tǒng)A網(wǎng)絡突然抖動了一下,B,C在他前面操作了Redis,這樣數(shù)據(jù)不就錯了么。就好比下單,支付,退款三個順序你變了,你先退款,再下單,再支付,那流程就會失敗,那數(shù)據(jù)不就亂了?你訂單還沒生成你卻支付,退款了?明顯走不通了,這在線上是很恐怖的事情。

我們可以找個管家?guī)臀覀児芾砗脭?shù)據(jù)的嘛!

某個時刻,多個系統(tǒng)實例都去更新某個 key。可以基于Zookeeper實現(xiàn)分布式鎖。每個系統(tǒng)通過Zookeeper獲取分布式鎖,確保同一時間,只能有一個系統(tǒng)實例在操作某個 Key,別人都不允許讀和寫。你要寫入緩存的數(shù)據(jù),都是從MySQL里查出來的,都得寫入MySQL中,寫入MySQL中的時候必須保存一個時間戳,從MySQL查出來的時候,時間戳也查出來。每次要寫之前,先判斷一下當前這個 Value 的時間戳是否比緩存里的 Value 的時間戳要新。如果是的話,那么可以寫,否則,就不能用舊的數(shù)據(jù)覆蓋新的數(shù)據(jù)。

你只要用緩存,就可能會涉及到緩存與數(shù)據(jù)庫雙存儲雙寫,你只要是雙寫,就一定會有數(shù)據(jù)一致性的問題,那么你如何解決一致性問題?

一般來說,如果允許緩存可以稍微的跟數(shù)據(jù)庫偶爾有不一致的情況,也就是說如果你的系統(tǒng)不是嚴格要求“緩存+數(shù)據(jù)庫” 必須保持一致性的話,最好不要做這個方案,即:讀請求和寫請求串行化,串到一個內(nèi)存隊列里去。串行化可以保證一定不會出現(xiàn)不一致的情況,但是它也會導致系統(tǒng)的吞吐量大幅度降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。把一些列的操作都放到隊列里面,順序肯定不會亂,但是并發(fā)高了,這隊列很容易阻塞,反而會成為整個系統(tǒng)的弱點,瓶頸。

最經(jīng)典的緩存+數(shù)據(jù)庫讀寫的模式,就是Cache Aside Pattern

讀的時候,先讀緩存,緩存沒有的話,就讀數(shù)據(jù)庫,然后取出數(shù)據(jù)后放入緩存,同時返回響應。

更新的時候,先更新數(shù)據(jù)庫,然后再刪除緩存

為什么是刪除緩存,而不是更新緩存?

很多時候,在復雜點的緩存場景,緩存不單單是數(shù)據(jù)庫中直接取出來的值。比如可能更新了某個表的一個字段,然后其對應的緩存,是需要查詢另外兩個表的數(shù)據(jù)并進行運算,才能計算出緩存最新的值的。

另外更新緩存的代價有時候是很高的。是不是說,每次修改數(shù)據(jù)庫的時候,都一定要將其對應的緩存更新一份?也許有的場景是這樣,但是對于比較復雜的緩存數(shù)據(jù)計算的場景,就不是這樣了。如果你頻繁修改一個緩存涉及的多個表,緩存也頻繁更新。但是問題在于,這個緩存到底會不會被頻繁訪問到。

Redis 的線程模型了解么?

Redis內(nèi)部使用文件事件處理器file event handler,這個文件事件處理器是單線程的,所以Redis才叫做單線程的模型。它采用 IO 多路復用機制同時監(jiān)聽多個Socket,根據(jù)Socket上的事件來選擇對應的事件處理器進行處理。

文件事件處理器的結構包含 4 個部分:

多個Socket

IO 多路復用程序(多線程)

文件事件分派器

事件處理器(連接應答處理器、命令請求處理器、命令回復處理器)(單線程的)

多個Socket可能會并發(fā)產(chǎn)生不同的操作,每個操作對應不同的文件事件,但是 IO 多路復用程序會監(jiān)聽多個Socket,會將Socket產(chǎn)生的事件放入隊列中排隊,事件分派器每次從隊列中取出一個事件,把該事件交給對應的事件處理器進行處理

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 什么是Redis? Redis 是一個使用 C 語言寫成的,開源的 key-value 數(shù)據(jù)庫。。和Memcach...
    yangfhit閱讀 280評論 0 0
  • 1 什么是redis? Redis 是一個基于內(nèi)存的高性能key-value數(shù)據(jù)庫。 (有空再補充,有理解錯誤或不...
    java高并發(fā)閱讀 1,782評論 0 57
  • 來自:https://www.cnblogs.com/jiahaoJAVA/p/6244278.htmlhttp:...
    碼農(nóng)小光閱讀 322評論 0 2
  • 什么是redis? Redis是一個基于內(nèi)存的高性能key-value數(shù)據(jù)庫。 (有空再補充,有理解錯誤或不足歡迎...
    Java黎先生閱讀 235評論 0 1
  • 夜鶯2517閱讀 128,103評論 1 9

友情鏈接更多精彩內(nèi)容