緩存穿透、雪崩、擊穿
- 1、緩存穿透
- 強調都沒有數據+并發訪問
- 布隆過濾器
- 緩存NULL值
- 2、緩存雪崩
- 強調批量Key過期+并發訪問
- 3、緩存擊穿
- 強調單個Key過期+并發訪問
- 互斥鎖
- 邏輯過期
- 分布式并發控制
1、緩存穿透
緩存穿透是指數據庫和緩存都沒有的數據,這樣緩存永遠不會生效,大量的請求有可能導致數據庫宕機。
強調都沒有數據+并發訪問
一般處理緩存穿透有布隆過濾器 和 緩存null值 兩種方式。
布隆過濾器
布隆過濾器是使用一個初始全部是0的位數組,插入元素時先哈希一下,把哈希計算出的多個值的對應位置設為1,然后下次再有同樣的元素來時先計算一下,如果對應位置的位都是1那就代表有這個元素。這樣再去放行訪問reids。但布隆過濾器是存在誤判的可能性的,因為它走的是哈希思想,只要哈希思想,就可能存在哈希沖突。
緩存NULL值
第二種解決緩存穿透的方式就是:緩存空對象的思想比較簡單,查詢發現緩存和數據庫都沒有,就給緩存里加一個空值,下次這個請求再來直接返回緩存的空對象就行。但有個問題就是可能會造成短期內數據的不一致,比如緩存空對象的過期時間為10秒,如果在這10秒內底層數據發生了變化,而緩存層的查詢仍然會返回緩存的空對象,就會導致短期內數據不一致。
2、緩存雪崩
緩存雪崩是指,有很多數據,數據庫有,但緩存沒有(比如同時失效或者Redis服務宕機),導致這些大量的請求不走redis而是直接去查數據庫的情況
強調批量Key過期+并發訪問
多層級緩存,或者也可以給不同的Key的TTL添加隨機值
3、緩存擊穿
緩存擊穿是指,數據庫有,但緩存沒有的某個熱點數據,突然失效,導致的大量這個key請求不走redis而是直接去查數據庫的情況。
強調單個Key過期+并發訪問
【之所以會出現這個緩存擊穿問題,主要原因是在于我們對key設置了過期時間,假設我們不設置過期時間,其實就不會有緩存擊穿的問題,但是不設置過期時間,數據就會一直占用我們內存】
出現這種問題之后,當然是需要把熱點數據回寫到緩存里,那這就會有并發寫的問題。
一般處理方案是1互斥鎖,2邏輯過期
互斥鎖
只要它們使用同一把鎖,就能保障共享資源的正確性和一致性。
互斥鎖實現簡單,因為僅僅只需要加一把鎖,不用其他的操作了,但它只能串行執行,性能肯定受到影響。
邏輯過期
邏輯過期指的是,我們把過期時間設置在value中。假設線程1去查詢緩存,從value中判斷出來當前的數據已經過期了,那它就會去獲得互斥鎖,然后專門開啟一個新線程11去進行重構數據的邏輯,而線程1此時不等了直接返回過期的舊數據,直到新開的線程完成這個邏輯后,才會釋放鎖。在這個過程當中如果線程2過來訪問,由于線程線程11持有著鎖,所以線程2無法獲得鎖,也直接返回舊數據,只有等到新開的線程11把重建數據構建完后,之后其他線程才能返回正確的數據。
邏輯過期的優點在于它更新緩存的操作是異步進行的,其他線程不用等待。缺點在于在構建完緩存之前,返回的都是臟數據。
分布式并發控制
分布式鎖的核心思想在于所有線程都共享同一把鎖。
- 實現的方式是使用redis中的 SET NX命令獲取鎖,這個命令可以保證互斥性,只有一個線程能夠成功獲取鎖。
為了避免死鎖情況的發生,在線程拿鎖的同時也設置鎖的過期時間,這樣即使系統發生故障也能保證鎖在一定時間后自動釋放。 - 還有就是在分布式條件下,可能會出現一些極端情況,【A線程拿到鎖然后阻塞了,時間到了之后鎖過期被放掉。然后B線程獲取到互斥鎖開始執行邏輯,A線程這時候恢復了,又繼續執行然后把B的鎖釋放掉】。那為了解決這種情況拿鎖的時候存進自己的線程標識,在釋放鎖時,先驗證一下這把鎖是不是自己存的,確定后才刪除,這樣就不會出現刪除別的線程鎖的情況。
- 除此之外,會有一個拿鎖-比鎖-釋放的過程,為了避免出現在這個過程還沒走完系統宕機或者其他極端情況影響系統的可靠性,拿鎖-比鎖-釋放的代碼封裝到一個lua腳本里,這樣代碼執行的時候就能保證它的原子性。
基于setnx實現的分布式鎖存在下面的問題:
1、主從一致性問題: 如果Redis提供了主從集群,當我們向集群寫數據時,主機需要異步的將數據同步給從機,而萬一在同步過去之前,主機宕機了,就會出現數據不一致問題。
2、可重入問題:同一個線程在持有鎖的情況下可以繼續執行需要獲取同一鎖的代碼,而不會被阻塞。可重入鎖有助于避免死鎖和提高代碼的靈活性。(lua里拿鎖,當鎖已經存在時,判斷傳入的線程標識是否相等,如果相等代表就是可重入鎖返回1代表獲取到了鎖,并且重置過期時間)