
一、數據庫與緩存一致性
使用緩存,可以降低耗時,提供系統吞吐性能。但是,使用緩存,會存在數據一致性的問題。
1、幾種緩存使用模式
- Cache-Aside Pattern,旁路緩存模式
- Read-Through/Write-Through(讀寫穿透)
- Write- behind (異步緩存寫入)
一般我們使用緩存,都是旁路緩存模式,讀請求流程如下:

- 讀的時候,先讀緩存,緩存命中的話,直接返回數據;
- 緩存沒有命中的話,就去讀數據庫,從數據庫取出數據,放入緩存后,同時返回響應。
旁路緩存模式的寫流程:

2、刪除緩存呢,還是更新緩存?
我們在操作緩存的時候,到底應該刪除緩存還是更新緩存呢?我們先來看個例子:

- 線程A先發起一個寫操作,第一步先更新數據庫;
- 線程B再發起一個寫操作,第二步更新了數據庫;
- 由於網絡等原因,線程B先更新了緩存;
- 線程A更新緩存。
這時候,緩存保存的是A的數據(老數據),數據庫保存的是B的數據(新數據),數據不一致了,臟數據出現啦。如果是刪除緩存取代更新緩存則不會出現這個臟數據問題。
3、先操作數據庫還是先操作緩存
雙寫的情況下,先操作數據庫還是先操作緩存?我們再來看一個例子:假設有A、B兩個請求,請求A做更新操作,請求B做查詢讀取操作。

- 線程A發起一個寫操作,第一步del cache;
- 此時線程B發起一個讀操作,cache miss;
- 線程B繼續讀DB,讀出來一個老數據;
- 然后線程B把老數據設置入cache;
- 線程A寫入DB最新的數據;
醬紫就有問題啦,緩存和數據庫的數據不一致了。緩存保存的是老數據,數據庫保存的是新數據。因此,Cache-Aside緩存模式,選擇了先操作數據庫而不是先操作緩存。
4、如何保證最終一致性
- 緩存延時雙刪
- 刪除緩存重試機制
- 讀取biglog異步刪除緩存
二、緩存穿透
1、原理
緩存穿透`:指查詢一個一定不存在的數據,由於緩存不命中時,需要從數據庫查詢,查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到數據庫去查詢,進而給數據庫帶來壓力。”
緩存穿透一般都是這幾種情況產生的:業務不合理的設計、業務/運維/開發失誤的操作、黑客非法請求攻擊。如何避免緩存穿透呢?
2、解決辦法
一般有三種方法。
-
如果是非法請求,我們在API入口,對參數進行校驗,過濾非法值。
-
如果查詢數據庫為空,我們可以給緩存設置個空值,或者默認值。但是如有有寫請求進來的話,需要更新緩存哈,以保證緩存一致性,同時,最后給緩存設置適當的過期時間。(業務上比較常用,簡單有效)
-
使用布隆過濾器快速判斷數據是否存在。即一個查詢請求過來時,先通過布隆過濾器判斷值是否存在,存在才繼續往下查。
三、緩存雪崩
1、原理
緩存雪崩
:指緩存中數據大批量到過期時間,而查詢數據量巨大,引起數據庫壓力過大甚至down機。”
2、解決辦法
緩存雪奔一般是由於大量數據同時過期造成的,對於這個原因,可通過均勻設置過期時間解決,即讓過期時間相對離散一點。如采用一個較大固定值+一個較小的隨機值,5小時+0到1800秒醬紫。
Redis 故障宕機也可能引起緩存雪奔。這就需要構造Redis高可用集群啦。
四、緩存機擊穿
1、原理
緩存擊穿
:指熱點key在某個時間點過期的時候,而恰好在這個時間點對這個Key有大量的並發請求過來,從而大量的請求打到db。”
緩存擊穿看着有點像緩存雪崩,其實它兩區別是,緩存雪奔是指數據庫壓力過大甚至down機,緩存擊穿只是大量並發請求到了DB數據庫層面。可以認為擊穿是緩存雪奔的一個子集吧。有些文章認為它倆區別,是在於擊穿針對某一熱點key緩存,雪奔則是很多key。
2、解決方法
解決方案就有兩種:
- 使用互斥鎖方案。緩存失效時,不是立即去加載db數據,而是先使用某些帶成功返回的原子操作命令,如(Redis的setnx)去操作,成功的時候,再去加載db數據庫數據和設置緩存。否則就去重試獲取緩存。
- “永不過期”。是指沒有設置過期時間,但是熱點數據快要過期時,異步線程去更新和設置過期時間。
五、緩存熱Key
1、原理
在Redis中,我們把訪問頻率高的key,稱為熱點key。如果某一熱點key的請求到服務器主機時,由於請求量特別大,可能會導致主機資源不足,甚至宕機,從而影響正常的服務。
2、解決方法
如何解決熱key問題?
- Redis集群擴容:增加分片副本,均衡讀流量;
- 對熱key進行hash散列,比如將一個key備份為key1,key2……keyN,同樣的數據N個備份,N個備份分布到不同分片,訪問時可隨機訪問N個備份中的一個,進一步分擔讀流量;
- 使用二級緩存,即JVM本地緩存,減少Redis的讀請求。
六、緩存容量內存考慮
1、評估容量,合理利用
如果我們使用的是Redis,而Redis的內存是比較昂貴的,我們不要什么數據都往Redis里面塞,一般Redis只緩存查詢比較頻繁的數據。同時,我們要合理評估Redis的容量,也避免頻繁set覆蓋,導致設置了過期時間的key失效。
如果我們使用的是本地緩存,如guava的本地緩存,也要評估下容量。避免容量不夠。
2、Redis的八種內存淘汰機制
為了避免Redis內存不夠用,Redis用8種內存淘汰策略保護自己~
- volatile-lru:當內存不足以容納新寫入數據時,從設置了過期時間的key中使用LRU(最近最少使用)算法進行淘汰;
- allkeys-lru:當內存不足以容納新寫入數據時,從所有key中使用LRU(最近最少使用)算法進行淘汰。
- volatile-lfu:4.0版本新增,當內存不足以容納新寫入數據時,在過期的key中,使用LFU算法進行刪除key。
- allkeys-lfu:4.0版本新增,當內存不足以容納新寫入數據時,從所有key中使用LFU算法進行淘汰;
- volatile-random:當內存不足以容納新寫入數據時,從設置了過期時間的key中,隨機淘汰數據。
- allkeys-random:當內存不足以容納新寫入數據時,從所有key中隨機淘汰數據。
- volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的key中,根據過期時間進行淘汰,越早過期的優先被淘汰;
- noeviction:默認策略,當內存不足以容納新寫入數據時,新寫入操作會報錯。
3、不同的業務場景,Redis選擇適合的數據結構**
- 排行榜適合用zset
- 緩存用戶信息一般用hash
- 消息隊列,文章列表適用用list
- 用戶標簽、社交需求一般用set
- 計數器、分布式鎖等一般用String類型