Java面試系列-redis相關

redis是個啥東西?

redis全稱是“Remote Dictionary Service”(遠程字典服務),在互聯網領域,它是使用最廣泛的緩存中間件,大部分公司或多或少的都會使用到redis,它不僅可以用作緩存,還可以有很多其他用途,如分佈式鎖、接口的冪等性等。

redis有哪些數據結構?

redis有五種基礎數據結構,分別是:string(字符串)、list(列表)、hash(字典)、set(集合)、zset(有序集合)。

具體各個數據結構的用途可以回看我以前的文章:傳送門

  • string(字符串):redis中最基礎的數據結構。(很多人在使用redis時,一直奉行着萬物皆字符串,這是不對的,要善於使用合適的數據結構才能發揮redis的最大效率)
  • list(列表):相當於Java中的LinkedList,是一個雙向鏈表,既可以用作隊列(先進先出),也可以用作棧(先進後出)。
  • hash(字典):相當於Java中的HashMap,也是數組+鏈表,不同的是,redis字典的值只能是字符串,而且他們的rehash方式不一樣,Java中是一次性全部rehash,而redis是漸進式rehash。
  • set(集合):相當於Java中的HashSet,無序和唯一,內部實現相當於一個特殊的字典,字典中所有的value都是null。
  • zset(有序集合):相當於Java中的SortedSet和HashMap的結合體,既能保證內部value的唯一性,又能通過value附帶的score進行排序。內部是通過一種叫做“ 跳躍表”的數據結構來實現的。

上述五種數據結構中除了字符串,其他四種都是容器型數據結構,它們有兩個共同點:1、當添加第一條數據時,如果容器不存在,就創建一個。2、當刪除最後一條數據時,也會同時刪除容器

所有的數據結構都可以設置過期時間,這也是redis特色功能之一。

redis還有很多高級數據結構,如:HyperLogLogBloom Filterpub/sub等。如果在面試中可以說一些高級數據結構,也是個加分項。

redis爲啥會那麼快?

  • 使用單線程,避免了上下文切換以及線程之間競爭帶來的性能消耗,也不用去考慮各種鎖的問題。
  • 所有數據都在內存中,所有的運算都是內存級別的運算。
  • 採用 多路複用模型來解決多個客戶端的併發請求。
  • redis6.0以後加入了多線程,這個多線程只是針對網絡IO的處理,最大能力發揮多核處理器的作用,主要的命令處理還是採用單線程。

redis持久化機制

全量數據採用RDB快照,可能遺失的數據採用AOF增量日誌補充。

具體細節可參考我以前的文章:傳送門

redis實現分佈式鎖

一般使用setnx指令來實現分佈式鎖。爲了防止死鎖,需要添加過期時間。但是如果過期時間結束,持有鎖的線程還未處理完,就會出現兩個線程同時持有鎖的情況,爲了防止這種情況產生,可以通過set命令設置參數或者LUA腳本來實現

在真實開發環境中,推薦使用Redisson框架來實現分佈式鎖

代碼實現可參數我以前的文章:傳送門

redis布隆過濾器

當一些請求查詢的數據在數據庫中不存在時,那這些請求每次都會之間去查詢數據庫,而不會走redis緩存,因爲數據庫中都沒有的數據,那緩存中也沒有。如果這些請求過多,則會對數據庫造成巨大壓力。

爲了解決這個問題,可以採用布隆過濾器,它的原理是採用一個大型的bit數組和幾個不一樣的hash函數,對每個key進行幾次hash,其結果會映射到bit數組中,由0變爲1。

請求過來的key首先通過hash函數計算的結果對應到bit數組中位置,如果有一位爲0,則這個請求在數據庫中肯定不存在,可以直接返回。

布隆過濾器不能確定key一定存在,但可以確定key一定不存在

布隆過濾器也可以用於其他用途,比如爬蟲中過濾重複的url等。

redis集羣

主從模式

一個主節點Master,多個從節點Slave。Master會將數據同步更新給Slave,保持數據同步。主節點可讀可寫,從節點只能讀。提高了讀寫的能力。

Sentinel哨兵模式

在主從模式中,如果主節點掛了,那整個集羣就處於不可寫的狀態了。爲了解決這個問題,Sentinel哨兵模式出現了。

Sentinel系統可以監控多個redis服務,如果出現故障可以通過api向管理員發送通知。如果發現主服務器出現故障,Sentinel會自動進行故障遷移操作,先選舉出新的主服務器,其他從服務器也會改爲從新主服務器複製數據,客戶端試圖請求舊主服務器時,Sentinel也會返回新的主服務器地址。(在Sentinel模式下,客戶端不要直接連redis,而是連Sentinel,由SenStinel來負責統一調度)。

Cluster模式

Sentinel哨兵模式可以滿足大部分生產需求,具有高可用性。但是如果redis中數據量過大,則這種模式就滿足不了了。這時候可以採用Cluster模式

在Cluster模式中,採用slot(槽)的概念,一共有16384個槽,每個槽都有對應的redis節點。當客戶端請求過來,對key作hash算法,映射到其中的一個槽,再找到對應的redis節點進行處理。

爲了保證Cluster模式的高可用性,加入了主從模式,所有槽對應的redis節點都是採用主從模式,當主節點出現問題以後,從節點中會選舉出一個來充當主節點,從而保證集羣不會掛掉。

redis事務

redis雖然是nosql數據庫,但是它也支持事務。redis事務指的是一次性執行一系列命令,但是不保證原子性,且沒有回滾,事務中任意命令執行失敗,其餘的命令任會執行。

  • multi:開啓事務
  • exec:執行事務中所有命令
  • discard:取消事務,放棄事務中所有命令
  • watch:監視一個或多個key,如果事務執行前,被監視的key被其他命令改動,則事務被打斷。
  • unwatch:取消watch對key的監控

redis的淘汰策略

key設置了過期時間,到時間了怎麼淘汰?

1、redis會將設置了過期時間的key放入到一個獨立的字典中,後面會定時遍歷這個字典來刪除過期的key,默認是一秒遍歷十次。每次從字典中隨機選出20個key,刪除這20箇中已經過期的key,如果過期比例超過四分之一,再重複選出20個刪除,以此類推。

2、採用惰性刪除,當客戶端訪問這個key的時候,再對這個key進行檢查,如果過期了則立即刪除。

redis內存超了,怎麼淘汰?

redis提供了maxmemory參數,表示最大使用內存,如果超出了這個限制,redis會採取策略來淘汰特定的key。

redis提供了下面幾種策略:

  • noeviction:不會服務寫請求(刪除可以),讀請求繼續服務。
  • volatile-lru:在設置了過期時間的key中,淘汰最近最少使用的key。
  • volatile-ttl:在設置了過期時間的key中,比較key的剩餘壽命ttl,ttl越小越先被淘汰。
  • volatile-random:在設置了過期時間的key中,淘汰隨機的key。
  • allkeys-lru:在所有的key中,淘汰最近最少使用的key。
  • allkeys-random:在所有的key中,隨機淘汰key。

volatile-xxx策略只針對設置了過期時間的key,而allkeys-xxx是針對所有的key。如果你使用了redis持久化功能,則應該選擇volatile-xxx策略,保證永久的key不會被淘汰,否則選擇allkeys-xxx策略。

redis跳躍表

redis中的跳躍表是一種特殊的數據結構,zset的排序功能就是通過跳躍表來實現的。在zset中可能會頻繁的插入和刪除,所以它不適合用數組來表示,只能採用鏈表。但是zset又有排序的功能,如果一個有序鏈表中插入一個數據,需要在合適的位置插入才能繼續保持有序,一般採用二分查找來定位,但是鏈表做不到。爲了解決這個問題,redis採用了一種特殊的數據結構-跳躍表

先看傳統的有序鏈表

傳統的鏈表如果要查找8這個元素,需要從頭遍歷到8節點。

再來看一下跳躍表

跳躍表中每個元素都可能處在多個層級,如果要查詢8這個元素,先從L2層查到元素7,再從L1層查到7的下一個L1層元素是9,最後從L0層中定位到7和9之間的元素8。

由此可見,跳躍表的查詢比傳統鏈表要快得多

redis和數據庫的雙寫一致性

首先要明確一點,當我們使用緩存的時候,基本都是讀多寫少的情況,而且redis和數據庫之間只能保證最終一致性,無法達到強一致性。但我們可以採用合適的一致性方案來減少不一致的時間。

方案一

讀請求:如果命中緩存,則直接返回;如果未命中,則查詢數據庫,成功後再放到緩存中。

寫請求:先讓緩存失效,再寫數據庫

(注意點:寫請求中,如果先寫數據庫,再讓緩存失效,那麼第二步失敗的話,則會造成緩存和數據庫數據不一致。所以應該先讓緩存失效,再寫數據庫,這樣就算第二步失敗,也只是緩存無數據,第二次讀取的時候則會補充數據)

方案二

方案一中,如果高併發情況下,如果先讓緩存失效後,還沒來得及寫數據庫,另一個線程進來發現緩存中沒有,然後讀取數據庫後更新緩存,這樣就會出現緩存中是舊數據,而數據庫中是新數據。

可以採用這種做法來解決:先讓緩存失效,再寫數據庫,接着等待一會後再讓緩存失效

方案三

寫操作直接寫數據庫,然後利用中間件讀取數據庫的binlog日誌,篩選合適的數據更新到redis緩存中

總結

通常來說,方案一就足夠了,正常情況下,緩存中都是讀多寫少的情況,很小的機率會出現寫請求的時候併發出現讀請求更新緩存,而且開發成本較低,非常實用。如果非要解決方案一中的問題,可以考慮方案二;如果系統對延遲性的要求很高,可以考慮方案三。

最後注意一點:緩存的數據一定要設置失效時間,這是一致性兜底的方案

redis可能帶來的問題

在我們使用緩存的時候,如果是高併發情況下,可能會出現緩存穿透、緩存擊穿和緩存雪崩等問題。具體出現原因和解決方案可參考我以前的文章:傳送門


掃一掃,關注我

徹底搞懂這煩人的編碼與亂碼!

2020-09-10

Java面試系列-線程相關(一)

2020-09-03

Java到底是引用傳遞還是值傳遞

2020-08-07

事務:不好意思,你被隔離了!

2020-07-23

spring事務咋和新冠病毒一樣,還會傳染?

2020-07-05


本文分享自微信公衆號 - pipi蛋(pipidan_fuyun)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章