- 為什么要使用 redis 做緩存?
- 封底估算
- 為什么是單行數據的QPS,而不是總的?
- 什么時候使用DB,Redis,本地緩存
- 數據的分類
- 一致性的方案
- 1. 先清除Redis,再更新 DB
- 2. 先更新DB,再清除 Redis
- 使用場景:
- 3. 延遲刪除與延遲雙刪
- 使用場景
- 4. 監聽 binlog 清除
- 5. 雙寫
- 使用場景:
- 6. 監聽binlog更新
- 7.定時刷新
- 使用場景
- 選擇
- 其他問題
- 緩存穿透
為什么要使用 redis 做緩存?
高性能
- redis壓力測試: 20w QPS(瓶頸是網絡 io)
- MySQL: 2w QPS(瓶頸是磁盤io)
封底估算
是否使用緩存是有單行數據的 QPS 決定的
為什么是單行數據的QPS,而不是總的?
- 使用(Redis)緩存主要是利用 緩存( Redis)高性能的特性減少DB的重復查詢
- 就是數據需要多次查詢,并且每次查詢的結果是相同的,這時候加到緩存中,可以攔截大量的請求在緩存層,減少db的請求壓力
舉個例子 - 一些 IM 系統的總的 QPS 有幾十萬(qq,wx 這些)
- 但是每個人接受的消息是不同的,并且消息是一次性消費的,所以針對單行消息而言,QPS 是小于 1 的并且是一次性消費,不涉及多次重復查詢,即使總的 QPS 非常高,也不會使用緩存(IM 系統會使用Redis,但是不是做緩存使用的)
什么時候使用DB,Redis,本地緩存
使用 DB
- 當數據是部分人可見的(比如后臺管理/GM 的需求):單行數據的QPS<1(是一個常數)
使用Redis 緩存
- 數據所有用戶可見,單行數據的QPS>1 ,就需要考慮 Redis 緩存了
本地緩存
- 原因 Redis 有 20 萬 QPS,但是單 key 的 QPS 只有幾千;
- 而本地內存的讀寫性能有千萬(map 讀寫)
- 所以當單行數據的 QPS>5000(參考阿里云的熱 key 二級緩存標準 QPS) 就可以考慮使用本地緩存了
數據的分類
- 熱數據: 考慮極端熱數據的情景(1w 的 QPS)
- 冷數據: 0 QPS(沒人訪問)
- 由冷數據突然變成熱數據: QPS 0 突變-> QPS 2000
我們的核心要求是:
- 熱數據不能失效,因為熱數據失效容易造成緩存擊穿的問題
- 冷數據不能長時間儲存在緩存(redis)中: 控制成本
- 冷數據(沒有緩存)突變到熱數據(大量請求): 不能全部請求都回源(查詢 DB),否則會造成緩存擊穿問題
一致性的方案
1. 先清除Redis,再更新 DB
- 存在的問題:
- 間隙查詢的不一致問題
- 有緩存擊穿的風險
舉個例子:
- 不一致
- 再清除 Redis 后,更新 DB 前有一個查詢請求
- 因為 DB 沒有更新,查詢請求查詢到舊數據,然后將舊數據更新到 Redis
- 這時 Redis 中的就是一條臟數據,造成數據不一致的問題
- 如果清除到一條有 1萬 QPS 的 key 很容易發生擊穿的問題
2. 先更新DB,再清除 Redis
- 存在的問題:
- 緩存擊穿的問題
還是清除到 1 萬 QPS 的 key 很容易發生擊穿
使用場景:
- QPS不高(單行數據QPS<20,不存在熱key 的場景)的非核心業務:比如用戶的主頁
3. 延遲刪除與延遲雙刪
工作流程: 先更新 DB,再等一段時間清除 Redis / 先清除 Redis,再更新 DB,再等一段時間清除 Redis
核心解決的問題: 查詢從庫導致的數據不一致的問題
舉個例子:
- 更新 DB 是更新的主庫,從庫需要一段時間進行同步
- 但是 DB更新完后,從庫沒有同步好,這時有一個查詢,查詢到有個未同步的從庫(舊數據)
- 然后將數據更新到 Redis
所以延遲的作用就是等待從庫的數據同步好,保證數據一直
延遲多久?
- 具體要根據項目中的同步的耗時與單行數據的 qps來確定是否需要延遲刪除/延遲雙刪
使用場景
- QPS不高(單行數據QPS<20,不存在熱key 的場景)的非核心業務:比如用戶的主頁 (本質與方案 2 是相同的,不能有熱 key; 只是增加了延遲解決從庫查詢的不一致問題)
4. 監聽 binlog 清除
解決的問題:
- 將自當成 DB 的一個從節點,監聽主節點的 binlog,并刪除Redis
- 服務解耦和: 與之前不同的是這種方案是低耦合實現
問題:
- 同樣只適用于 QPS<20(沒有熱 key 問題的需求)
- 實現復雜: 監聽 binlog 的邏輯是比較復雜的(比如 binlog 的獲取,解析,消費等)
5. 雙寫
工作流程: 先更新 DB,再更新 Redis
- 存在的問題與解決方案
- 并發寫的不一致問題
- 舉個例子: 有兩個寫請求(請求 1,將數據改為 A; 請求 2 將數據改為 B)
- 這時請求 1 先執行,將DB 改為 A,然后請求 1 阻塞
- 請求 2 后執行,先將 DB 改成 B , 再將 Redis 改成 B
- 這時請求 1 阻塞恢復了,將 Redis 改成 A
- 這時 DB 是 B,Redis(緩存)是 A,造成了一個數據不一致的問題
- 解決方案: 加分布式鎖,不允許同時更改,只能一個執行完成才能執行下一個
- 并發寫的不一致問題
使用場景:
-
完善一下
- 熱數據的過期失效問題(自動續期)
- 冷數據突變為熱數據(只放一個請求回源更新,其他請求返回空)
-
完善后可以適用于高讀,高寫,高一致性的場景: 比如搶紅包業務
-
搶紅包業務分析:
- 紅包發出會有大量的搶紅包的操作,就涉及到大量的寫(并發寫)
- 紅包發出有大量的人查詢搶紅包的記錄: 大量讀
- 搶完紅包后需要所有人看到最新的記錄: 高的一致性
6. 監聽binlog更新
核心解決的問題
- binlog 是有序的,如果能順序消費 binlog 就可以解決雙寫中并發寫的問題
如何訂閱 binlog?
- 使用Canal組件(原理就是偽裝成從節點向主節點發送 dump)
缺點:
- 復雜,binlog 的獲取,解析,消費整套是比較復雜的邏輯(比鎖實現起來要更復雜),并且還要考慮過期失效的問題與冷數據突變為熱數據的問題
- 一致性沒有雙寫好(雙寫使用了分布式鎖,基本就是一個強一致的方案)
7.定時刷新
實現方式:
-
儲存(Redis)緩存的時候儲存一個數據的更新標記(val 是一個空值),并且 更新標記的過期時間<數據的過期時間
- 比如:更新標記 10s 過期; 數據 10 分鐘過期
-
查詢的時候先使用 set nx px 命令檢查更新標記是否存在,
- 如果存在(寫失敗): 就使用 Redis 的數據
- 如果不存在(寫成功): 就回源(查詢 DB)更新 Redis(更新數據與過期時間)
如此就可以實現數據的定時刷新(每個更新標記的存活時間刷新一次),這樣可以解決一致性的問題與熱數據過期失效的問題,一舉兩得
解決完熱數據的問題還剩冷數據與冷數據突變為熱數據
- 冷數據: 不使用會自動過期,釋放空間
- 冷數據突變為熱數據: 第一個會回源(set nx 成功),其他請求暫時返回空,知道回源完成
為什么是返回空而不是回源或者阻塞?
- 回源的問題: 擊穿
- 阻塞的問題: 阻塞大量的請求(協程)(假設有 1萬 QPS,回源需要 10s,就會阻塞 10 萬個請求)
優點:
- 實現簡單: 定時刷新同時解決了一致性問題與熱數據的失效問題
- 好拓展: 不僅適用于 Redis 與 DB 的一致性,還適用于內存與 Redis 的一致性
使用場景
- 一致性要求不高(不敏感): 比如要求是更新后 10 分鐘內可以看到數據最新的狀態
- 讀高: 有大量讀并存在熱點問題
比如: 短視頻平臺的視頻數據
選擇
- 封頂估算: 對單行數據進行封頂估算決定是否使用緩存,使用什么緩存
- 任務分解: 將實際需求中可能遇到的情況羅列出來(比如:熱數據,冷數據,冷數據突變為熱數據)
- 分析每一種情況,并提供對應的解決方案.
參考: https://blog.csdn.net/weixin_42338901/article/details/129231758
其他問題
上面主要解決的是擊穿問題,Redis 緩存的常見的三大問題還有兩個
- 緩存穿透: 使用不存在的 key 進行訪問
- 緩存雪崩: 緩存大面積失效
緩存穿透
常見的解決方案:
- 布隆過濾器
實現: 多次哈希,然后標記對應的 bit 位(變為 1)
判斷是否出現過: 多次哈希,所有 bit 位都為 1 才算
問題:
- 不是 100% 準確的,存在誤判的可能
- 只能記錄是否出現過(適合做加法,很難做減法)
舉個例子: 某個 key 開始是不存在的,加到布隆過濾器中防止穿透,后面存在了;這時候別人訪問這個 key 就會攔截掉.
- 記錄空值
- 相同的不存在的 key 重復訪問會攔截到 Redis 層
缺點:
比布隆過濾器消耗空間更大