應知應會的 33 條 Redis 基礎八股文
今天對 Redis 八股文進行收官總結,共收錄了 33 條基礎八股文。
文章目錄
- 應知應會的 33 條 Redis 基礎八股文
- Redis 持久化
- 簡述 Redis 持久化的兩種策略?
- AOF 的三種持久化策略?
- AOF 磁盤重寫機制?
- 為什么先執行一個 Redis 寫命令,再將該命令寫入到 AOF 緩沖區?
- AOF 重寫的具體過程?
- AOF 子進程內存跟主進程內存不一致怎么辦?
- RDB 執行快照時,數據可以修改嗎?
- Redis 過期機制?
- Redis 內存淘汰策略?
- Redis 持久化過程中如何處理過期的鍵?
- Redis 主從集群中,如何處理過期的鍵?
- Redis 應用
- 緩存雪崩、緩存擊穿、緩存穿透的現象解釋及解決辦法?
- 如何保證數據庫和緩存的一致性?
- 如何保證緩存刪除一定成功?
- 針對業務一致性要求高的場景,如何確保緩存與數據庫的一致性?
- 如何避免緩存失效?
- 如何實現延遲隊列?
- 如何設計一個緩存策略,以動態緩存熱點數據?
- Redis 實現分布式鎖?
- Redis 分布式鎖的優缺點?
- Redis 如何解決集群模式下分布式鎖的可靠性?
- Redis 管道有什么作用?與事務的區別?
- Redis 的線程模型
- 簡述 Redis 的線程模型
- Redis 集群
- Redis 集群模式有哪些?
- Redis 切片集群的工作原理
- 主從模式的同步過程?
- 主服務器如何知道要將哪些數據發送給從服務器?
- 如何避免主從復制的不一致?
- 主從結構中過期的 key 如何處理?
- 主從復制是同步復制還是異步復制?
- 什么是哨兵機制?
- 什么是集群的腦裂?
- 如何避免 Sentinel 集群當中主從切換導致的數據不一致?
Redis 持久化
簡述 Redis 持久化的兩種策略?
- RDB:Redis 自動或手動
fork()
一個子進程,對fork()
時刻的數據生成一個全量的數據快照,RDB 持久化期間 Redis 服務器仍然可以接收客戶端的請求,使用 COW 寫時復制來接收新的寫請求修改數據頁。RDB 文件較小,通常用于 Redis 備份或容災恢復,存在的問題是不夠安全,如果 Redis 服務宕機,那么 RDB 快照生成期間的數據都會丟失。 - AOF:Redis 每接收到一個寫請求,就先執行這個請求,再根據 AOF 寫會策略將該語句持久化到 AOF 文件當中。AOF 的文件體積通常較大,但是它的優點是可以確保 Redis 的數據安全性,在
everysec
寫回策略下至多丟失一秒的數據。 - 混合持久化:即同時使用 RDB 和 AOF,RDB 直接寫入 AOF 文件當中。具體來說,Redis 服務仍然開啟一個子進程來復制全量快照,在此期間新增的 Redis 寫請求先寫入到 AOF 文件當中。混合持久化兼顧了 RDB 的緊湊性以及 AOF 的安全性。
AOF 的三種持久化策略?
always
:每條 Redis 寫操作執行后都先寫入 AOF 緩沖區,再直接持久化到磁盤,安全性最好,但是性能開銷較大;everysec
:每秒將 AOF 緩沖區當中的數據持久化到磁盤,最多丟失一秒的數據;no
:由 OS 擇機將緩沖區中的數據持久化到磁盤,安全性欠佳,但是性能開銷最小。
AOF 磁盤重寫機制?
當 Redis 寫請求過多后,AOF 文件中可能存在重復的命令以及失效的命令,導致 Redis AOF 文件較大。
AOF 磁盤重寫機制就是開啟一個子進程將當前 Redis 緩存中的狀態逐條轉換寫操作,并寫入新的 AOF 文件來替換舊的 AOF 文件。
具體來說,AOF 重寫觸發后,Redis 會 fork()
一個子進程,負責將 fork()
時刻的快照數據轉為 Redis 寫命令,在此期間新的客戶端寫請求會寫入到 AOF 緩沖區和 AOF 重寫緩沖區,通過 COW 寫時復制技術來處理新的寫命令產生的修改。AOF 重寫完成后,AOF 重寫緩沖區當中的命令會追加到新的 AOF 文件,之后替換舊的 AOF 文件,完成寫時復制。
為什么先執行一個 Redis 寫命令,再將該命令寫入到 AOF 緩沖區?
如果先將命令寫入到 AOF 緩沖區再執行 Redis 寫命令,不能保證寫入緩沖區的命令一定正確,后續通過 AOF 加載 Redis 緩存時還需要逐條檢查命令的正確性,時間開銷較大。
先執行 Redis 寫命令,再寫入緩沖區可以保證寫入到 AOF 文件當中的寫命令一定是正確的。
AOF 重寫的具體過程?
詳見方才所說的 AOF 磁盤重寫機制,此處不再重復。
AOF 子進程內存跟主進程內存不一致怎么辦?
指的是 AOF 重寫開啟后的情況。
AOF 重寫開始時,Redis fork()
一個子進程,并與主進程共享該時刻的數據頁,當主進程接收到新的來自客戶端的寫命令后會通過 COW 寫時復制,對要修改的數據頁進行復制,并在復制的數據頁上執行實際的修改,子進程看到的仍然是 fork()
時刻的舊數據頁。
RDB 執行快照時,數據可以修改嗎?
可以修改,和上一個問題的回答同理。
Redis 過期機制?
Redis 可以為 Key 設置 TTL,基于惰性刪除 + 定期刪除來刪除過期鍵。
惰性刪除
- Redis 讀請求到來時,會先檢查 Key 是否過期,如果過期則直接刪除,并返回
nil
; - 存在的問題時,有一些過期的數據可能永遠不會被訪問到,導致空間浪費,所以需要結合定期刪除。
定期刪除
- Redis 每隔一段時間就會隨機抽取部分 Key 進行過期檢查,如果發現抽取的 Key 當中有超過 25% 的 Key 都過期了,則重新抽取一次直到抽取的 Key 中過期比例小于 25% 或超時;
- 優點是降低了內存泄露的風險,存在的問題是無法刪除所有過期鍵。
Redis 內存淘汰策略?
不淘汰
Redis 內存滿時,只讀不可寫,新的寫請求到來后會直接返回操作失敗。
淘汰過期鍵
分為隨機淘汰、優先淘汰過期時間較早的鍵,以及淘汰最近最久未使用和最近最少使用的鍵。
淘汰任意鍵
分為隨機淘汰,淘汰最近最久未使用和最近最少使用。
Redis 持久化過程中如何處理過期的鍵?
RDB
- 生成 RDB 快照時:跳過過期的鍵;
- 加載 RDB 快照時:如果 Redis 以主節點啟動,會檢查鍵的過期時間,加載后立即刪除。如果以從節點啟動,由于從節點只讀,因此不會刪除過期鍵,依賴主節點同步
DEL
命令刪除。
AOF
- AOF 文件記錄:寫入 AOF 的過期鍵以
DEL
命令形式記錄; - AOF 重寫:跳過過期鍵。如果鍵在重啟期間過期,主進程會追加
DEL
到緩沖區,緩沖區當中的命令會追加到新的 AOF 文件中。
Redis 主從集群中,如何處理過期的鍵?
從節點不具備寫的能力,因此如果客戶端訪問從節點當中的過期鍵仍然可以得到值。從庫中鍵的過期依賴主節點的同步。
Redis 應用
緩存雪崩、緩存擊穿、緩存穿透的現象解釋及解決辦法?
緩存雪崩:大量鍵同時過期,或是 Redis 宕機,此時恰有大量請求到來,由于在緩存中得不到回復,會進一步查數據庫,導致數據庫壓力驟增,嚴重時會導致服務崩潰。解決辦法是均勻設置鍵的過期時間,或是在 Redis 宕機時觸發熔斷降級停止服務,或是在請求到數據庫中重新構建緩存時加鎖。
緩存擊穿:熱點數據在緩存中過期,請求擊穿緩存到數據庫查數據。解決辦法是不為熱點數據設置過期時間,以及請求查數據庫重放緩存時加鎖。
緩存穿透:不斷有惡意請求訪問緩存中不存在的數據,導致系統先查緩存再查數據庫,增加系統開銷。解決辦法是在緩存中設置一個默認值或零值,或是將頻繁惡意請求的客戶端拉黑。
如何保證數據庫和緩存的一致性?
Cache-Aside(旁路緩存):寫請求到來時,先修改數據庫,修改數據庫后刪除緩存。
Read/Write Through:依賴于緩存自身的組件,比如 Redis 企業版。當讀請求到來時,如果能在緩存中查到則直接返回,否則依賴于緩存組件到數據庫中查數據并放回到緩存,返回響應。當寫請求到來時,直接寫緩存,由緩存組件將修改重放到數據庫。
Write Behind:所有寫請求都先寫緩存,依賴于異步線程將緩存中的修改批量同步到數據庫。
延遲雙刪:寫請求到來時,先刪除緩存并向 MQ 發送一個延遲消息。然后到數據庫中修改數據。MQ 中消息到期后再次刪除緩存,避免寫時的并發讀將臟數據重放到緩存。
基于 binlog 的最終一致性:所有寫請求都修改數據庫,數據庫的修改會記錄到 binlog,通過 Canal 這類中間件偽裝成 MySQL 從庫拉取 binlog 中的修改并將修改內容打包為消息事件發送到 MQ,作為主題訂閱方的緩存拉取消息得到 binlog 并消費,修改緩存當中的內容,確保緩存與數據庫的最終一致性。
如何保證緩存刪除一定成功?
引入 MQ
將緩存刪除的消息打包為事件放入 MQ 當中,即使緩存宕機,消息事件沒有被消費就不會丟失,將緩存刪除交由消費者來做,可以確保緩存一定被刪除。
基于 binlog 的最終一致性
所有刪除操作都會記錄到 bin log,基于 Canal + MQ 可以確保緩存作為訂閱方消費刪除事件,保證緩存被刪除。
針對業務一致性要求高的場景,如何確保緩存與數據庫的一致性?
旁路緩存、Read/Write Through、延遲雙刪、基于 binlog 的最終一致性。
如何避免緩存失效?
有兩種方式。
最簡單的方式是業務上線之后開啟一個后臺線程持續檢查緩存是否有效,如果失效立馬查數據庫重放緩存。
進階的方式是緩存失效后,業務線程將重放緩存的操作打包為消息事件發送到 MQ。后臺緩存重放線程從 MQ 消費消息之后再次查緩存,如果緩存失效就去數據庫重放緩存。
如何實現延遲隊列?
通過 Redis 的 ZSET 數據結構可以實現延遲隊列。具體來說,設置 ZSET 中 value 的 score 為到期時間,它的值可以設置為當前時間 + 延遲時間
。要處理延遲消息時,通過 zrangebyscore
查找出所有 score 小于當前時間的消息,就是已經到期的消息,處理這些消息即可。
如何設計一個緩存策略,以動態緩存熱點數據?
仍然可以通過 ZSET 數據結構來完成。比如針對一件商品,它的瀏覽量可以作為商品熱點的評價依據,每被瀏覽一次,就通過 zadd
為該商品添加相應的分數。可以使用時間來對分數進行初始化,使得商品的熱度隨時間下降。
Redis 實現分布式鎖?
Redis 分布式鎖的原理是:通過一個全局可見的共享內存空間實現跨節點的分布式鎖。通過命令 SET key unique_value NX PX time_out
來加鎖。NX 可以確保只有 key 不存在時加鎖成功,PX 是設置鎖的過期時間為 time_out。unique_value 是 key 的唯一標識,在釋放鎖的時候,可以通過 lua 腳本確保鎖校驗和鎖釋放的原子性。
Watchdog 鎖續期機制
Redis 分布式鎖有 Watchdog 鎖續期機制,客戶端獲取鎖之后,會開啟一個后臺線程,如果鎖要到期但是業務執行尚未結束,會延長鎖的時間。業務執行結束后由后臺線程釋放鎖。
Redis 分布式鎖單點故障
通過 RedLock 算法可以避免單點故障。具體來說,客戶端想要獲取 Redis 分布式鎖時,需要向若干個 Redis 集群或 Redis 單例發送加鎖請求,多數 Redis 主節點同意之后才算加鎖成功。鎖釋放時,同樣需要向這些 Redis 主節點發送鎖釋放請求。
Redis 分布式鎖的優缺點?
- 優點:性能高效,實現方便,可以通過 RedLock 避免單點故障。
- 缺點:分布式鎖的過期時間不好把控(但是可以通過 Watchdog 續期)。此外,Redis 主從復制是異步的,這可能會導致分布式鎖的不一致,例如 Redis Sentinel 集群中,Redis 主節點獲取鎖之后還沒來得及同步到從節點就宕機了,此時主持 Sentinel 選取從節點成為新的主節點,這個主節點仍然可以接收加鎖請求,導致鎖沖突。解決單點故障(一個 Sentinel 集群只有一個主節點,所以 Redis 集群也是單點,多個主節點才構成多點以避免單點故障)的方法是通過 RedLock 向多個 Redis 主節點請求加鎖,多數同意才視為加鎖成功。
Redis 如何解決集群模式下分布式鎖的可靠性?
RedLock 算法。
其具體步驟為:
- 客戶端請求加鎖,記錄當前時刻 t 1 t_1 t1?;
- 多數 Redis 主節點收到加鎖請求并響應給客戶端,客戶端才認為加鎖成功,此時記錄時刻 t 2 t_2 t2?;
- 計算獲取鎖的時間 t 2 ? t 1 t_2 - t_1 t2??t1?,如果大于鎖的過期時間則認為加鎖失敗,向所有節點發送鎖釋放請求;
- 否則認為加鎖成功,鎖的過期時間為 t i m e o u t ? ( t 2 ? t 1 ) timeout - (t_2 - t_1) timeout?(t2??t1?)。
Redis 管道有什么作用?與事務的區別?
Redis 的 Pipeline 指的是在客戶端累積若干個 Redis 請求一并發送到 Redis 服務器,Redis 服務器處理這些請求后將返回結果打包發回給客戶端。Pipeline 的核心作用是降低 Redis 請求與相應的 RTT,即減少網絡 I/O。Pipeline 中即使有請求失敗,也不會影響其他請求的執行。
事務通過 MULTI
和 EXEC
命令執行,如果這兩條命令之間存在語法錯誤,則認為事務失敗,否則即使有一條命令執行失敗,也不會導致事務中其他命令執行失敗。故 Redis 的事務不具備回滾能力,但由于 Redis 采用單線程模型,每一條命令天然具有原子性。
在實際使用中,推薦使用 lua 腳本來實現 Redis 事務,因為 lua 腳本具備事務回滾的能力,并且 lua 腳本當中可以引入復雜的邏輯判斷。
Redis 的線程模型
簡述 Redis 的線程模型
Redis 在底層有一個文件事件處理器,它是單線程的,因此 Redis 是單線程的。文件事件處理器分為兩個部分,分別是事件分派器和事件處理器。具體來說,文件事件處理器會通過多路 I/O 復用技術監聽多個 socket,將產生的事件壓入事件分派器當中,事件分派器將具體的事件發送給特定的事件處理器處理事件,比如連接應答處理器、命令請求處理器、命令回復處理器等。
Redis 6.0 之前和之后,處理具體命令都是通過單線程來完成的,只不過 6.0 之前,網絡 I/O 等請求也是通過單線程來完成的,6.0 之后引入多線程輔助網絡 I/O。
Redis 集群
Redis 集群模式有哪些?
- 主從:集群當中有且僅有一個主節點,以及若干個從節點。主節點可讀可寫,從節點只讀。
- 哨兵:主從集群的升級版,集群中仍然有且只有一個主節點,至少 3 個(一定是奇數個)哨兵節點,以及若干個從節點。哨兵的引入為 Redis 集群提供了故障恢復和故障轉移功能,并可以持續向客戶端同步 Redis 服務的狀態。
- 切片:針對緩存較大的情況,切片集群將數據劃分到若干個節點當中,這些節點都是主節點,它們都是可讀可寫的,每個主節點有自己的若干個只讀的從節點。通過 CRC 算法實現 Key 到哈希槽的映射,哈希槽在集群上線時自動或手動地分配到具體的物理節點。
Redis 切片集群的工作原理
切片集群有 16384 個哈希槽,通過 CRC 算法將 Key 映射為 0~65536 之間的一個整型,在取模映射到各個哈希槽。哈希槽在集群上線時自動或手動分配到具體的切片集群節點。
主從模式的同步過程?
從節點與主節點建立 socket 連接之后,維護主節點的 run_id 以及一個 offset。
之后,從節點向主節點發送同步請求,主節點會生成一個 RDB 文件發送給從節點,并將 RDB 快照生成期間的數據記錄到緩沖區中,RDB 發送之后將緩沖區中的命令一并發送到從節點。
從節點加載 RDB 以及緩沖區命令完成同步。
在增量同步階段,通過主從節點各自維護的 offset 確定數據同步進度。如果差距過大,則重新進行全量同步。需要補充的一點是,在增量同步階段,主節點向從節點同步的是具體的 Redis 命令。
主服務器如何知道要將哪些數據發送給從服務器?
主從集群當中,主從節點各自維護一個 offset。增量同步階段,主從之間比對 offset 確定進度。如果從節點下線后再次上線,要讀取的數據在 repl_backlog_buffer 中,則采用增量同步,否則重新進行全量同步。
如何避免主從復制的不一致?
無法完全避免。可行的方法包括:將主從節點在物理上放置在同一個機房降低網絡傳輸延遲;或者由外部進程監控同步進度,差距過大時下線從節點,讓客戶端直接訪問主節點。
主從結構中過期的 key 如何處理?
依賴于主節點同步過期鍵刪除的命令。從節點不會主動刪除過期鍵,即使有客戶端請求到來,也不會惰性刪除過期鍵,而是返回值。
主從復制是同步復制還是異步復制?
異步復制。主節點接收到寫命令先寫入緩沖區,再擇機同步給從節點,確保高可用低時延。
什么是哨兵機制?
Sentinel 機制的引入是對主從集群的升級,進一步為主從集群引入了故障恢復和故障轉移功能,并可以持續同步給客戶端集群的狀態。
具體來說,當主節點因故障宕機時:
- 主觀下線:單個 Sentinel 節點檢測到主節點宕機;
- 客觀下線:多個 Sentinel 之間達成主節點宕機的共識;
客觀下線后,Sentinel 之間選舉出一個領導人,領導者 Sentinel 會選出一個最優的從節點作為新的主節點,重新上線服務并將 Redis 服務的狀態同步給客戶端。
多個 Sentinel 之間通過 Raft 算法確保達成共識。
什么是集群的腦裂?
存在于可能的網絡分區故障當中。比如 Redis 主節點和少部分 Sentinel 處于分區 A,大量從節點和大量 Sentinel 處于分區 B。當分區 A 和 B 之間因故斷聯后,B 區多數 Sentinel 認為 Redis 主節點宕機而客觀下線,選舉出一個新的 Redis 主節點,此時分區故障恢復,會短暫地存在兩個 Redis 主節點,舊的主節點發現已經有了新的主節點會自動降級,但是分區斷聯時 A 區的主節點處理的寫請求會丟失,造成數據不一致,這就是集群的腦裂。
如何避免 Sentinel 集群當中主從切換導致的數據不一致?
異步復制同步丟失
Redis 主節點接收寫請求時,需要確保至少有一部分從節點同步到了新的寫命令才繼續接收客戶端的請求,避免未同步就宕機。如果從節點的延遲超過閾值或從節點數量不足,直接拒絕寫入。
合理配置 Sentinel 數量
避免某個分區(特別是與主節點不同區的分區)的 Sentinel 過多,產生由于網絡抖動而造成的腦裂。