簡略總結#
- 認識 Redis
- 記憶體資料庫
- 常用於快取、消息隊列、分佈式鎖
- 原子性操作
- Redis 值的資料結構(五種常見)
- String 字串:快取物件、常規計數、分佈式鎖、共享 session 資訊等
- List 列表(鏈表):消息隊列等
- Hash 哈希(無序散列表):快取物件、購物車等
- Set 集合(字串的無序集合):聚合計算(並集、交集、差集)場景,比如點讚、共同關注、抽獎活動等
- Zset 有序集合:排序場景,比如排行榜、電話和姓名排序等
- …(還有 4 種,先不寫了)
- Redis 執行緒模型
- 單執行緒是指:
- [接收客戶端請求> 解析 > 資料讀寫 > 發送資料給客戶端] 由主執行緒完成。
- redis 程式並不是單執行緒
- 6.0 前後
- 前 - 單執行緒:可維護高,簡單,無鎖問題
- 後 - 多執行緒:採用多個 I/O 執行緒處理網路請求(因為網路 I/O 性能瓶頸)
- 總結:執行命令還是單執行緒,引入多執行緒為了提供網路 I/O 處理性能
- 單執行緒是指:
- Redis 持久化
- AOF 日誌:執行命令後,將命令寫入 AOF 檔案,重啟時加載該檔案
- 策略:
- Always:執行完命令後同步寫入
- Everysec:先將命令寫入核心緩衝區,然後每秒將緩衝區的內容寫入磁碟
- NO:由作業系統控制何時將緩衝區的內容寫進磁碟
- 過大後:觸發 AOF 重寫機制
- 缺點:檔案過大,載入時間長
- 策略:
- RDB 快照:將某一時刻的記憶體資料(頻率可控),以二進位的方式寫入磁碟
- 方式:
- save 命令:通過主執行緒生成,會阻塞執行緒
- bgsave 命令:通過創建子進程生成,不會阻塞執行緒
- 缺點:資料丟失風險高
- 方式:
- 混合:4.0 新增,解決 AOF 恢復慢,RDB 資料丟失風險高,集合兩者優點
- AOF 日誌:執行命令後,將命令寫入 AOF 檔案,重啟時加載該檔案
- Redis 集群
- 方式:
- 主從複製:主同步到從(首次全量,後續增量),主寫從讀
- 哨兵模式:在主從複製的基礎上,增加哨兵監控主、從、哨兵的健康狀態,自動故障轉移
- 切片集群:將資料分佈到不同的伺服器,降低單點依賴,提高讀寫性能
- 集群腦裂:網路分區(不同網路下)導致雙主
- 解決:
- min-slaves-to-write x,限制至少 x 個從連接到主,低於時主禁寫
- min-slaves-max-lag x,限制主從資料同步的延遲小於 x 秒,超過時主禁寫
- 解決:
- 方式:
- Redis 過期刪除與記憶體淘汰
- 過期刪除:對比過期字典,未過期返回資料,已過期刪除
- 刪除策略:
- 惰性刪除:訪問 key 時檢測是否過期,是則刪除 key 返回 null
- 定期刪除:定時隨機取一定數量的 key 檢查,刪除其中過期的
- 流程:抽取 key → 檢查是否過期,並刪除 → 過期數量超過 25% 時,重複之前步驟 → 小於 25% ,等待下一輪檢查
- 持久化時過期鍵處理:
- AOF:
- 對過期鍵添加刪除指令到 AOF 檔案
- 載入時過期鍵不會寫入 AOF
- RDB:
- 生成 RDB 時忽略過期鍵
- 載入時主過期不載入,從全部載入
- AOF:
- 主從過期鍵處理:從依賴主(不掃描過期鍵),主檢查刪除過期鍵後同步到從
- 記憶體滿載:達到閾值觸發記憶體淘汰機制,配置 maxmemory
- 記憶體淘汰機制策略:共八種,歸類為 [不進行淘汰] 和 [進行淘汰] 兩類
- 不進行淘汰:超過閾值,直接返回錯誤
- 進行淘汰:分為 [在設定了過期時間的資料中] 和 [所有資料] 內進行淘汰兩類,最好的是設定了過期時間且使用率最低的鍵
- Redis 快取設計
- 雪崩:為了資料一致性,對快取的資料設定過期時間,過期後從資料庫請求並重新更新快取,當大量鍵過期,請求直接到 DB 導致宕機。或 Redis 宕機。
- 解決:
- 過期:
- 打散過期時間
- 互斥鎖:同一時間僅一個業務執行緒更新快取
- 雙 key(主設過期,備永久)
- 宕機:
- 服務熔斷
- 請求限流
- 高可用
- 過期:
- 解決:
- 擊穿:熱點資料過期,請求直接到 DB(雪崩的子集情況)導致宕機
- 解決:
- 互斥鎖:同一時間僅一個業務執行緒更新快取
- 不設定過期時間
- 解決:
- 穿透:資料不在快取中,也不在 DB 中,一般是業務誤操作和惡意攻擊兩種
- 解決:
- 限制非法請求
- 快取預設值
- 使用布隆過濾器判斷資料是否存在,避免查 DB
- 解決:
- 動態快取熱點資料:通過資料最新訪問時間排序,留下 top 資料
- 常見快取更新策略:
- Cache Aside 旁路快取(實際開發時使用,最常用,適合讀多寫少):應用程式直接與 [資料庫、快取] 互動,負責維護快取
- 寫策略:先更新 DB,再刪除 Cache(不能改變順序,否則導致資料不一致,刪除是懶加載思想)
- 讀策略:
- 有快取,直接返回資料
- 沒快取,讀 DB,寫入 Cache,返回資料
- Read/Write Through 讀穿 / 寫穿
- Write Back 寫回:有資料丟失風險
- Cache Aside 旁路快取(實際開發時使用,最常用,適合讀多寫少):應用程式直接與 [資料庫、快取] 互動,負責維護快取
- 保證 Cache 和 DB 資料一致性:更新資料庫 + 更新快取(使用分佈式鎖,加上較短的過期時間)
- 雪崩:為了資料一致性,對快取的資料設定過期時間,過期後從資料庫請求並重新更新快取,當大量鍵過期,請求直接到 DB 導致宕機。或 Redis 宕機。
- Redis 實戰
- 延遲執行隊列:使用 ZSet(有序集合),其中 Score 屬性存儲延遲執行的時間,再利用 zrangebysocre 查詢並對比時間找到待處理的
- Redis 大 key:指 key 的 value 很大
- 條件:
- String 類型的值大於 10 KB
- Hash、List、Set、ZSet 類型的元素超過 5k
- 影響:
- 客戶端超時阻塞:redis 單執行緒執行命令,操作大 key 耗時,阻塞執行緒
- 引發網路堵塞:資料 * 訪問量 = 產生大流量
- 阻塞工作執行緒:del 大 key,阻塞工作執行緒
- 記憶體分佈不均:集群模型在 slot 分片均勻時,出現資料和查詢傾斜情況,部分有大 key 的節點佔用記憶體多,QPS 較大
- 排查:
redis-cli --bigkeys
- 注意:
- 在從節點執行,因為會阻塞執行緒
- 無從時在低峰時間執行,可使用
-i
控制掃描間隔,降低性能影響
- 注意:
SCAN
:先用 SCAN 掃描,再用 TYPE 獲取 key 的類型- String 類型:使用 STRLEN 獲取長度
- 集合類型:
- 平均大小 * 數量
MEMORY USAGE
查詢鍵佔用空間(Redis 4.0+)
- https://github.com/sripathikrishnan/redis-rdb-tools:解析 RDB 檔案
- 刪除:直接刪除會釋放大量記憶體,同時會造成空閒記憶體塊鏈表操作時間增加,從而 Redis 主執行緒阻塞,致使各種請求超時,最終 Redis 連接耗盡,產生各種異常
- 方法:
- 分批次
- 大 Hash:使用
hscan
每次獲取 100 個字段,再用hdel
one by one 刪除 - 大 Set:使用
sscan
每次掃描 100 個元素,再用srem
one by one 刪除 - 大 ZSet:使用
zremrangebyrank
每次刪除 top 100 個元素
- 大 Hash:使用
- 非同步(Redis 4.0+):用
unlink
代替del
- 方式:
- 主動調用
unlink
命令 - 配置參數,滿足條件時非同步刪除
- 主動調用
- 方式:
- 分批次
- 方法:
- 條件:
- Redis 管道(Pipeline):是客戶端而非伺服器提供的一種批處理技術,一次處理多個 Redis 命令,解決多個命令執行時的網路等待
- Redis 事務不支持回滾:不支持事務運行時錯誤的事務回滾,只有
DISCARD
放棄事務執行- 不支持原因:
- 作者認為生產環境很少出錯,沒必要開發
- 事務回滾較複雜與 Redis 簡單高效設計不符
- 不支持原因:
- 分佈式鎖:在分佈式環境下並發控制的一種機制,用於控制資源在同一時刻只能被一個應用使用
-
原理:
SET
命令的NX
參數可以實現 [key 不存在才插入]- key 不存在:顯示插入成功,表示加鎖成功
- key 存在:顯示插入失敗,表示加鎖失敗
-
加鎖條件:
- 加鎖是對鎖變數進行 [讀取、檢查和設定] 三個操作,需要原子操完成,所以使用
SET
+NX
- 鎖變數需要設定過期時間,使用
EX/PX
選項,單位為毫秒 - 鎖變數的值使用客戶端的唯一標識區分來源
# 滿足三條件的命令: SET lock_key unique_value NX PX 10000 - lock_key: key 值 - unique_value: 客戶端唯一標識 - NX: lock_key 不存在時才進行設定 - PX 10000: 設定 lock_key 過期時間為 10 秒
- 加鎖是對鎖變數進行 [讀取、檢查和設定] 三個操作,需要原子操完成,所以使用
-
解鎖:有兩個操作,需要 Lua 腳本保證原子性,即先比較值是否相等,再刪除鍵
// 釋放鎖時,先比較 unique_value 是否相等,避免鎖的誤釋放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
-
優點:
- 性能高
- 實現方便
- 避免單點故障(Redis 集群部署)
-
缺點:
- 超時時間不好設定:過長影響性能,過短失去鎖保護
- Redis 主從複製模式中資料是非同步複製的,可能導致分佈式鎖的不可靠性。
-
集群下的可靠性:官方設計了一個分佈式鎖演算法 Redlock(紅鎖)。基於多個 Redis 節點的分佈式鎖,官方推薦至少 5 個孤立的主節點
- 原理:讓客戶端和多個獨立的 Redis 節點依次請求申請加鎖,和半數以上的節點加鎖成功,就認為客戶端加鎖成功
- 過程(加鎖成功 = 超半數成功獲取鎖 & 總耗時 < 鎖有效時間):
- 客戶端獲取當前時間(t1)
- 客戶端按順序依次向 N 個節點執行加鎖
- 客戶端從超過半數(大於等於 N/2+1)的節點上成功獲取到了鎖,就再次獲取當前時間(t2),然後計算耗時(t2-t1),t2-t1 < 鎖的過期時間,認為加鎖成功
-