1 緩存基礎(chǔ)
1 緩存的讀寫(xiě)模式
1.1 Cache Aside(旁路緩存)
- 適合場(chǎng)景
- 數(shù)據(jù)一致性要求高,緩存數(shù)據(jù)更新比較復(fù)雜的業(yè)務(wù)。
- 缺點(diǎn)
- 需要同時(shí)維護(hù) 緩存 和 DB 兩個(gè)數(shù)據(jù)存儲(chǔ)方,過(guò)于繁瑣
- 寫(xiě)操作
- 先更新數(shù)據(jù)庫(kù),直接將key從緩存中刪除,然后由數(shù)據(jù)庫(kù)驅(qū)動(dòng)緩存數(shù)據(jù)的更新。
- 數(shù)據(jù)庫(kù)驅(qū)動(dòng)緩存的更新如
- 使用一個(gè) Trigger 組件,讀取并解析mysql的binlog,然后進(jìn)行一些業(yè)務(wù)邏輯處理,更新緩存數(shù)據(jù)
- 可以先刪除緩存,再更新數(shù)據(jù),再隊(duì)列方式公共更新緩存,去除重復(fù)。
- 數(shù)據(jù)庫(kù)驅(qū)動(dòng)緩存的更新如
- 先更新數(shù)據(jù)庫(kù),直接將key從緩存中刪除,然后由數(shù)據(jù)庫(kù)驅(qū)動(dòng)緩存數(shù)據(jù)的更新。
- 讀操作
- 先讀緩存,如果緩存沒(méi)有,則讀數(shù)據(jù)庫(kù),同時(shí)將 數(shù)據(jù)庫(kù)中讀取的數(shù)據(jù)回寫(xiě)到緩存。
1.2 Read/Write Through(讀寫(xiě)穿透)
- 適合場(chǎng)景
- 數(shù)據(jù)有冷熱區(qū)分
- 描述
- 業(yè)務(wù)應(yīng)用只關(guān)注一個(gè)存儲(chǔ)服務(wù)即可,業(yè)務(wù)方的讀寫(xiě) cache 和 DB操作。 的操作,都由存儲(chǔ)服務(wù)代理
- 存儲(chǔ)服務(wù) 寫(xiě)操作
- 先查 緩存,
- 緩存存在 先更新 緩存,再更新db
- 緩存不存在 只更新db
- 先查 緩存,
- 存儲(chǔ)服務(wù)讀
- 先讀緩存,緩存沒(méi)有,讀db,同時(shí)將db中數(shù)據(jù)回寫(xiě)到數(shù)據(jù)庫(kù)。
1.3 Write Behind Caching(異步緩存寫(xiě)入)
- 適合場(chǎng)景
- 寫(xiě)頻率超高,需要合并寫(xiě)請(qǐng)求的業(yè)務(wù),一致性要求不高。
- 缺點(diǎn)
- 即數(shù)據(jù)的一致性變差,甚至在一些極端場(chǎng)景下可能會(huì)丟失數(shù)據(jù),
- 描述
- 由數(shù)據(jù)存儲(chǔ)服務(wù)來(lái)管理 cache 和 DB 的讀寫(xiě),數(shù)據(jù)更新 只更新緩存,不直接更新db,改為異步批量的方式更新db。
- 存儲(chǔ)服務(wù) 寫(xiě)操作
- 先查 緩存,
- 緩存存在 先更新緩存,異步批量更新db。
- 緩存不存在 只更新db
- 先查 緩存,
- 存儲(chǔ)服務(wù)讀
- 先讀緩存,緩存沒(méi)有,讀db,同時(shí)將db中數(shù)據(jù)回寫(xiě)到數(shù)據(jù)庫(kù)。
1.4 好的db緩存方案
-
實(shí)時(shí)一致性方案
- 采用“先寫(xiě) MySQL,再刪除 Redis”的策略,這種情況雖然也會(huì)存在兩者不一致,但是需要滿足的條件有點(diǎn)苛刻,所以是滿足實(shí)時(shí)性條件下,能盡量滿足一致性的最優(yōu)解。
- image.png
-
最終一致性方案
- 采用“先寫(xiě) MySQL,通過(guò) Binlog,異步更新 Redis”,可以通過(guò) Binlog,結(jié)合消息隊(duì)列異步更新 Redis,是最終一致性的最優(yōu)解。
- 這種方案有個(gè)前提,查詢的請(qǐng)求,不會(huì)回寫(xiě) Redis。
- image.png
-
先刪除 Redis,再寫(xiě) MySQL,再刪除 Redis(負(fù)責(zé)方案不推薦) 緩存雙刪
- image.png
- “刪除緩存 10”必須在“回寫(xiě)緩存10”后面,那如何才能保證一定是在后面呢?網(wǎng)上給出的第一個(gè)方案是,讓請(qǐng)求 A 的最后一次刪除,等待 500ms。完全不行。
- image.png
3 緩存穿透,緩存擊穿,緩存雪崩解決方案分析
1 緩存穿透
- 含義: 查詢一個(gè)一定不存在的數(shù)據(jù),然后導(dǎo)致查數(shù)據(jù)庫(kù),導(dǎo)致DB掛掉,有人利用不存在的key頻繁攻擊我們的應(yīng)用,這就是漏洞
- 原因
- 系統(tǒng)設(shè)計(jì),更多考慮的是正常路徑,對(duì)特殊訪問(wèn)路徑考慮欠缺。
- 解決方案:
- 布隆過(guò)濾器,所有可能存在的key hash到一個(gè)足夠大的bitmap中。不存在的key,被bitmap攔截掉。
- 問(wèn)題 如果數(shù)據(jù)量特別大,不合適
- 解決
- 10億以內(nèi)最佳(1.2GB),可以使用布隆過(guò)濾器
- 10億 用10倍大小的位圖存儲(chǔ), 也就是 100 億的二進(jìn)制
- 數(shù)據(jù)量過(guò)大,用布隆過(guò)濾器,緩存非法key
- 會(huì)導(dǎo)致key持續(xù)高速增長(zhǎng)
- 要定期清零處理
- 10億以內(nèi)最佳(1.2GB),可以使用布隆過(guò)濾器
- 如果數(shù)據(jù)庫(kù)查詢?yōu)榭眨ú还軘?shù)據(jù)是不存在,還是系統(tǒng)故障),緩存都對(duì)空結(jié)果key緩存,過(guò)期時(shí)間會(huì)很短,最長(zhǎng)不超過(guò)5分鐘。
- 問(wèn)題 如果訪問(wèn)大量不存在key,也會(huì)占用大量存儲(chǔ)空間
- 解決
- 設(shè)計(jì)的時(shí)間短一些,讓它盡快過(guò)期
- 設(shè)計(jì)一個(gè)獨(dú)立的公共緩存非法key
- 查詢先查 正常緩存組件,如果沒(méi)有再查非法key的緩存。
- DB 查出來(lái)為空,就記錄 非法key緩存
- 布隆過(guò)濾器,所有可能存在的key hash到一個(gè)足夠大的bitmap中。不存在的key,被bitmap攔截掉。
2 緩存雪崩
問(wèn)題描述 部分緩存節(jié)點(diǎn)不可用,導(dǎo)致整個(gè)緩存體系甚至服務(wù)系統(tǒng)不可用的情況。
-
解決辦法
- 對(duì)業(yè)務(wù)db的訪問(wèn)增加讀寫(xiě)開(kāi)關(guān)
- 當(dāng)db 請(qǐng)求變慢,阻塞,慢請(qǐng)求超過(guò)閾值時(shí),就關(guān)閉讀開(kāi)關(guān)。
- 部分或所有讀db的請(qǐng)求進(jìn)行 failfast 立即返回,待db恢復(fù)后再打開(kāi)讀開(kāi)關(guān)。
- 對(duì)緩存增加多個(gè)副本
- 緩存異常,或請(qǐng)求miss后,再讀取其他緩存副本。
- 多個(gè)副本 盡量部署在不同 機(jī)架,保證可用。
- 緩存異常,或請(qǐng)求miss后,再讀取其他緩存副本。
- 對(duì)緩存體系進(jìn)行實(shí)時(shí)監(jiān)控
- 當(dāng)請(qǐng)求訪問(wèn)的慢速比超過(guò)閥值時(shí),及時(shí)報(bào)警,通過(guò)機(jī)器替換、服務(wù)替換進(jìn)行及時(shí)恢復(fù);也可以通過(guò)各種自動(dòng)故障轉(zhuǎn)移策略,自動(dòng)關(guān)閉異常接口、停止邊緣服務(wù)、停止部分非核心功能措施,確保在極端場(chǎng)景下,核心功能的正常運(yùn)行。
- 對(duì)業(yè)務(wù)db的訪問(wèn)增加讀寫(xiě)開(kāi)關(guān)
-
實(shí)際方案
- redis 高可用,主從+哨兵,redis cluster,避免全盤崩潰。
- 本地 ehcache 緩存 + hystrix 限流&降級(jí),避免 MySQL 被打死。
- redis 持久化,一旦重啟,自動(dòng)從磁盤上加載數(shù)據(jù),快速恢復(fù)緩存數(shù)據(jù)。
含義: 某一時(shí)刻緩存全部失效,可能是 設(shè)置了相同的過(guò)期時(shí)間,可能redis掛掉
-
解決方案:
- 大多數(shù) 加鎖,或者隊(duì)列方式
- 簡(jiǎn)單方案 在原有失效時(shí)間基礎(chǔ)上增加一個(gè)隨機(jī)值 比如1-5分鐘隨機(jī)。
- 做好集群
3 緩存擊穿
- 含義 某一個(gè)key,在某個(gè)時(shí)間點(diǎn)被超高并發(fā)訪問(wèn),恰好這個(gè)時(shí)間點(diǎn)過(guò)期了。 與雪崩區(qū)別這里 雪崩是很多key。
- 解決方案:
- 使用互斥鎖 redis分布式鎖
- 設(shè)置永不過(guò)期
- 如 我解析規(guī)則 一直不過(guò)期,如果修改了, 手動(dòng)清除緩存。
- 通過(guò)后臺(tái)定時(shí)刷新,根據(jù)緩存失效時(shí)間節(jié)點(diǎn)去批量刷新緩存數(shù)據(jù)
- 這個(gè)適合 Key 失效時(shí)間相對(duì)固定的場(chǎng)景。
- hystrix 做資源隔離
4 如何保證緩存與數(shù)據(jù)庫(kù)雙寫(xiě)的一致性
- 讀
- 先讀緩存
- 緩存沒(méi)有 讀數(shù)據(jù)庫(kù),然后取出數(shù)據(jù)放入緩存,同時(shí)響應(yīng)
- 先讀緩存
- 更新
- 先更新數(shù)據(jù)庫(kù),然后刪除緩存
- 為什么是刪除緩存,而不是更新緩存
- 在復(fù)雜點(diǎn)的緩存場(chǎng)景,緩存不單單是數(shù)據(jù)庫(kù)中直接取出來(lái)的值
- 問(wèn)題:
- 先更新數(shù)據(jù)庫(kù),刪除緩存失敗
- 導(dǎo)致 數(shù)據(jù)庫(kù)是新數(shù)據(jù),緩存時(shí)舊的數(shù)據(jù)
- 解決思路:
- 先刪除緩存,再更新數(shù)據(jù)庫(kù)
- 如果數(shù)據(jù)庫(kù)修改失敗,緩存時(shí)空的,數(shù)據(jù)庫(kù)是舊的。
- 先刪除緩存,再更新數(shù)據(jù)庫(kù)
- 先更新數(shù)據(jù)庫(kù),刪除緩存失敗
- 為什么是刪除緩存,而不是更新緩存
- 最終 先刪除緩存,再更新數(shù)據(jù)庫(kù)
- 問(wèn)題:
- 大并發(fā),還沒(méi)修改,但是緩存時(shí)空的,查到緩存時(shí)空的。
- 解決思路
- 還是要串行,考慮如何串行
- 隊(duì)列
- 更新緩存 不管是讀,還是寫(xiě), 生成唯一標(biāo)識(shí),放入java隊(duì)列中。
- 隊(duì)列還可以做過(guò)濾,相同的更新緩存沒(méi)有意義。
- 如 寫(xiě)到緩存后,后面讀 可以直接從緩存中拿。
- 隊(duì)列還可以做過(guò)濾,相同的更新緩存沒(méi)有意義。
- 更新緩存 不管是讀,還是寫(xiě), 生成唯一標(biāo)識(shí),放入java隊(duì)列中。
- 鎖實(shí)現(xiàn)呢?
- 隊(duì)列
- 還是要串行,考慮如何串行
- 問(wèn)題:
- 先更新數(shù)據(jù)庫(kù),然后刪除緩存
5 緩存失效
- 因?yàn)楹芏鄈ey 設(shè)置了相同過(guò)期時(shí)間,導(dǎo)致一起失效
- 解決
- 過(guò)期時(shí)間=baes時(shí)間+隨機(jī)時(shí)間
6 如何設(shè)計(jì)一個(gè)動(dòng)態(tài)緩存熱點(diǎn)數(shù)據(jù)的策略
- 由于數(shù)據(jù)存儲(chǔ)受限,系統(tǒng)并不是將所有數(shù)據(jù)都需要存放到緩存中的,而只是將其中一部分熱點(diǎn)數(shù)據(jù)緩存起來(lái)
- 策略思路
- 判斷數(shù)據(jù)最新訪問(wèn)時(shí)間來(lái)做排名,并過(guò)濾掉不常訪問(wèn)的數(shù)據(jù),只留下經(jīng)常訪問(wèn)的數(shù)據(jù)
- 如商品數(shù)據(jù)
- 先通過(guò)緩存系統(tǒng)做一個(gè)排序隊(duì)列(如存放1000個(gè)商品),系統(tǒng)根據(jù)商品訪問(wèn)時(shí)間 正序排序
- 同時(shí)緩存系統(tǒng)定期過(guò)濾掉最后 200 個(gè)商品, 再?gòu)臄?shù)據(jù)中 隨機(jī)讀取200 個(gè)商品,加入隊(duì)列。
- 這樣 請(qǐng)求每次到達(dá)的時(shí)候,先從隊(duì)列獲取商品ID,如果命中,再根據(jù)ID 從另一個(gè)緩存結(jié)構(gòu) 讀取商品信息讀取實(shí)際的商品信息,并返回。
- 如商品數(shù)據(jù)
- 判斷數(shù)據(jù)最新訪問(wèn)時(shí)間來(lái)做排名,并過(guò)濾掉不常訪問(wèn)的數(shù)據(jù),只留下經(jīng)常訪問(wèn)的數(shù)據(jù)
7 熱key 問(wèn)題
-
問(wèn)題
- 熱key,大流量,直接打到一個(gè)緩存機(jī)器,這個(gè)緩存機(jī)器很容易到達(dá) CPU,網(wǎng)卡,帶寬極限,從而導(dǎo)致緩存訪問(wèn)變慢。
-
解決
- 找到熱key
- 提前評(píng)估可能出現(xiàn)的熱key
- 突發(fā)的可以用 spark,flink 進(jìn)行事實(shí)分析
- 之前發(fā)生的,可以通過(guò)hadoop 離線計(jì)算。 找到最近歷史數(shù)據(jù)中的熱 key
- 將熱 key 分散,
- 如 hotkey 被分散成 hotkey#1, hotkey#2, ... hotkeyn。 n 就是key分散的多個(gè)緩存節(jié)點(diǎn)。
- 客戶端訪問(wèn) 隨機(jī) hotkey 1-n 的后綴。
- 也可以
- key 名字不變,對(duì)緩存提前進(jìn)行多副本,多級(jí)結(jié)合的緩存架構(gòu)
- 對(duì)緩存監(jiān)控,快速擴(kuò)容來(lái)減少熱key的沖擊
- 業(yè)務(wù)端將熱key記錄在本地緩存。
- 找到熱key
8 大key 問(wèn)題
- 方案
- 設(shè)計(jì)緩存閾值,當(dāng)value 超過(guò)閾值,進(jìn)行壓縮
- 大value 對(duì)于結(jié)構(gòu)元素很多 如set, 可以進(jìn)行序列化構(gòu)建,通過(guò)restore 一次性寫(xiě)入。
- 將大value拆分,,盡量減少大 key 的存在
- 由于一些大key 一旦穿透到DB,加載耗時(shí),可以設(shè)置過(guò)期時(shí)間長(zhǎng)一些。
9 數(shù)據(jù)不一致
-
問(wèn)題
- 如 更新 DB 后,寫(xiě)緩存失敗,導(dǎo)致緩存的是老數(shù)據(jù)
- 采用 rehash 自動(dòng)漂移策略,多個(gè)副本數(shù)據(jù)不一致
-
解決
- 緩存更新失敗以后,進(jìn)行重試
- 如果重試失敗 ,緩存服務(wù)出了問(wèn)題, 寫(xiě)入MQ服務(wù), 緩存服務(wù)恢復(fù),從MQ 刪除。
- 緩存設(shè)置較短的時(shí)間,讓緩存及時(shí)過(guò)期。
- 不采用rehash 飄逸策略,而采用緩存分層策略。
- 緩存更新失敗以后,進(jìn)行重試
10 懶加載的緩存過(guò)期方案,性能毛刺 23講搞定后臺(tái)架構(gòu)實(shí)戰(zhàn)
- 懶加載的緩過(guò)過(guò)期方案
- Redis 作為主存儲(chǔ),MySQL 作為兜底來(lái)構(gòu)建
- 當(dāng)讀服務(wù)接受請(qǐng)求時(shí),會(huì)先去緩存中查詢數(shù)據(jù),如果沒(méi)有查詢到數(shù)據(jù),就會(huì)降級(jí)到數(shù)據(jù)庫(kù)中查詢,并將查詢結(jié)果保存在 Redis 中,以供下一次請(qǐng)求進(jìn)行查詢。保存在 Redis 中的數(shù)據(jù)會(huì)設(shè)置一個(gè)過(guò)期時(shí)間,防止數(shù)據(jù)庫(kù)的數(shù)據(jù)變更了,請(qǐng)求還一直讀取緩存中的臟數(shù)據(jù)
- 性能毛刺
- 當(dāng)緩存過(guò)期時(shí),讀服務(wù)的請(qǐng)求都會(huì)穿透到數(shù)據(jù)庫(kù)中,對(duì)于穿透請(qǐng)求的性能和使用緩存的性能差距非常大,時(shí)常是毫秒和秒級(jí)別的差異。
- 解決方案
- 全量緩存(適合讀類型的業(yè)務(wù))
- 將數(shù)據(jù)庫(kù)種的所有數(shù)據(jù)都存儲(chǔ)在緩存種,同時(shí)在緩存種不設(shè)置過(guò)期時(shí)間的一種實(shí)現(xiàn)方式。
-
image.png
- 沒(méi)有解決分布式事務(wù)問(wèn)題,反而把問(wèn)題放大了。
- 基于 Binlog 的全量緩存的基本架構(gòu)
- binlog 的開(kāi)源工具
- Canal
- mysql_Streamer
- Maxwell
- Databus
- image.png
- image.png
-
image.png
- 優(yōu)化
- image.png
- 優(yōu)化
- 缺點(diǎn)
- 提升了系統(tǒng)的整體復(fù)雜度
- 整個(gè)資源同步 的流程變長(zhǎng),且關(guān)注點(diǎn)合出錯(cuò)點(diǎn)由一個(gè)中間件變成了兩個(gè)
- 緩存的容量會(huì)成倍上升,響應(yīng)的資源成本也大幅上升
- 在一些對(duì)性能要求極致且是實(shí)時(shí)性高的場(chǎng)景下,只能進(jìn)行取舍。
- 優(yōu)化
- 緩存數(shù)據(jù)進(jìn)行篩選,有業(yè)務(wù)含義且被查詢
- 存儲(chǔ)在緩存種的數(shù)據(jù)可以進(jìn)行壓縮。
-
數(shù)據(jù)json 序列號(hào)時(shí)候,字段上添加替代標(biāo)識(shí)。 - image.png
- 如果使用的redis hash 結(jié)構(gòu)。hash結(jié)構(gòu) field 字段 也可以用 json 標(biāo)識(shí)一樣的模式
-
數(shù)據(jù)json 序列號(hào)時(shí)候,字段上添加替代標(biāo)識(shí)。 -
- 使用全量緩存 承接所有請(qǐng)求時(shí)候,會(huì)出現(xiàn)無(wú)法感知緩存丟失問(wèn)題。
- image.png
- image.png
- image.png
- 提升了系統(tǒng)的整體復(fù)雜度
- 技巧
- redis 還是可能丟失數(shù)據(jù),使用 異步校準(zhǔn)加報(bào)警及自動(dòng)化補(bǔ)齊的方式來(lái)應(yīng)對(duì)
- 從緩存獲取數(shù)據(jù)
- 通過(guò)mq通知對(duì)比程序
- 對(duì)比程序根據(jù)條件 查詢數(shù)據(jù)庫(kù)
- 進(jìn)行對(duì)比,不一致進(jìn)行告警,并自動(dòng)把數(shù)據(jù)刷新至緩存。
- redis 還是可能丟失數(shù)據(jù),使用 異步校準(zhǔn)加報(bào)警及自動(dòng)化補(bǔ)齊的方式來(lái)應(yīng)對(duì)
- binlog 的開(kāi)源工具
- 全量緩存(適合讀類型的業(yè)務(wù))
11 數(shù)據(jù)并發(fā)競(jìng)爭(zhēng)
- 描述
- 在高并發(fā)訪問(wèn)場(chǎng)景,一旦緩存訪問(wèn)沒(méi)有找到數(shù)據(jù),大量請(qǐng)求就會(huì)并發(fā)查詢 DB,導(dǎo)致 DB 壓力大增的現(xiàn)象
- 主要是同一個(gè)key
- 解決
- 1 使用全局鎖
- 2 對(duì)緩存數(shù)據(jù)保持多個(gè)備份,即便其中一個(gè)備份中的數(shù)據(jù)過(guò)期或被剔除了,還可以訪問(wèn)其他備份,從而減少數(shù)據(jù)并發(fā)競(jìng)爭(zhēng)的情況
4 常見(jiàn)系統(tǒng)設(shè)計(jì)
1 秒殺系統(tǒng)緩存解析
- 設(shè)計(jì)原則
- 盡力將請(qǐng)求攔截在系統(tǒng)上游,減輕后端壓力。
- 充分利用緩存,提高系統(tǒng)性能和可用性。
- 架構(gòu)設(shè)計(jì)
前端靜態(tài)資源 cdn 前置
前端請(qǐng)求限制
負(fù)載均衡分發(fā)請(qǐng)求
-
web 服務(wù)預(yù)先處理
- 權(quán)限檢測(cè)
- 服務(wù)前置檢查
-
業(yè)務(wù)請(qǐng)求處理
- 所有處理交給緩存
- 后續(xù)事務(wù)操作 通過(guò)MQ 緩存,降低 db壓力。
2 海量計(jì)數(shù)緩存解析
- chang












