Redis相關面試題
緩存三劍客
面試官:什么是緩存穿透 ? 怎么解決 ?
緩存穿透是指查詢一個一定不存在的數據,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到 DB 去查詢,可能導致 DB 掛掉。這種情況大概率是遭到了攻擊。
解決方案的話:
- 緩存空對象(實現簡單,維護方便),但是會有額外的內存消耗,可以設置合理的TTL,數據可能不一致
- 布隆過濾器
面試官:好的,你能介紹一下布隆過濾器嗎?
布隆過濾器主要是用于檢索一個元素是否在一個集合中。我們當時使用的是redisson實現的布隆過濾器。
它的底層主要是先去初始化一個比較大數組,里面存放的二進制0或1。在一開始都是0,當一個key來了之后經過3次hash計算,模于數組長度找到數據的下標然后把數組中原來的0改為1,這樣的話,三個數組的位置就能標明一個key的存在。查找的過程也是一樣的。
當然是有缺點的,布隆過濾器有可能會產生一定的誤判,我們一般可以設置這個誤判率,大概不會超過5%,其實這個誤判是必然存在的,要不就得增加數組的長度,其實已經算是很劃分了,5%以內的誤判率一般的項目也能接受,不至于高并發下壓倒數據庫。
面試官:什么是緩存擊穿 ? 怎么解決 ?
熱點key問題:一個被高并發訪問并且緩存重建業務較復雜的key突然失效了,無數的請求訪問會在瞬間給數據庫帶來巨大的沖擊。
解決方案有兩種方式:
-
互斥鎖:當緩存失效時,線程1先獲取一個互斥鎖,查詢數據庫實現緩存重建,此時其他線程獲取不到鎖只能休眠再重試,最后緩存重建完成后,其他線程可以查到緩存。
-
邏輯過期:
- 在設置key的時候,設置一個過期時間字段一塊存入緩存中,不給當前key設置過期時間
- 當查詢的時候,從redis取出數據后判斷時間是否過期
- 如果過期則開通另外一個線程進行數據同步,當前線程正常返回數據,這個數據不是最新
當然兩種方案各有利弊:
- 強一致性:分布式鎖的方案,性能上可能沒那么高,鎖需要等,也有可能產生死鎖的問題
- 高可用性:邏輯刪除,則優先考慮的高可用性,性能比較高,但是數據同步這塊做不到強一致。
面試官:什么是緩存雪崩 ? 怎么解決 ?
存雪崩是指在同一時段大量的緩存key同時失效或者Redis服務宕機,導致大量請求到達數據庫,帶來巨大壓力。
與緩存擊穿的區別:雪崩是很多key,擊穿是某一個key緩存。
解決方案:
- 給不同的Key的TTL添加隨機值
- 利用Redis集群提高服務的可用性
- 給緩存業務添加降級限流策略
- 給業務添加多級緩存
**面試官:**緩存的淘汰策略
- 內存淘汰(redis會自動將一些不常用的緩存清理)
- 超時剔除(超過了TTL,會自動清理)
- 主動更新(主動刪除緩存)
數據庫緩存不一致
面試官:redis做為緩存,mysql的數據如何與redis進行同步呢?(雙寫一致性)
性能高,數據不是強一致:我們當時是把商鋪的熱點數據存入到了緩存中,雖然是熱點數據,但是實時要求性并沒有那么高,所以,我們當時采用的延遲雙刪。
性能低,數據強一致:我們當時是把搶券的庫存存入到了緩存中,這個需要實時的進行數據同步,為了保證數據的強一致,我們當時采用的是redisson提供的讀寫鎖來保證數據的同步。
延遲雙刪:不能保證強一致,有臟數據的風險
- 不論先刪除哪個都有臟數據的風險
- 所以要刪除兩次緩存就是為了降低臟數據的風險
- 延遲刪除,因為數據庫是主從模式,讀寫是分離的,延時一會讓主節點把數據同步到從節點
分布式鎖:保證強一致,但是性能低。
共享鎖:讀鎖readLock,加鎖之后,其他線程可以共享讀操作
排他鎖:獨占鎖writeLock也叫,加鎖之后,阻塞其他線程讀寫操作
異步通知:保證數據最終一致性。
持久化
面試官:redis做為緩存,數據的持久化是怎么做的?
在Redis中提供了兩種數據持久化的方式:1、RDB 2、AOF
面試官:這兩種持久化方式有什么區別呢?
RDB是一個快照文件,它是把redis內存存儲的數據寫到磁盤上,當redis實例宕機恢復數據的時候,方便從RDB的快照文件中恢復數據。
AOF的含義是追加文件,當redis操作寫命令的時候,都會存儲這個文件中,當redis實例宕機恢復數據的時候,會從這個文件中再次執行一遍命令來恢復數據。
面試官:這兩種方式,哪種恢復的比較快呢?
RDB因為是二進制文件,在保存的時候體積也是比較小的,它恢復的比較快,但是它有可能會丟數據,我們通常在項目中也會使用AOF來恢復數據,雖然AOF恢復的速度慢一些,但是它丟數據的風險要小很多,在AOF文件中可以設置刷盤策略,我們當時設置的就是每秒批量寫入一次命令
RDB和AOF各有自己的優缺點,如果對數據安全性要求較高,在實際開發中往往會結合兩者來使用。
數據過期策略
面試官:假如redis的key過期之后,會立即刪除嗎?
惰性刪除:在設置該key過期時間后,我們不去管它,當使用key時,再檢查其是否過期,如果過期就刪掉它,反之返回該key。
定期刪除:每隔一段時間,我們就對一些key進行檢查,刪除里面過期的key。
定期清理的兩種模式:
- SLOW模式是定時任務,執行頻率默認為10hz,每次不超過25ms,以通過修改配置文件redis.conf 的 hz 選項來調整這個次數
- FAST模式執行頻率不固定,每次事件循環會嘗試執行,但兩次間隔不低于2ms,每次耗時不超過1ms
Redis的過期刪除策略:惰性刪除 + 定期刪除兩種策略進行配合使用。
數據淘汰策略
面試官:假如緩存過多,內存是有限的,內存被占滿了怎么辦?
嗯,這個在redis中提供了很多種,默認是noeviction,不刪除任何數據,內部不足直接報錯
是可以在redis的配置文件中進行設置的,里面有兩個非常重要的概念,一個是LRU,另外一個是LFU
LRU:最少最近使用,用當前時間減去最后一次訪問時間,這個值越大則淘汰優先級越高。
LFU:最少頻率使用。會統計每個key的訪問頻率,值越小淘汰優先級越高
面試官:數據庫有1000萬數據,Redis只能緩存20w數據,如何保證Redis中的數據都是熱點數據 ?
可以使用 allkeys-lru (挑選最近最少使用的數據淘汰)淘汰策略,那留下來的都是經常訪問的熱點數據
面試官:Redis的內存用完了會發生什么?
這個要看redis的數據淘汰策略是什么,如果是默認的配置,redis內存用完以后則直接報錯。我們當時設置的 allkeys-lru 策略。把最近最常訪問的數據留在緩存中。
Redis分布式鎖
面試官:Redis分布式鎖如何實現 ?
嗯,在redis中提供了一個命令setnx(SET if not exists)
由于redis的單線程的,用了命令之后,只能有一個客戶端對某一個key設置值,在沒有過期或刪除key的時候是其他客戶端是不能設置這個key的
面試官:好的,那你如何控制Redis實現分布式鎖有效時長呢?
的確,redis的setnx指令不好控制這個問題,我們當時采用的redis的一個框架redisson實現的。
在redisson中需要手動加鎖,并且可以控制鎖的失效時間和等待時間,當鎖住的一個業務還沒有執行完成的時候,在redisson中引入了一個看門狗機制,就是說每隔一段時間就檢查當前業務是否還持有鎖,如果持有就增加加鎖的持有時間,當業務執行完成之后需要使用釋放鎖就可以了
還有一個好處就是,在高并發下,一個業務有可能會執行很快,先客戶1持有鎖的時候,客戶2來了以后并不會馬上拒絕,它會自旋不斷嘗試獲取鎖,如果客戶1釋放之后,客戶2就可以馬上持有鎖,性能也得到了提升。
面試官:好的,redisson實現的分布式鎖是可重入的嗎?
是可以重入的。這樣做是為了避免死鎖的產生。這個重入其實在內部就是判斷是否是當前線程持有的鎖,如果是當前線程持有的鎖就會計數,如果釋放鎖就會在計算上減一。在存儲數據的時候采用的hash結構,大key可以按照自己的業務進行定制,其中小key是當前線程的唯一標識,value是當前線程重入的次數。
面試官:redisson實現的分布式鎖能解決主從一致性的問題嗎?
這個是不能的,比如,當線程1加鎖成功后,master節點數據會異步復制到slave節點,此時當前持有Redis鎖的master節點宕機,slave節點被提升為新的master節點,假如現在來了一個線程2,再次加鎖,會在新的master節點上加鎖成功,這個時候就會出現兩個節點同時持有一把鎖的問題。
我們可以利用redisson提供的紅鎖來解決這個問題,它的主要作用是,不能只在一個redis實例上創建鎖,應該是在多個redis實例上創建鎖,并且要求在大多數redis節點上都成功創建鎖,紅鎖中要求是redis的節點數量要過半。這樣就能避免線程1加鎖成功后master節點宕機導致線程2成功加鎖到新的master節點上的問題了。
但是,如果使用了紅鎖,因為需要同時在多個節點上都添加鎖,性能就變的很低了,并且運維維護成本也非常高,所以,我們一般在項目中也不會直接使用紅鎖,并且官方也暫時廢棄了這個紅鎖
面試官:好的,如果業務非要保證數據的強一致性,這個該怎么解決呢?
redis本身就是支持高可用的,做到強一致性,就非常影響性能,所以,如果有強一致性要求高的業務,建議使用zookeeper實現的分布式鎖,它是可以保證強一致性的。
Redis集群
面試官:Redis集群有哪些方案, 知道嘛 ?
在Redis中提供的集群方案總共有三種:主從復制、哨兵模式、Redis分片集群
面試官:那你來介紹一下主從同步
是這樣的,單節點Redis的并發能力是有上限的,要進一步提高Redis的并發能力,可以搭建主從集群,實現讀寫分離。一般都是一主多從,主節點負責寫數據,從節點負責讀數據,主節點寫入數據之后,需要把數據同步到從節點中。
面試官:能說一下,主從同步數據的流程?
主從同步分為了兩個階段,一個是全量同步,一個是增量同步
全量同步是指從節點第一次與主節點建立連接的時候使用全量同步,流程是這樣的:
第一:從節點請求主節點同步數據,其中從節點會攜帶自己的replication id和 offset 偏移量。
第二:主節點判斷是否是第一次請求,主要判斷的依據就是,主節點與從節點是否是同一個replication id,如果不是,就說明是第一次同步,那主節點就會把自己的replication id和offset發送給從節點,讓從節點與主節點的信息保持一致。
第三:在同時主節點會執行bgsave,生成rdb文件后,發送給從節點去執行,從節點先把自己的數據清空,然后執行主節點發送過來的rdb文件,這樣就保持了一致
當然,如果在rdb生成執行期間,依然有請求到了主節點,而主節點會以命令的方式記錄到緩沖區,緩沖區是一個日志文件,最后把這個日志文件發送給從節點,這樣就能保證主節點與從節點完全一致了,后期再同步數據的時候,都是依賴于這個日志文件,這個就是全量同步
增量同步流程
當從節點服務重啟之后,數據就不一致了,所以這個時候,從節點會請求主節點同步數據,主節點還是判斷不是第一次請求,不是第一次就獲取從節點的offset值,然后主節點從命令日志中獲取offset值之后的數據,發送給從節點進行數據同步。
Redis的高并發高可用
面試官:怎么保證Redis的高并發高可用
首先可以搭建主從集群,再加上使用redis中的哨兵模式,哨兵模式可以實現主從集群的自動故障恢復(對主從服務的監控、自動故障恢復、通知);如果master故障,Sentinel會將一個slave提升為master。當故障實例恢復后也以新的master為主;同時Sentinel也充當Redis客戶端的服務發現來源,當集群發生故障轉移時,會將最新信息推送給Redis的客戶端,所以一般項目都會采用哨兵的模式來保證redis的高并發高可用。
面試官:你們使用redis是單點還是集群,哪種集群
我們當時使用的是主從(1主1從)加哨兵。一般單節點不超過10G內存,如果Redis內存不足則可以給不同服務分配獨立的Redis主從節點。盡量不做分片集群。因為集群維護起來比較麻煩,并且集群之間的心跳檢測和數據通信會消耗大量的網絡帶寬,也沒有辦法使用lua腳本和事務。
面試官:redis集群腦裂,該怎么解決呢?
有的時候由于網絡等原因可能會出現腦裂的情況,就是說,由于redis master節點和redis salve節點和sentinel處于不同的網絡分區,使得sentinel沒有能夠心跳感知到master,所以通過選舉的方式提升了一個salve為master,這樣就存在了兩個master,就像大腦分裂了一樣,這樣會導致客戶端還在old master那里寫入數據,新節點無法同步數據,當網絡恢復后,sentinel會將old master降為salve,這時再從新master同步數據,這會導致old master中的大量數據丟失。
關于解決的話,我記得在redis的配置中可以設置:第一可以設置最少的salve節點個數,比如設置至少要有一個從節點才能同步數據,第二個可以設置主從數據復制和同步的延遲時間,達不到要求就拒絕請求,就可以避免大量的數據丟失。
redis的分片集群
面試官:redis的分片集群有什么作用?
分片集群主要解決的是,海量數據存儲的問題,集群中有多個master,每個master保存不同數據,并且還可以給每個master設置多個slave節點,就可以繼續增大集群的高并發能力。同時每個master之間通過ping監測彼此健康狀態,就類似于哨兵模式了。當客戶端請求可以訪問集群任意節點,最終都會被轉發到正確節點。
面試官:Redis分片集群中數據是怎么存儲和讀取的?
Redis 集群引入了哈希槽的概念,有 16384 個哈希槽,集群中每個主節點綁定了一定范圍的哈希槽范圍, key通過 CRC16 校驗后對 16384 取模來決定放置哪個槽,通過槽找到對應的節點進行存儲。
取值的邏輯是一樣的
Redis是單線程
面試官:Redis是單線程的,但是為什么還那么快?
1、完全基于內存的,C語言編寫
2、采用單線程,避免不必要的上下文切換可競爭條件
3、使用多路I/O復用模型,非阻塞IO
例如:bgsave 和 bgrewriteaof 都是在后臺執行操作,不影響主線程的正常使用,不會產生阻塞
面試官:能解釋一下I/O多路復用模型?
I/O多路復用是指利用單個線程來同時監聽多個Socket ,并在某個Socket可讀、可寫時得到通知,從而避免無效的等待,充分利用CPU資源。目前的I/O多路復用都是采用的epoll模式實現,它會在通知用戶進程Socket就緒的同時,把已就緒的Socket寫入用戶空間,不需要挨個遍歷Socket來判斷是否就緒,提升了性能。
其中Redis的網絡模型就是使用I/O多路復用結合事件的處理器來應對多個Socket請求,比如,提供了連接應答處理器、命令回復處理器,命令請求處理器;
在Redis6.0之后,為了提升更好的性能,在命令回復處理器使用了多線程來處理回復事件,在命令請求處理器中,將命令的轉換使用了多線程,增加命令轉換速度,在命令執行的時候,依然是單線程