Redis 面试常问问题
Redis 系列筆記:
第一篇:Redis 基礎命令
第二篇:Redis 常見應用場景
第三篇:Redis Cluster集群搭建
第四篇:Redis 主從及哨兵搭建
第五篇:Redis 主從及集群
第六篇:Redis 持久化
第七篇:Redis 分布式鎖
第八篇:Redis 底層數據存儲結構
第九篇:Redis 面試常問問題
文章目錄
- Redis 系列筆記:
- 前言
- 一、單線程的Redis為什么那么快
- 1、Redis為什么是單線程
- 2、Redis為什么那么快
- 1、多路I/O復用模型
- 2、VM(虛擬內存)機制
- 二、Redis 緩存問題及預防措施
- 1、緩存穿透
- 2、緩存擊穿
- 3、緩存雪崩
- 4、預防措施
- 1、緩存預熱
- 2、熔斷
- 3、降級
- 4、限流
- 三、Redis與數據庫的數據一致性問題
- 1、先刪除緩存、再更新數據庫
- 2、先更新數據庫、再刪除緩存(推薦)
- 3、主從一致性
- 4、刪除失敗怎么辦
- 四、分布式鎖
- 五、Redis持久化機制
- 六、秒殺場景
- 七、Redis和Memcahe對比
- 八、Redis的過期策略及淘汰機制
- 1、過期策略
- 2、淘汰機制
- 3、其他場景過期key的操作
- 九、Redis常見性能問題及解決方案
- 總結
前言
本篇文章簡單總結一下 Redis 的常見問題及解決辦法。
提示:以下是本篇文章正文內容,下面案例可供參考
一、單線程的Redis為什么那么快
1、Redis為什么是單線程
因為CPU不是Redis的瓶頸。Redis的瓶頸最有可能是機器內存或者網絡帶寬,既然單線程容易實現,且避免了線程切換和資源競爭帶來的開銷,而CPU又不會成為瓶頸,那就順理成章地采用單線程的方案了。
Redis采用的是基于內存的采用的是單進程單線程模型的 KV 數據庫,由C語言編寫,官方提供的數據是可以達到100000+的QPS(每秒內查詢次數)。
橫軸是連接數,縱軸是QPS。
2、Redis為什么那么快
1、完全基于內存,絕大部分請求是純粹的內存操作,非??焖佟祿嬖趦却嬷?#xff0c;類似于HashMap,HashMap的優勢就是查找和操作的時間復雜度都是O(1);
?
2、數據結構簡單,對數據操作也簡單,Redis中的數據結構是專門進行設計的;
?
3、采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗;
?
4、使用多路I/O復用模型,非阻塞IO;
?
5、使用底層模型不同,它們之間底層實現方式以及與客戶端之間通信的應用協議不一樣,Redis直接自己構建了 VM(虛擬內存)機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求;
1、多路I/O復用模型
為什么 Redis 中要使用 I/O 多路復用這種技術呢?因為 Redis 是跑在「單線程」中的,所有的操作都是按照順序線性執行的,但是「由于讀寫操作等待用戶輸入 或 輸出都是阻塞的」,所以 I/O 操作在一般情況下往往不能直接返回,這會導致某一文件的 I/O 阻塞導,致整個進程無法對其它客戶提供服務。而 I/O 多路復用就是為了解決這個問題而出現的?!笧榱俗寙尉€程(進程)的服務端應用同時處理多個客戶端的事件,Redis 采用了 IO 多路復用機制。」
多路:指的是多個網絡連接客戶端。
復用:指的是復用同一個線程(單進程)。
I/O 多路復用其實是使用一個線程來檢查多個 Socket 的就緒狀態,在單個線程中通過記錄跟蹤每一個 socket(I/O流)的狀態來管理處理多個 I/O 流。如下圖是 Redis 的 I/O 多路復用模型:
-
1、Redis 的 I/O 多路復用程序函數有 select、poll、epoll、kqueue。select 作為備選方案,由于其在使用時會掃描全部監聽的文件描述符,并且只能同時服務 1024 個文件描述符,所以是備選方案。
- 以 Redis 的 I/O 多路復用程序 epoll 函數為例:
多個客戶端連接服務端時,Redis 會將客戶端 socket 對應的 fd 注冊進 epoll,然后 epoll 同時監聽多個文件描述符(FD)是否有數據到來,如果有數據來了就通知事件處理器趕緊處理,這樣就不會存在服務端一直等待某個客戶端給數據的情形。
- 以 Redis 的 I/O 多路復用程序 epoll 函數為例:
-
2、 I/O 多路復用模型是利用 select、poll、epoll 函數可以同時監察多個流的 I/O 事件的能力,在空閑的時候,會把當前線程阻塞掉。當有一個或多個流有 I/O 事件時,就從阻塞態中喚醒,于是程序就會輪詢一遍所有的流(epoll 是只輪詢那些真正發出了事件的流),依次順序的處理就緒的流,這種做法就避免了大量無用的等待操作。
參考:說說Redis之I/O多路復用模型實現原理
2、VM(虛擬內存)機制
Redis的 VM(虛擬內存)機制 就是暫時把不經常訪問的數據(冷數據)從內存交換到磁盤中,從而騰出寶貴的內存空間用于其它需要訪問的數據(熱數據)。通過VM功能可以實現冷熱數據分離,使熱數據仍在內存中、冷數據保存到磁盤。這樣就可以避免因為內存不足而造成訪問速度下降的問題。
Redis提高數據庫容量的辦法有兩種:一種是可以將數據分割到多個Redis Server上;另一種是使用虛擬內存把那些不經常訪問的數據交換到磁盤上。需要特別注意的是Redis并沒有使用OS提供的Swap,而是自己實現。
Redis為了保證查找的速度,只會將value交換出去,而在內存中保留所有的Key。所以它非常適合Key很小,Value很大的存儲結構。如果Key很大,value很小,那么vm可能還是無法滿足需求。
通過在redis的redis.conf文件里,設置VM的相關參數來實現數據在內存和磁盤之間 換入和 換出操作。
二、Redis 緩存問題及預防措施
1、緩存穿透
問題: 當有大量查詢請求未命中redis緩存時,引起對后臺數據庫的頻繁訪問,導致數據庫負載壓力增大。
?
解決方案 :
- 1、布隆過濾器:使用布隆過濾器,過濾掉一些不存在的key。布隆過濾器判定為true時,key可能存在于數據庫中,也可能不存在;判定為false時,key一定不存在于數據庫。
- 2、緩存空對象:如果該key在redis和數據庫中都沒有,那么在redis中設置該key值為NULL,再設置一個有效期。
2、緩存擊穿
問題: 指大量請求并發訪問的key-value數據,而在這個時候緩存key突然失效了,緩存未命中引起對后臺數據庫的頻繁訪問,導致數據庫負載壓力增大。
?
解決方案:
- 1、設置緩存永遠不過期。
- 2、加互斥鎖,使用分布式鎖,保證每個key只有一個線程去查詢后端服務,而其他線程為等待狀態。這種模式將壓力轉到了分布式鎖上。
3、緩存雪崩
問題: 當某?時刻發??規模的緩存失效的情況,例如緩存服務宕機、大量key在同一時間過期,如:緩存集體過期、redis宕機
?
解決方案:
- 1、 給key的失效時間設置為隨機時間,避免集體過期或者永不過期;
- 2、搭建集群;
- 3、加互斥鎖。當熱點key過期后,大量的請求涌入時,只有第一個請求能獲取鎖并阻塞,此時該請求查詢數據庫,并將查詢結果寫入redis后釋放鎖。后續的請求直接走緩存。
- 4、當訪問次數急劇增加導致服務出現問題時,我們如何確保服務仍然可用。在國內使用比較多的是 Hystrix,它通過熔斷、降級、限流三個手段來降低雪崩發生后的損失。
- 5、Redis備份和快速預熱:Redis數據備份和恢復、快速緩存預熱。
緩存穿透: 并發時請求不存在的數據。
緩存擊穿: 并發請求時單個緩存過期。
緩存雪崩: 大量的緩存同一時刻過期。
4、預防措施
1、緩存預熱
緩存預熱是指系統上線后,提前將相關的緩存數據加載到緩存系統中,避免剛上線使用戶有太多請求打到數據庫上去,然后再去將數據緩存的問題。
1、直接寫個緩存刷新頁面.上線時手工操作下;
2、數據且不大.可以在項目啟動的時候自動進行加載;
3、定時刷新緩存;
2、熔斷
緩存雪崩會造成大面積的服務節點出現異常,為了解決這個問題出現了 熔斷機制 :當下游服務器不可用或相應過慢的時候,上游服務器為保證自己整體服務的可用性,不再繼續調用目標服務器,直接返回,快速釋放資源,如果目標服務器好轉則恢復調用。
3、降級
緩存降級是指緩存失效或者緩存服務器掛掉的情況下,不去訪問數據庫,直接返回默認數據或者訪問服務的內存數據。降級一般是有損的操作,所以盡量減少降級對業務的影響程度。
系統可以根據一些關鍵數據進行自動降級,降級的最終目的是保證核心服務可用,即使是有損的。但是有的一些業務的核心服務是不能降級的。這是一種丟卒保帥的思想。
1. 一般:比如有些服務偶爾因為網絡抖動或者服務正在上線而超時,可以自動降級; 2. 警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,并發送告警; 3. 錯誤:比如可用率低于90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級; 4. 嚴重錯誤:比如因為特殊原因數據錯誤了,此時需要緊急人工降級。4、限流
參考:Redis 限流的 3 種方式,速速拿走
1、基于Redis的setnx的操作:
- 比如我們需要在10秒內限定20個請求,那么我們在setnx的時候可以設置過期時間10,當請求的setnx數量達到20時候即達到了限流效果。
- 缺點:當統計1-10秒的時候,無法統計2-11秒之內,如果需要統計N秒內的M個請求,那么我們的Redis中需要保持N個key等等問題。
?
2、基于Redis的數據結構zset:
- 我們可以將請求打造成一個zset數組,當每一次請求進來的時候,value保持唯一,可以用UUID生成,而score可以用當前時間戳表示,因為score我們可以用來計算當前時間戳之內有多少的請求數量。而zset數據結構也提供了range方法讓我們可以很輕易的獲取到2個時間戳內有多少請求。
- 缺點:zset的數據結構會越來越大。
?
3、基于Redis的令牌桶算法:
- 令牌桶算法提及到輸入速率和輸出速率,當輸出速率大于輸入速率,那么就是超出流量限制了。也就是說我們每訪問一次請求的時候,可以從Redis中獲取一個令牌,如果拿到令牌了,那就說明沒超出限制,而如果拿不到,則結果相反。
- 實現:依靠List的LPOP來獲取令牌,再依靠定時任務,定時往List中RPUSH令牌,當然令牌也需要唯一性,所以我這里還是用UUID進行了生成。
三、Redis與數據庫的數據一致性問題
Redis 與數據庫的數據一致性不能保證強一致性,也就是不能保證時時刻刻數據一樣,只能保證一段時候后數據最終一致性。那么操作上就有一個先后順序的問題:
1、先更新數據庫,再更新緩存; 2、更新緩存,再更新數據庫;而緩存又有更新和刪除兩種選擇,無論是「先更新數據庫,再更新緩存」,還是「先更新緩存,再更新數據庫」,這兩個方案都存在并發問題,當兩個請求并發更新同一條數據的時候,可能會出現緩存和數據庫中的數據不一致的現象。
所以來看看下面兩種刪除緩存的情況:
1、先刪除緩存、再更新數據庫
如果刪除了緩存Redis,還沒來得及寫入數據庫,另一個請求就來讀取,發現緩存為空,則去數據庫中讀取數據寫入緩存,此時緩存中為臟數據。
這種情況下可以使用雙刪策略:先刪除緩存,,再寫數據庫,休眠N秒,再次刪除緩存(N秒根據業務需要判斷);如果第二次刪除失敗,那就把第二次刪除加入到消息隊列中執行或者多次刪除(刪除緩存重試機制)。
2、先更新數據庫、再刪除緩存(推薦)
當前這種方式是比較推薦的,更新數據庫和刪除緩存這段時間內,請求讀取的還是緩存內的舊數據,不過等數據庫更新完成后,就會恢復一致。
上圖是情況發生后,現在已經沒有緩存,哪呢就有可能發生下圖這種情況:
發生上述情況有一個先天性條件,就是右側更新數據庫操作比左側讀數據庫操作耗時更短,才有可能發生上圖的情況,但是數據庫的讀操作的速度遠快于寫操作的,因此這一情形很難出現。緩存數據可以加上過期時間,就算在這期間存在緩存數據不一致,有過期時間來兜底,這樣也能達到最終一致。
先寫數據庫 ,后刪除緩存不會出現雙寫不一致,只會出現讀寫并發不一致。
3、主從一致性
主從同步有時延,這個時延期間讀從庫,可能讀到不一致的數據。任何脫離業務的架構設計都是耍流氓,絕大部分業務,例如:百度搜索,淘寶訂單,QQ消息,58帖子都允許短時間不一致。如果業務能接受,別把系統架構搞得太復雜。
強制讀主:
1、使用一個高可用主庫提供數據庫服務 2、讀和寫都落到主庫上 3、采用緩存來提升系統讀性能這是很常見的微服務架構,可以避免數據庫主從一致性問題。
針對主從一致性的問題常用解決辦法:
前兩個不用多說,現在講講 選擇性讀主:
緩存標記法:
1)用戶A發起寫請求,更新了主庫,并在客戶端設置標記,過期時間(預估的主庫和從庫同步延遲的時間),可以使用cookie實現
2)用戶A再發起讀請求時,帶上這個cookie
3)服務器處理請求時,獲取請求傳過來的數據,看有沒有這個標記
4)有這個業務標記,走主庫;沒有走從庫。
這個方案就保證了用戶A的讀請求肯定是數據一致的,而且沒有性能問題,因為標記是本地客戶端傳過去的。
但是無法保證其他用戶讀數據是一致的,但是實際場景很少需要保持其他用戶也保持強一致。延遲個幾秒也沒問題。
參考:讀寫分離數據庫如何保持數據一致性
4、刪除失敗怎么辦
雙刪策略,消息隊列。
四、分布式鎖
1、SET NX|EX 2、SETNX + EXPIRE + LUA 3、分布式鎖:redLock(有爭議)、zookeeper這個問題在之前文章中已經講過了,可參考Redis 分布式鎖。
五、Redis持久化機制
Redis的持久化有兩種aof和rdb,默認情況下,使用快照RDB的持久化方式。
RDB文件是一個緊湊的二進制壓縮文件,是Redis在某個時間點的全部數據快照。所以使用RDB恢復數據的速度遠遠比AOF的快,非常適合備份、全量復制、災難恢復等場景。
?
AOF是每次寫命令追加寫入日志中,主要作用是解決了數據持久化的實時性。
RDB 和 AOF 各有其優缺點, Redis 4.0 之后推出的 RDB-AOF 混合持久化模式,即以 RDB 作為全量備份,AOF 作為增量備份。修改配置文件redis.conf:
aof-use-rdb-preamble yes在該模式下,AOF 重寫產生的文件將同時包含 RDB 格式的內容和 AOF 格式的內容,該文件的前半段是 RDB 格式的全量數據,而后半段是 Redis 命令格式的增量數據,這個方法既能享受到 RDB 文件快速恢復的好處,又能享受到 AOF 只記錄操作命令的簡單優勢, 實際環境中用的很多.。
詳情可參考Redis 持久化。
六、秒殺場景
在雙11,618這樣的網絡購物節,平臺會提供一些物美價廉、限時限量的是商品或者優惠券來引流,Redis 經常用于這種秒殺場景。秒殺場景的兩個特征:瞬時并發高、多讀少寫;使用 Redis 可以攔截絕大部分的請求,把庫存存在 Redis 隊列中,超出數量直接返回結果搶購失敗 。
秒殺流程:在秒殺開始前大量用戶就是頻繁的刷新搶購頁面,秒殺開始后用戶點擊搶購按鈕需要返回搶購結果。所以為了解決秒殺問題在搶購頁面就需要做一些處理,如:搶購頁面靜態化、按鈕加鎖定。
解決思路:
秒殺開始前準備:
- 1、前端搶購頁面靜態化,使用CDN或者瀏覽器緩存。點擊搶購后鎖定按鈕或者打開新頁面等待搶購結果。
- 2、秒殺開始前 redis 開啟持久化,把商品和存庫都存入 redis 中。(庫存存到消息隊列中,通過LPOP取出,庫存數也要另存)
?
秒殺時:
- 1、redis使用 消息隊列 實現商品秒殺。每次點擊都從消息隊列中取出一個元素,當隊列的 LPOP 操作失敗則代表活動結束。(因為 redis 是單線程的,所以可以將并發請求串行化,而且 redis LPOP 操作是原子性的。)
- 2、同步庫存量到數據庫。數據庫開始處理搶購成功后的業務邏輯。
也可以使用樂觀鎖配合 incr實現秒殺。
示例代碼:
<?php header("content-type:text/html;charset=utf-8"); $redis = new redis(); $result = $redis->connect('127.0.0.1', 7379); // 把所有搶購數量導入列表 if ($redis->lpop("stock")) {$redis->hset("user_list", "user_id_" . rand(100000, 999999), 123);echo "搶購成功!<br/>"; } else { echo "已賣光!";exit; }// 計數器 - 創建活動商品時把庫存翻入消息隊列中 public function maple() {$count=999; // 庫存//添加到redis list中for($i=0;$i<$count;$i++){$redis->lpush('stock', 111);} }以上只寫了 Redis 的解決辦法,實際情況還是根據業務需要使用不同解決辦法。
七、Redis和Memcahe對比
說到緩存技術,就會想到redis和memcache。他們都是內存型數據庫。但互聯網用到的緩存技術絕大部分都是使用redis,那我們來分析一下redis和memcache的區別,以及redis是如何成為大眾的寵兒呢。
1、支持的數據類型不同
- memcache支持string類型,圖片,視屏等緩存;
- redis不僅支持簡單的k/v類型,還提供list,hash,set,zset等數據類型。
2、是否支持持久化
- memcache不支持持久化,數據都是在內存中,一旦停電則會造成數據丟失,且不可以恢復;
- redis支持持久化,通過RDB/AOF兩種方式來將數據持久化到磁盤,若停電還可以恢復數據。
3、內存空間和key數量
- memcache的key最長250個字符,value不能超過1MB(可以通過配置文件修改);
- redis的key和valve均不能超過512MB。(字符串值的最大長度為512MB)。
4、緩存時間
- memcache默認支持最多緩存30天;
- redis緩存時間沒有限制。
5、應用場景不同
- redis不僅用做數據緩存,還可以用來消息隊列,數據堆棧等方面;
- memcache適合簡單數據類型,熱點常用數據。
6、redis支持主從復制
八、Redis的過期策略及淘汰機制
1、過期策略
Redis的過期策略——“定期刪除+惰性刪除” 。
1、定期刪除
- 定期刪除是指Redis默認每隔100ms就隨機抽取 N 個設置了過期時間的key,檢測這些key是否過期,如果過期了就將其刪除。
- 100ms:在Redis的配置文件redis.conf中有一個屬性 hz,默認為10,表示1s執行10次定期刪除,即每隔100ms執行一次,可以修改這個配置值。
- N 個key:N 同樣是由redis.conf文件中的 maxmemory-samples 屬性決定的,默認為5。、
?
2、惰性刪除
- 惰性刪除不是去主動刪除,而是在你要獲取某個key 的時候,redis會先去檢測一下這個key是否已經過期,如果沒有過期則返回給你,如果已經過期了,那么redis會刪除這個key,不會返回給你。
2、淘汰機制
在配置文件redis.conf 中,可以通過參數 maxmemory 來設定最大內存,不設定該參數默認是無限制的。當內存不足時,就會采取內存淘汰機制,Redis內存淘汰方式有以下八種:
| no-eviction | 當內存不足以容納新寫入數據時,新寫入操作會報錯。(默認選項,一般不會選用) |
| allkeys-lru | 當內存不足以容納新寫入數據時,在整個鍵空間中,移除最近最少使用的key。(這個是最常用的) |
| allkeys-lfu | 當內存不足以容納新寫入數據時,在整個鍵空間中,移除最不經常(最少)使用的key。 |
| allkeys-random | 當內存不足以容納新寫入數據時,在整個鍵空間中,隨機移除某個key。 |
| volatile-lru | 當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的key。 |
| volatile-lfu | 當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最不經常(最少)使用的key。 |
| volatile-random | 當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個key。 |
| volatile-ttl | 當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的key優先移除。 |
在配置文件redis.conf 中,可以通過參數 maxmemory-policy 來設置淘汰的方式。
過期策略用于處理過期的緩存數據;內存淘汰策略用于處理內存不足時的需要申請額外空間的數據。所以內存淘汰策略的選取并不會影響過期的key的處理。
3、其他場景過期key的操作
1、快照生成RDB文件時:
- 過期的key不會被保存在RDB文件中。
?
2、服務重啟載入RDB文件時:
- Master載入RDB時,文件中的未過期的鍵會被正常載入,過期鍵則會被忽略。Slave 載入RDB 時,文件中的所有鍵都會被載入,當主從同步時,再和Master保持一致。
?
3、AOF 文件寫入時:
- 因為AOF保存的是執行過的Redis命令,所以如果redis還沒有執行del,AOF文件中也不會保存del操作,當過期key被刪除時,DEL 命令也會被同步到 AOF 文件中去。
?
4、重寫AOF文件時:
- 執行 BGREWRITEAOF 時 ,過期的key不會被記錄到 AOF 文件中。
?
5、主從同步時:
- Master 刪除 過期 Key 之后,會向所有 Slave 服務器發送一個 DEL命令,Slave 收到通知之后,會刪除這些 Key。
Slave 在讀取過期鍵時,不會做判斷刪除操作,而是繼續返回該鍵對應的值,只有當Master 發送 DEL 通知,Slave才會刪除過期鍵,這是統一、中心化的鍵刪除策略,保證主從服務器的數據一致性。
參考:Redis過期策略及內存淘汰機制
九、Redis常見性能問題及解決方案
1、主服務器寫內存快照,會阻塞主線程的工作,當快照比較大時對性能影響是非常大的,會間斷性暫停服務。
- 解決方案:主服務器最好不要寫內存快照。如果數據比較重要,某個 Slave 開啟 AOF 備份數據,策略設置為每秒同步一次;
?
2、Redis 主從復制的性能問題
- 解決方案:為了主從復制的速度和連接的穩定性,主從庫最好在同一個局域網內。盡量避免在壓力很大的主庫上增加從庫。
總結
本篇簡單總結一下面試時 Redis常問問題的回答,如果有錯誤或不全的地方歡迎補充。
總結
以上是生活随笔為你收集整理的Redis 面试常问问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【PMP】七、项目成本管理
- 下一篇: MySQL查询不同年份母亲节_不同国家的