Redis常见面试问题汇总及解析
??面試后端開發的職位,相信大家經常被問到有關redis問題。Redis作為緩存系統的代表很有必要弄熟搞懂,無論是在工作當中還是求職面試過程中都是大有裨益的,本文將詳細介紹一些redis的一些典型問題,并給出了一些參考解答。
??由于作者水平有限,可能會有存在一些問題,歡迎大家不吝批評指教。文中參考了網友的一些資料,在這里先他們表示感謝。本文全文約4000字,閱讀完大概需要10分鐘時間。
常見問題
Redis性能為什么高?
單線程的redis如何利用多核cpu機器?
Redis的緩存淘汰策略?
Redis如何持久化數據?
Redis有哪幾種數據結構?
Redis集群有哪幾種形式?
有海量key和value都比較小的數據,在redis中如何存儲才更省內存?
如何保證redis和DB中的數據一致性?
如何解決緩存穿透和緩存雪崩?
如何用redis實現分布式鎖?
問題參考列表
Redis性能為什么高?
Redis是key-value存儲的nosql數據庫,具有以下一些性質,使其性能優異。
- 完全基于內存操作,絕大多數操作都在內存中完成,非常高效。
- 內部采用多路I/O復用模型,非阻塞式IO。Redis會根據系統情況優先調用高效的IO復用模型,例如linux的epoll多路IO函數;
- 數據結構和數據操作簡單,redis中的數據結構是專門進行設計的;
- 處理請求模塊采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗;
- 關于底層模型為了更有效的通訊,redis直接自己構建了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。
單線程的redis如何利用多核cpu機器?
??Redis的數據讀取和處理性能非常強大,對于一般服務器配置,cpu都不會是性能瓶頸。Redis的性能瓶頸主要集中在內存和網絡方面。舉例來說,運行在一臺普通的Linux機器上至少能處理50萬并發請求。所以使用的redis命令多為O(N)、O(log(N))時間復雜度,那么基本上不會出現cpu瓶頸的情況。
??但是如果你確實需要充分使用多核cpu的能力,那么需要在單臺服務器上運行多個redis實例(主從部署/集群化部署),并將每個redis實例和cpu內核進行綁定來實現充分利用多核CPU。
Redis的緩存淘汰策略?
Redis緩存淘汰策略與Redis鍵的過期刪除策略并不完全相同,前者是在Redis內存使用超過一定值的時候(一般這個值可以配置)使用的淘汰策略;而后者是通過定期刪除+惰性刪除兩者結合的方式進行內存淘汰的。
有六種緩存淘汰策略:
- volatile-lru:從已設置過期時間的數據中挑選最近最少使用的數據淘汰;
- volatile-ttl:從已設置過期時間的數據中挑選將要過期的數據淘汰;
- volatile-random:從已設置過期時間的數據中任意選擇數據淘汰;
- allkeys-lru:從數據集中挑選最近最少使用的數據淘汰;
- allkeys-random:從數據集中任意選擇數據淘汰;
- no-enviction(驅逐):禁止驅逐數據
注意這里的6種機制,volatile和allkeys都會對已設置過期時間的數據集淘汰數據。allkeys會從全部數據集淘汰數據,后面的lru、ttl以及random是三種不同的淘汰策略,再加上一種no-enviction永不回收的策略。
Redis如何持久化數據?
Redis支持兩種數據持久化方式:RDB方式和AOF方式。前者會根據配置的規則定時將內存中的數據持久化到硬盤上,后者則是在每次執行寫命令之后將命令記錄下來。兩種持久化方式可以單獨使用,但是通常會將兩者結合使用。
1、RDB方式
RDB方式的持久化是通過快照的方式完成的。當符合某種規則時,會將內存中的數據全量生成一份副本存儲到硬盤上,這個過程稱作”快照”。舉例來說,根據配置規則進行自動快照流程:
- 用戶執行SAVE, BGSAVE命令;
- 執行FLUSHALL命令;
- 執行復制(replication)。
2、AOF方式
在使用Redis存儲非臨時數據時,一般都需要打開AOF持久化來降低進程終止導致的數據丟失,AOF可以將Redis執行的每一條寫命令追加到硬盤文件中,這一過程顯然會降低Redis的性能,但是大部分情況下這個影響是可以接受的,另外,使用較快的硬盤能提高AOF的性能。
Redis有哪幾種數據結構?
Redis支持五種數據類型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
Redis集群有哪幾種形式?
redis有三種集群方式:主從復制,哨兵模式和集群。 詳細內容參見:Redis集群管理方式
有海量key和value都比較小的數據,在redis中如何存儲才更省內存?
可以考慮通過大幅減少key的數量來降低內存的消耗。
實現:在客戶端通過分組將海量的key根據一定的策略映射到一組hash對象中,由于value較小,故hash類型的對象會使用占用內存較小的ziplist編碼。
例如:如存在100萬個鍵,可以映射到1000個hash中,每個hash保存1000個元素。
如何保證redis和DB中的數據一致性?
大多情況下,緩存策略是:讀緩存,讀取不到就讀數據庫然后同步到緩存中。
問題出現場景
在并發訪問中,不論是先寫庫,再刪除緩存;還是先刪緩存,再寫庫,都有可能出現數據不一致的情況
1、在并發中是無法保證讀寫的先后順序的,如果刪掉了緩存還沒來得及寫庫,另一個線程就過來讀取發現緩存為空就去數據庫讀取并寫入緩存,此時緩存中為臟數據。
2、如果先寫了庫,再刪除緩存前,寫庫的線程宕機了,沒有刪除掉緩存,則也會出現數據不一致情況。
3、如果是redis集群,或者主從模式,寫主讀從,由于redis復制存在一定的時間延遲,也有可能導致數據不一致。
雙刪 + 超時
在寫庫前后都進行redis.del(key)操作,并且設定合理的超時時間。這樣最差的情況是在超時時間內存在不一致,當然這種情況極其少見,可能的原因就是服務宕機。此種情況可以滿足絕大多數需求。
當然這種策略要考慮redis和數據庫主從同步的耗時,所以在第二次刪除前最好休眠一定時間,比如500毫秒,這樣毫無疑問又增加了寫請求的耗時。
如何解決緩存穿透和緩存雪崩?
緩存雪崩:是指緩存同一時間大面積的失效,瞬間請求全落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。
解決方案:緩存數據的過期時間設置隨機,防止同一時間大量數據過期現象發生。
一般并發量不是特別多的時候,使用最多的解決方案是加鎖排隊。給每一個緩存數據增加相應的緩存標記,記錄緩存的是否失效,如果緩存標記失效,則更新數據緩存。
緩存穿透: 是指緩存和數據庫中都沒有的數據,導致所有的請求都落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。
解決方案
- 接口層增加校驗,如用戶鑒權校驗,id做基礎校驗,id<=0的直接攔截;
- 從緩存取不到的數據,在數據庫中也沒有取到,這時也可以將key-value對寫為key-null,緩存有效時間可以設置短點,如30秒(設置太長會導致正常情況也沒法使用)。這樣可以防止攻擊用戶反復用同一個id暴力攻擊
- 采用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的 bitmap 中,一個一定不存在的數據會被這個 bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力
如何用redis實現分布式鎖?
在分布式環境下多個不同線程對共享資源進行訪問,傳統的鎖比如Java的鎖機制就無法實現了,這個時候就必須借助分布式鎖來解決分布式環境下共享資源的同步問題。
為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件:
- 互斥性。在任意時刻,只有一個客戶端能持有鎖
- 不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續其他客戶端能加鎖。
- 具有容錯性。只要大部分的Redis節點正常運行,客戶端就可以加鎖和解鎖。
- 加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。[1]
public class RedisTool {private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";private static final Long RELEASE_SUCCESS = 1L;/*** 嘗試獲取分布式鎖* @param jedis Redis客戶端* @param lockKey 鎖* @param requestId 請求標識* @param expireTime 超期時間* @return 是否獲取成功*/public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}return false;}/*** 釋放分布式鎖* @param jedis Redis客戶端* @param lockKey 鎖* @param requestId 請求標識* @return 是否釋放成功*/public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));if (RELEASE_SUCCESS.equals(result)) {return true;}return false;}
}
參考文獻
[1]: Redis分布式鎖的正確實現方式
歡迎大家關注我的CSDN博客,您的行動就是對我最大的鼓勵!
總結
以上是生活随笔為你收集整理的Redis常见面试问题汇总及解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis集群管理方式
- 下一篇: 微服务架构必备的几点知识