Redis四種模式
單節點模式
架構??:單個Redis實例運行在單臺服務器。
??優點??:
??簡單??:部署和配置容易,適合開發和測試。
??低延遲??:無網絡通信開銷。
??缺點??:
??單點故障??:節點故障會導致服務不可用。
??容量受限??:數據量受限于單機內存。
??性能瓶頸??:讀寫壓力集中在單節點,無法擴展。
??適用場景??:
本地開發、測試環境。
數據量小且對可用性要求不高的場景。
主從模式(Replication)?
主從模式的核心原理?
- 角色劃分??:
??主節點(Master)??:唯一處理寫操作(SET, DEL等),數據變更后異步復制到從節點。
??從節點(Slave/Replica)??:通過復制主節點數據保持與主節點一致,僅處理讀操作(GET等)。 - ??數據復制流程??:
??建立連接??:從節點啟動后向主節點發送 SYNC 或 PSYNC 命令,請求同步數據。
??全量復制(RDB快照)??:主節點生成當前數據的 RDB 快照發送給從節點,從節點加載快照完成初始同步。
??增量復制(Replication Buffer)??:主節點將后續的寫命令緩存在 repl_backlog_buffer 中,從節點持續接收并執行這些命令以保持數據一致。
??心跳檢測??:主從節點通過定期發送 PING 和 REPLCONF ACK 維持連接,檢測網絡和節點狀態。 - 復制模式??:
??全量復制??:首次連接或復制中斷后無法增量恢復時觸發(資源消耗大)。
??增量復制??(Redis 2.8+):基于 PSYNC 命令,通過偏移量(offset)和復制積壓緩沖區(repl_backlog)實現斷點續傳。
主從模式拓撲結構??:
一主多從??:單個主節點掛載多個從節點,適合讀請求量大的場景。
??樹狀結構??:從節點可以作為其他從節點的主節點(級聯復制),減輕主節點壓力。
Master → Slave1 → Slave2
↘ Slave3
主從模式的優缺點?
- 優點??:
??讀寫分離??:
主節點處理寫請求,從節點分擔讀請求,提升讀并發能力。
??數據冗余??:
從節點是主節點的完整副本,提供數據備份,防止單點數據丟失。
??災備恢復??:
主節點故障時,可手動將從節點提升為新主節點(需配合哨兵實現自動化)。 - ??缺點??:
??主節點單點故障??:
主節點宕機后需手動切換從節點,服務會短暫不可用。
??復制延遲??:
異步復制可能導致從節點數據短暫不一致(寫入主節點后,從節點未及時同步)。
??寫能力受限??:
所有寫操作仍由主節點處理,無法橫向擴展寫性能。
主從數據同步詳解
Redis 主從模式通過??異步復制??實現數據同步,核心目標是讓從節點(Slave/Replica)與主節點(Master)保持數據一致。其同步過程分為 ??全量同步(Full Sync)?? 和 ??增量同步(Partial Sync)?? 兩種機制,具體流程如下:
全量同步(Full Sync)?
- 觸發條件??:
從節點首次連接主節點。
主從節點復制偏移量(offset)差距過大,超出主節點repl_backlog緩沖區范圍。
主節點重啟或切換導致 run_id 變更,從節點無法識別新主節點。 - 全量同步流程??:
a ??建立連接??
從節點發送 PSYNC ? -1 命令請求同步(若 Redis 版本 < 2.8,使用 SYNC 命令)。
b ??主節點生成 RDB 快照??
主節點調用 bgsave 后臺生成當前數據的 ??RDB 快照文件??。
生成期間的新寫命令會存入 ??復制緩沖區(Replication Buffer)??。
c ??傳輸 RDB 文件??
RDB 文件生成完成后,主節點將其發送給從節點。
傳輸期間主節點繼續處理寫命令,并緩存到復制緩沖區。
d ??從節點加載 RDB??
從節點清空舊數據,加載 RDB 文件完成初始數據同步。
加載完成后,主節點將復制緩沖區中的寫命令發送給從節點執行。
e ??進入增量同步??
全量同步完成后,主從進入增量同步階段,主節點持續推送新寫命令。 - ?全量同步資源消耗??:
??主節點??:生成 RDB 消耗 CPU/內存,傳輸 RDB 占用網絡帶寬。
??從節點??:加載 RDB 時可能阻塞其他操作(取決于配置)。
增量同步(Partial Sync)?
- 觸發條件??:
主從節點斷開后重連,且從節點的復制偏移量(offset)仍在主節點的 repl_backlog 緩沖區內。 - 增量同步流程??:
a 從節點發送 PSYNC 命令??
攜帶主節點 run_id 和自身記錄的復制偏移量(offset)。
??b 主節點校驗偏移量??
若從節點的 offset 在 repl_backlog 緩沖區內,則發送缺失的寫命令。
否則觸發全量同步(例如緩沖區已被覆蓋)。
c ??同步缺失數據??
主節點發送從 offset 之后的所有寫命令,從節點執行這些命令更新數據。 - 核心組件??:
??復制偏移量(offset)??
主從節點各自維護一個偏移量,表示已復制的數據字節數。
主節點每次接收寫命令,offset 增加;從節點復制后更新自身 offset。
??復制積壓緩沖區(repl_backlog)??
主節點維護的??環形內存緩沖區??,默認大小 1MB(可配置)。
記錄最近一段時間(由緩沖區大小決定)的寫命令,用于增量同步。
??主節點運行 ID(run_id)??
每個主節點啟動時生成唯一 run_id,從節點記錄該 ID。
主節點重啟或切換時,run_id 變更,從節點需重新全量同步。
哨兵模式(Sentinel)?
Redis 哨兵模式是 Redis ??高可用性(HA)??的核心解決方案,用于自動化監控主從節點、故障檢測與轉移,保障服務持續可用。(哨兵模式基于主從復制模式,只是引入了哨兵來監控與自動處理故障。)
哨兵模式的核心功能?
- 監控(Monitoring)??
哨兵持續檢查主節點和從節點的健康狀態(是否在線、響應延遲等)。
??2. 自動故障轉移(Automatic Failover)??
主節點故障時,自動選舉新主節點,并更新從節點和客戶端的配置。 - ??配置中心(Configuration Provider)??
客戶端通過哨兵獲取當前主節點地址,無需硬編碼。 - ??通知(Notification)??
支持通過 API 或腳本通知管理員集群狀態變化。
哨兵模式架構?
- 哨兵節點(Sentinel)??:
獨立運行的 Redis 進程,??不存儲業務數據??,僅負責監控和決策。 - ??主從節點(Master/Slave)??:
與普通主從模式相同,哨兵依賴主從結構實現數據冗余。
哨兵的工作流程?
- 監控階段??
a ??周期性檢查主節點狀態??:
每個哨兵每隔 sentinel down-after-milliseconds (默認30秒)向主節點發送 PING 命令。
b ??主觀下線(SDOWN)??:
若主節點在指定時間內未響應,哨兵將其標記為 ??主觀下線??。
??c 客觀下線(ODOWN)??:
當超過半數哨兵(由 quorum 參數控制)確認主節點主觀下線,主節點被標記為 ??客觀下線??,觸發故障轉移。 - 故障轉移(Failover)??
a ??選舉領導者哨兵??:
所有哨兵通過 ??Raft算法?? 選舉一個領導者哨兵(Leader Sentinel)來執行故障轉移。
Raft:
發現master下線的哨兵節點(我們稱他為A)向每個哨兵發送命令,要求對方選自己為領頭哨兵
如果目標哨兵節點沒有選過其他人,則會同意選舉A為領頭哨兵
如果有超過一半的哨兵同意選舉A為領頭,則A當選
如果有多個哨兵節點同時參選領頭,此時有可能存在一輪投票無競選者勝出,此時每個參選的節點等待一個隨機時間后再次發起參選請求,進行下一輪投票競選,直至選舉出領頭哨兵
??b 選擇新主節點??:
領導者哨兵根據規則從從節點中選舉新主節點,優先級規則:
從節點與主節點斷開時間(slave_repl_offset 差異小者優先)。
數據最新的從節點(復制偏移量最大者優先)。
若配置了 slave-priority,優先級高者優先。
??c 切換主從角色??:
新主節點執行 REPLICAOF NO ONE 脫離從屬角色。
其他從節點通過 REPLICAOF 命令指向新主節點。
??d 客戶端更新??:
哨兵通知客戶端(通過發布/訂閱機制)新主節點地址。 - 舊主節點恢復??
舊主節點恢復后,哨兵將其降級為從節點,并指向新主節點。
哨兵模式的優缺點?
- 優點??:
??高可用性??:自動故障轉移,減少人工干預。
??透明切換??:客戶端自動感知主節點變化。
??擴展性??:支持多哨兵節點,防止哨兵單點故障。 - ??缺點??:
??配置復雜??:需部署多個哨兵節點并維護配置一致性。
??寫擴展不足??:寫操作仍由單一主節點處理,無法水平擴展。
??網絡分區風險??:極端情況下可能出現腦裂(需合理配置 quorum 和 majority)。
集群模式(Cluster)?
Redis 集群(Cluster)是 Redis 官方提供的分布式解決方案,旨在解決??大規模數據存儲??、??高并發訪問??和??高可用性??需求。
Redis 集群的核心特性?
- 數據分片(Sharding)??
數據被自動分片到多個節點,突破單機內存限制。 - ??高可用性??
每個分片(主節點)至少有一個從節點,支持故障自動切換。 - 橫向擴展??
可動態添加節點,提升集群容量和性能。 - 去中心化架構??
節點間通過 ??Gossip 協議?? 直接通信,無需依賴外部協調服務(如 ZooKeeper)。
集群架構核心概念?
- 數據分片機制?
哈希槽(Hash Slot)??
Redis 將數據劃分為 ??16384 個槽??,每個鍵通過 ??CRC16 算法?? 計算哈希值后,映射到其中一個槽:
slot = CRC16(key) % 16384
??槽分配??
每個主節點負責一部分槽。例如,3主節點集群可能的槽分配:
節點A:0-5460
節點B:5461-10922
節點C:10923-16383 - 節點角色?
主節點(Master)??
負責處理讀寫請求,并管理分配的槽。
??從節點(Slave)??
復制主節點數據,主節點故障時通過選舉成為新主節點。
最小集群要求
至少3個主節點??:確保故障轉移時能達成多數派共識。
??每個主節點至少1個從節點??:保證高可用性,推薦3主3從架構。
數據分片算法
哈希求余
設有N個分?,使?[0,N-1]這樣序號進?編號.
針對某個給定的key,先計算hash值,再把得到的結果%N,得到的結果即為分?編號.
優點: 簡單?效,數據分配均勻.
缺點: ?旦需要進?擴容,N改變了,原有的映射規則被破壞,就需要讓節點之間的數據相互傳輸,重新排列,以滿?新的映射規則.此時需要搬運的數據量是?較多的,開銷較?.
N為3的時候,[100,120]這21個hash值的分布(此處假定計算出的hash值是?個簡單的整數,?便 ?眼觀察)
當引??個新的分?,N從3=>4時,?量的key都需要重新映射.(某個key%3和%4的結果不?樣, 就映射到不同機器上了).
?致性哈希算法
為了降低上述的搬運開銷,能夠更?效擴容,業界提出了"?致性哈希算法".
key 映射到分?序號的過程不再是簡單求余了,?是改成以下過程:
第?步:把0->2^32-1這個數據空間,映射到?個圓環上.數據按照順時針?向增?.
第?步:假設當前存在三個分?,就把分?放到圓環的某個位置上.
第三步:假定有?個key,計算得到hash值H,那么這個key映射到哪個分?呢?規則很簡單,就是從H 所在位置,順時針往下找,找到的第?個分?,即為該key所從屬的分?.
這就相當于,N個分?的位置,把整個圓環分成了N個管轄區間.Key的hash值落在某個區間內,就歸對應區間管理.
在這個情況下,如果擴容?個分?,如何處理呢? 原有分?在環上的位置不動,只要在環上新安排?個分?位置即可.
此時,只需要把0號分?上的部分數據,搬運給3號分?即可.1號分?和2號分?管理的區間都是不變的.
優點: ??降低了擴容時數據搬運的規模,提?了擴容操作的效率.
缺點: 數據分配不均勻(有的多有的少,數據傾斜).
哈希槽分區算法(Redis使?)
為了解決上述問題(搬運成本?和數據分配不均勻),Rediscluster引?了哈希槽(hashslots)算法
hash_slot = crc16(key) % 16384
其中crc16也是?種hash算法.
16384 其實是16*1024,也就是2^14.
相當于是把整個哈希值,映射到16384個槽位上,也就是[0,16383]。
然后再把這些槽位?較均勻的分配給每個分?.每個分?的節點都需要記錄??持有哪些分?.
假設當前有三個分?,?種可能的分配?式:
- 0號分?:[0,5461],共5462個槽位
- 1號分?:[5462,10923],共5462個槽位
- 2號分?:[10924,16383],共5460個槽位
這?的分?規則是很靈活的.每個分?持有的槽位也不?定連續. 每個分?的節點使?位圖來表???持有哪些槽位.對于16384個槽位來說,需要2048個字 節(2KB)??的內存空間表?.
如果需要進?擴容,?如新增?個3號分?,就可以針對原有的槽位進?重新分配.
?如可以把之前每個分?持有的槽位,各拿出?點,分給新分?.
?種可能的分配?式:
- 0號分?:[0,4095],共4096個槽位
- 1號分?:[5462,9557],共4096個槽位
- 2號分?:[10924,15019],共4096個槽位
- 3號分?:[4096,5461]+[9558,10923]+[15019,16383],共4096個槽位
集群的高可用機制
在 Redis 集群模式中,當主節點宕機時,??從節點(Replica)會自動觸發故障轉移(Failover)流程??,選舉出新的主節點。
故障轉移觸發條件?
- 主觀下線(PFAIL)??
集群中其他節點通過心跳檢測(Gossip協議)發現主節點無響應,會將其標記為 ??PFAIL??(Possible Failure)。 - ??客觀下線(FAIL)??
當超過半數的主節點(至少 N/2+1,N為集群主節點總數)確認該主節點不可達,則標記為 ??FAIL??,觸發故障轉移。
從節點選舉流程?
- 資格檢查??
從節點必須滿足以下條件才能參與選舉:
數據同步正常:slave_repl_offset 與舊主節點的 master_repl_offset 差距在合理范圍內(由 cluster-replica-validity-factor 控制,默認10秒)。
舊主節點已被標記為 ??FAIL?? 狀態。 - 發起選舉??
從節點向集群中所有主節點發送 ??FAILOVER_AUTH_REQUEST?? 請求投票。 - 投票機制??
每個主節點只能投一票,且需滿足以下條件:
主節點當前未參與其他故障轉移。
主節點確認舊主節點確實處于 ??FAIL?? 狀態。
從節點獲得 ??超過半數主節點(N/2+1)的投票?? 后,成為新主節點。 - 切換主節點角色??
當選的從節點執行 REPLICAOF NO ONE,脫離從屬角色,接管舊主節點的哈希槽(Hash Slots)。
其他從節點重新指向新主節點,開始同步數據。
對比總結?
Redis持久化
Redis 提供兩種核心持久化機制:??RDB(Redis Database)?? 和 ??AOF(Append Only File)??,用于將內存數據持久化到磁盤,確保數據在服務重啟或故障后不丟失。
RDB(快照持久化)?
工作原理??
??定時快照??:根據配置規則,將內存數據生成二進制壓縮的 ??RDB文件??(默認 dump.rdb)。
??全量備份??:每次持久化保存整個數據庫狀態。
觸發條件??
??自動觸發??:
配置 save 規則(默認:save 900 1、save 300 10、save 60 10000)。
執行 SHUTDOWN 或 FLUSHALL 命令時自動生成 RDB。
手動觸發??:
# 同步生成RDB(阻塞主線程,生產環境慎用)
SAVE
# 異步生成RDB(后臺執行)
BGSAVE
優點?
??高性能??:二進制壓縮文件體積小,恢復速度快。
??容災友好??:適合備份全量數據到遠程存儲(如 AWS S3)。
??資源消耗低??:異步 BGSAVE 通過 fork 子進程處理,主進程無阻塞。
缺點?
??數據丟失風險??:兩次快照之間的數據可能丟失(依賴配置頻率)。
??大內存場景 fork 延遲??:BGSAVE 在數據量較大時,fork 子進程可能引發短暫阻塞。
配置示例?(Redis默認開啟RDB)
# redis.conf
save 900 1 # 900秒內至少1次修改觸發保存
save 300 10 # 300秒內至少10次修改觸發保存
save 60 10000 # 60秒內至少10000次修改觸發保存
rdbcompression yes # 啟用壓縮
dbfilename dump.rdb # RDB文件名
dir ./ # 保存路徑
RDB對過期key的處理
過期key對RDB沒有任何影響,
- 從內存數據庫持久化數據到RDB文件,持久化key之前,會檢查是否過期,過期的key不進入RDB文件。
- 從RDB文件恢復數據到內存數據庫,數據載入數據庫之前,會對key先進行過期檢查,如果過期,不導入數據庫(主庫情況)。
AOF(日志追加持久化)?
工作原理??
??日志記錄??:將每個??寫操作??以協議文本格式追加到 appendonly.aof 文件。
??重寫機制(Rewrite)??:定期壓縮AOF文件,消除冗余命令(如多次SET同一鍵)。
同步策略??
??appendfsync 配置??:
always:每次寫操作都同步到磁盤(最安全,性能最低)。
everysec(默認):每秒同步一次(平衡安全與性能)。
no:由操作系統決定同步時機(最快,數據丟失風險最高)。
優點??
??高數據安全??:appendfsync always 可確保零數據丟失。
??可讀性強??:AOF文件為文本格式,便于人工審計或修復。
缺點??
??文件體積大??:未壓縮的日志文件可能遠大于RDB。
??恢復速度慢??:重放AOF日志比加載RDB慢。
??長期寫入壓力??:頻繁同步可能影響性能(尤其是 always 模式)。
配置示例?
# redis.conf
appendonly yes # 啟用AOF
appendfilename "appendonly.aof" # AOF文件名
appendfsync everysec # 同步策略
auto-aof-rewrite-percentage 100 # 文件體積比上次重寫后增長100%時觸發重寫
auto-aof-rewrite-min-size 64mb # 文件體積至少64MB才觸發重寫
aof-load-truncated yes # 加載截斷的AOF文件(避免啟動失敗)
AOF對過期key的處理
過期key對AOF沒有任何影響
從內存數據庫持久化數據到AOF文件: 當key過期后,還沒有被刪除,此時進行執行持久化操作(該key是不會進入aof文件的,因為沒有發生修改命令) 當key過期后,在發生刪除操作時,程序會向aof文件追加一條del命令(在將來的以aof文件恢復數據的時候該過期的鍵就會被刪掉) AOF重寫 重寫時,會先判斷key是否過期,已過期的key不會重寫到aof文件
混合持久化(Redis 4.0+)?
工作原理??
??RDB + AOF??:在AOF重寫時,將當前數據狀態以RDB格式寫入AOF文件頭部,后續增量操作仍以AOF格式追加。
??文件格式??:.aof 文件前半部分為RDB二進制數據,后半部分為AOF日志。
優點??
??快速恢復??:利用RDB快速加載全量數據,再重放增量AOF日志。
??數據安全??:保留AOF的細粒度操作記錄。
配置啟用?
# redis.conf
aof-use-rdb-preamble yes # 啟用混合持久化(需同時開啟AOF)
持久化策略對比?
Redis主從數據不一致的解決方案
- 強制全量同步(Full Resync)??
通過重啟從節點或手動觸發全量復制,使從節點重新同步主節點數據:
# 在從節點執行
redis-cli -h <slave-ip> -p <slave-port> REPLICAOF NO ONE # 解除從屬關系
redis-cli -h <slave-ip> -p <slave-port> FLUSHALL # 清空數據(可選)
redis-cli -h <slave-ip> -p <slave-port> REPLICAOF <master-ip> <master-port> # 重新同步
- ??使用 WAIT 命令(折中一致性)?
?原理??:主節點寫入后阻塞客戶端,直到數據同步到指定數量的從節點。
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;@Service
public class RedisWaitService {private final RedisTemplate<String, String> redisTemplate;public RedisWaitService(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 寫入數據并等待同步到指定數量的從節點* @param key 鍵* @param value 值* @param numReplicas 需要等待的從節點數* @param timeoutMillis 超時時間(毫秒)* @return 實際同步成功的從節點數*/public Long writeAndWait(String key, String value, int numReplicas, long timeoutMillis) {// 執行寫操作redisTemplate.opsForValue().set(key, value);// 執行 WAIT 命令return redisTemplate.execute((RedisCallback<Long>) connection -> {Object result = connection.execute("WAIT".getBytes(),String.valueOf(numReplicas).getBytes(),String.valueOf(timeoutMillis).getBytes());return (result != null) ? Long.parseLong(result.toString()) : 0L;});}
}
??注意??:WAIT 會降低寫入性能,需權衡一致性與吞吐量。
- 強制讀主節點(強一致性)?
適用場景??:對數據一致性要求極高的關鍵操作(如支付成功頁)。
??實現方式??:
??業務邏輯區分??:對需要強一致性的讀請求,直接訪問主節點。
# 偽代碼示例:寫操作后,后續讀操作強制走主節點
def set_key(key, value):master_conn.set(key, value)# 標記后續讀操作需走主節點cache.set(f"force_master:{user_id}", True, timeout=2) # 設置短期標記def get_key(key, user_id):if cache.get(f"force_master:{user_id}"):return master_conn.get(key)else:return slave_conn.get(key)
優點??:數據強一致。
??缺點??:主節點壓力增大,失去讀寫分離優勢。
- 客戶端降級重試(最終一致性)?
適用場景??:可容忍短暫不一致的非關鍵業務(如商品詳情頁)。
??實現方式??:
def get_key_with_retry(key, retries=3):for _ in range(retries):value = slave_conn.get(key)if value is not None:return valuetime.sleep(0.1) # 等待短暫延遲后重試# 降級策略:最終從主節點讀取return master_conn.get(key)
?優點??:減少主節點壓力,保持讀寫分離。
??缺點??:增加業務邏輯復雜度,可能需多次重試。
Redis過期策略
Redis的過期鍵刪除策略結合了??惰性刪除??和??定期刪除??兩種機制,以平衡內存利用和性能消耗。
- 惰性刪除(Lazy Expiration)?
原理??:當客戶端訪問某個鍵時,Redis會先檢查該鍵是否過期。如果過期,則立即刪除并返回空值;否則正常返回數據。
??優點??:僅在訪問時觸發,節省CPU資源。
??缺點??:若大量鍵長期未被訪問,會導致內存浪費。 - 定期刪除(Active Expiration)?
??原理??:Redis周期性隨機抽取部分鍵檢查過期狀態,刪除已過期的鍵。
??實現細節??:
??頻率控制??:由配置參數hz(默認10)決定,即每秒運行10次,每100ms執行一次。
??掃描過程??:
每次從每個數據庫中??隨機選取一定數量的鍵??(默認20個,由ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP定義)。
刪除其中已過期的鍵。
若過期的鍵比例??超過25%??,則重復該過程,直到比例低于25%或超時。
??時間限制??:每次定期刪除的總時間不超過25ms(避免阻塞主線程)。
對于定期刪除,在程序中有一個全局變量current_db來記錄下一個將要遍歷的庫,假設有16個庫,我們這一次定期刪除遍歷了10個,那此時的current_db就是11,下一次定期刪除就從第11個庫開始遍歷,假設current_db等于15了,那么之后遍歷就再從0號庫開始(此時current_db==0)
Redis緩存淘汰策略
Redis的緩存淘汰策略(Eviction Policy)用于在內存達到上限(maxmemory)時決定刪除哪些鍵以釋放空間。
策略名稱 | 行為描述 | 適用場景 |
---|---|---|
noeviction | 拒絕所有寫入操作(默認策略) | 數據不可丟失,且內存不足時需人工干預的場景 |
??volatile-lru | 從??設置了過期時間??的鍵中,淘汰??最近最少使用??(LRU)的鍵 | 區分緩存和數據,僅淘汰緩存 |
allkeys-lru | 從??所有鍵??中淘汰??最近最少使用??(LRU)的鍵 | 全部數據為緩存,需全局優化 |
??volatile-lfu | 從??設置了過期時間??的鍵中,淘汰??最不經常使用??(LFU)的鍵(Redis 4.0+) | 高頻訪問緩存場景,優先保留常用數據 |
allkeys-lfu | 從??所有鍵??中淘汰??最不經常使用??(LFU)的鍵(Redis 4.0+) | 全局高頻訪問場景 |
??volatile-random | 從??設置了過期時間??的鍵中隨機淘汰 | 緩存淘汰無需考慮訪問模式 |
allkeys-random | 從??所有鍵??中隨機淘汰 | 數據重要性相同,淘汰隨機鍵 |
volatile-ttl | 從??設置了過期時間??的鍵中,淘汰??剩余存活時間最短??的鍵(TTL) | 優先清理即將過期的緩存 |
LRU與LFU的區別?
Redis的LRU機制
傳統 LRU??:
維護一個鏈表,每次訪問鍵時將其移動到鏈表頭部,淘汰時直接刪除鏈表尾部(需要精確維護順序,內存和 CPU 開銷大)。
??Redis 的 LRU??:
為了節省內存和性能,Redis 采用??近似 LRU??:
- 每個鍵會記錄一個 lru 字段(24 bits),存儲最后一次訪問的時間戳(精度為秒級,但實際是邏輯時鐘計數)。
- 當需要淘汰鍵時,Redis ??隨機抽取 N 個鍵??(默認 N=5,由 maxmemory-samples 配置),比較它們的 lru 字段值,淘汰其中??最久未被訪問的鍵??。
- 若淘汰后內存仍不足,重復此過程。
Redis的LFU機制
LFU(Least Frequently Used),表示最近最少使用,它和key的使用次數有關,其思想是:根據key最近被訪問的頻率進行淘汰,比較少訪問的key優先淘汰,反之則保留。
LRU的原理是使用計數器來對key進行排序,每次key被訪問時,計數器會增大,當計數器越大,意味著當前key的訪問越頻繁,也就是意味著它是熱點數據。 它很好的解決了LRU算法的缺陷:一個很久沒有被訪問的key,偶爾被訪問一次,導致被誤認為是熱點數據的問題。
LFU維護了兩個鏈表,橫向組成的鏈表用來存儲訪問頻率,每個訪問頻率的節點下存儲另外一個具有相同訪問頻率的緩存數據。具體的工作原理是:
- 當添加元素時,找到相同訪問頻次的節點,然后添加到該節點的數據鏈表的頭部。如果該數據鏈表滿了,則移除鏈表尾部的節點。當獲取元素或者修改元素時,都會增加對應key的訪問頻次,并把當前節點移動到下一個頻次節點。
- 添加元素時,訪問頻率默認為1,隨著訪問次數的增加,頻率不斷遞增。而當前被訪問的元素也會隨著頻率增加進行移動。
頻率計數與衰減?
傳統 LFU 需要精確記錄每個鍵的訪問次數,但會帶來兩個問題:
??內存開銷大??:每個鍵需存儲一個較大的計數器(如 64 位整數),對海量鍵的場景不友好。
??對突發訪問敏感??:短期大量訪問的鍵可能迅速成為“高頻”鍵,擠占長期穩定訪問的鍵。
Redis 的 LFU 采用 ??8 位(0~255)對數計數器?? + ??概率遞增?? 機制,既能壓縮存儲空間,又能抑制短期突發訪問對頻率的影響。
- 計數器遞增規則?
每次訪問鍵時,計數器的遞增??不是固定加 1??,而是??按概率決定是否增加??。
概率公式為:
fu_log_factor?? 是配置參數(默認 10),控制計數器的增長速度。
當前計數器值?? 是鍵的當前頻率估算值(0~255)。
例如當前值=1,lfu_log_factor=10,??訪問 11 次??,大約有 1 次(1/11≈9.09%)會觸發計數器值+1。 - ??頻率衰減
為避免舊數據長期占用內存,Redis會定期減少計數器的值:
每經過 lfu_decay_time 分鐘(默認1),計數器值減半(若值大于0)。
LFU關鍵配置參數?:
參數 | 默認值 | 說明 |
---|---|---|
lfu-log-factor | 10 | 控制計數器增長速率。值越大,低頻訪問的計數器增長越慢,區分度越高。 |
lfu-decay-time | 1 | 計數器衰減時間(單位:分鐘)。值越大,頻率衰減越慢。 |
maxmemory-samples | 5 | 每次淘汰時隨機采樣的鍵數量。值越大,淘汰精度越高,但CPU消耗增加。 |
淘汰策略配置方式?
# 設置最大內存(例如1GB)
maxmemory 1gb
# 選擇淘汰策略(例如allkeys-lru)
maxmemory-policy allkeys-lru
主從節點的淘汰行為?
- 主節點??:主動執行淘汰策略并同步DEL命令到從節點。
- 從節點??:默認不執行淘汰策略,依賴主節點同步刪除指令(可通過replica-lazy-eviction no強制從節點自行淘汰)。
Redis中的緩存穿透、雪崩、擊穿的原因以及解決方案
三者出現的根本原因:Redis命中率下降,請求直接打在DB上,導致DB的壓力瞬間變大而卡死或者宕機。
緩存穿透
緩存穿透產生的原因:請求根本不存在的資源(DB本身就不存在,Redis更是不存在)
解決方式:
- 對空值進行緩存
- 實時監控:
對redis進行實時監控,當發現redis中的命中率下降的時候進行原因的排查,配合運維人員對訪問對象和訪問數據進行分析查詢,從而進行黑名單的設置限制服務(拒絕黑客攻擊) - 使用布隆過濾器
使用BitMap作為布隆過濾器,將目前所有可以訪問到的資源通過簡單的映射關系放入到布隆過濾器中(哈希計算),當一個請求來臨的時候先進行布隆過濾器的判斷,如果有那么才進行放行,否則就直接攔截 - 接口校驗
類似于用戶權限的攔截,對于id=-3872這些無效訪問就直接攔截,不允許這些請求到達Redis、DB上。
緩存雪崩
緩存雪崩產生的原因:redis中大量的key集體過期
解決方式:
- 將失效時間分散開
通過使用自動生成隨機數使得key的過期時間是隨機的,防止集體過期 - 使用多級架構
使用nginx緩存+redis緩存+其他緩存,不同層使用不同的緩存,可靠性更強 - 設置緩存標記
記錄緩存數據是否過期,如果過期會觸發通知另外的線程在后臺去跟新實際的key - 使用鎖或者隊列的方式
如果查不到就加上排它鎖,其他請求只能進行等待
緩存擊穿
產生緩存雪崩的原因:redis中的某個熱點key過期,但是此時有大量的用戶訪問該過期key
解決方案:
- 提前對熱點數據進行設置
類似于新聞、某博等軟件都需要對熱點數據進行預先設置在redis中 - 監控數據,適時調整
監控哪些數據是熱門數據,實時的調整key的過期時長 - 使用鎖機制
只有一個請求可以獲取到互斥鎖,然后到DB中將數據查詢并返回到Redis,之后所有請求就可以從Redis中得到響應