Redis 在為程序帶來性能提高的同時,也會有缺點,這些都是我們需要考慮的,比如: Redis與持久化數據庫的數據一致性問題,緩存雪崩問題,緩存穿透問題,緩存擊穿問題等,本文就上述幾個問題,簡單的闡述一下解決思路。
緩存穿透
如果我們的查詢數據路線是: App -> Redis -> MySQL(或其它RDBS),正常情況下,我們查詢數據都會在Redis命中,Redis未命中的情況下才去查詢MySQL,然后將查詢到的數據寫入到Redis中,這樣下次就直接可以在Redis層命中。
那么如果查詢一條MySQL中壓根兒根本就不存在的數據,也就是緩存和數據庫都查詢不到這條數據,但是請求每次都會打到數據庫上面去。這種查詢不存在數據的現(xiàn)象我們稱為緩存穿透。
解決方案(緩存穿透)
- 將null值存入redis,設置過期時間
- BloomFilter
第一種方案比較簡單易懂,查詢不到數據,將Null值存到Redis中,并設置上過期時間,這樣下次請求再過來,可以直接返回Null,不會再落到MySQL。
但是,針對于一些惡意攻擊,攻擊帶過來的大量key 是不存在的,那么我們采用第一種方案就會緩存大量不存在key的數據。此時我們采用第一種方案就不合適了,我們完全可以先對使用第二種方案進行過濾掉這些key。關于 BloomFilter , 下文列出簡單的描述,我們可以在應用層加一層 BloomFilter 來解決這個問題。
Bloomfilter: 布隆過濾器, 它是由一個很長的二進制向量和一系列隨機映射函數組成,布隆過濾器可以用于檢索一個元素是否在一個集合中。它的優(yōu)點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率。即Bloom Filter報告某一元素存在于某集合中,但是實際上該元素并不在集合中。但是如果某個元素確實沒有在該集合中,那么Bloom Filter 是不會報告該元素存在于集合中的,所以不會漏報。
針對key非常多、請求重復率比較低的數據,我們就沒有必要進行緩存,使用第二種方案直接過濾掉。而對于空數據的key有限的,重復率比較高的,我們則可以采用第一種方式進行緩存。
緩存擊穿
在高并發(fā)的系統(tǒng)中,大量的請求同時查詢一個 key 時,此時這個key正好失效了,就會導致大量的請求都打到數據庫上面去。這種現(xiàn)象我們稱為緩存擊穿。
解決方案(緩存擊穿)
- 熱點Key不設置過期時間
- 使用 互斥鎖,在第一個查詢數據的請求上使用一個互斥鎖來鎖住它。后續(xù)線程阻塞,等第一個線程查詢到數據并做了緩存,鎖釋放后后續(xù)線程,發(fā)現(xiàn)已經有緩存了,就直接走緩存。
第一種方案使用具有局限性,需要結合業(yè)務考慮是否可以不設置過期時間。
第二種方案,由于使用了互斥鎖,所以性能肯定會差很多。
緩存雪崩
緩存雪崩的情況是說,當某一時刻發(fā)生大規(guī)模的緩存失效的情況 或者 緩存服務宕機了,會有大量的請求進來直接打到DB上面。稱為 緩存雪崩。
解決方案(緩存雪崩)
- 保證Redis服務的高可用
- 應用種使用本地緩存,以及限流降級等措施
- 解決熱點數據集中失效,為每個key設置不同的過期時間
Redis 的集群是必須要做的,保證Redis服務的高可用,在應用種也要處理Redis宕機的情況,比如做出限流或者降級的措施,采用本地緩存等,讓查詢請求盡可能少的落到數據庫上。Redis的集群方案怎么來做,可以參考我之前的文章。
針對一些熱點數據集中失效的問題,可以采用設置不同的過期時間的方案,在一個基礎時間上左右浮動,例如: 30min + 3s,30min -5s .
Redis與持久層數據一致性的問題
App -> Redis -> MySQL ,App查詢請求先請求Redis,如果查詢不到再請求MySQL,查詢到數據后將數據寫入MySQL。
在這樣的一條查詢鏈路種,由于數據的寫入可能會造成Redis與MySQL數據的不一致。
比如: 數據寫入時,先寫入Redis再寫入MySQL,可能在寫入MySQL時出現(xiàn)異常,導致數據不一致,反過來也是如此。
解決方案(數據不一致)
- 寫入時,刪除Redis中的數據
總結
在實際使用過程種還是要結合具體的業(yè)務,來從上述幾個方面來考慮程序的健壯性。最終的目的都是保護數據庫。