1.緩存的作用
數據庫(如Mysql)的持久化特點帶來了較低的性能,高并發的場景下,連接池很快被耗盡而出現宕機或DOS,無法繼續對外提供服務。相對于數據庫的硬盤IO,緩存中間件基于內存進行讀寫,從而具備較大的吞吐量和高并發抵抗能力。
在服務器與數據庫之間添加一層緩存,一方面可以緩解數據庫壓力,適應高并發場景;另一方面可以提高服務器的響應速度(內存讀寫速度遠高于磁盤IO),具體流程如下所示:
引入緩存后,服務器會先從緩存服務器查詢,若數據不存在,才會從數據庫查詢,并將數據庫查詢結果寫入緩存服務器。當數據被緩存至緩存服務器后,服務器后續直接從內存讀取,不再經過數據庫。
數據庫中的數據都多寫少,而且一般而言遵循二八定律,即20%為熱點數據,80%為不常用數據。內存資源較為寶貴,所以希望緩存中盡可能多的是熱點數據;過期時間、續期等機制為其提供了一個很好的解決方案。
Redis是緩存常用的方案,本文介紹的緩存服務器默認指Redis,數據庫默認指代Mysql。Redis中以鍵值對的形式存放數據。Redis之所以可以保護Mysql,是因為過濾了絕大部分請求壓力,當這部分壓力透過Redis直接轉移到Mysql時, 會導致Mysql服務宕機。有三種場景會導致這個問題,以下分章節進行介紹。
2.緩存穿透
當訪問數據庫中不存在的數據時,也不會將數據緩存到Redis中,從而每次請求都直接訪問數據庫,如同穿透了緩存一樣。攻擊者可以借此繞開Redis的緩存保護,供給服務器的數據庫,如Mysql數據庫的ID為自增序列時,高并發查詢ID為-1的數據。
如下圖所示,Redis和Mysql中不存在數據C,客戶端高并發請求C數據時,請求會全部發送到Mysql中。
存在以下解決方案:
方案1:緩存空值
查詢數據據庫的結果為空時,在Redis中緩存空值并設置較短的有效時間。對于每個不存在的數據都緩存一個空值,可能導致Redis中緩存了大量無效的空值,占據內存空間;另外,在空值的有效期內,可能出現數據不一致情況(數據在數據庫中被添加了)。
方案2:布隆過濾器
布隆過濾器基于Hash函數和長數組實現,特點是可能誤判(不存在表示一定不存在,存在表示可能存在)和不可刪除,當數據變化時,需要重建(定時器執行)布控過濾器。
使用布隆過濾器的流程如下:
初始化:
處理請求:
在《Redis系列-1 Redis介紹》中對Redission庫僅介紹了分布式鎖API的使用方式,除此之外,Redission庫還提供了大量分布式操作的API,如布隆過濾器等。
3.緩存擊穿
Redis中存放的熱點數據存在過期時間,當熱點數據過期后,客戶端的請求會穿過Redis直達數據庫。如下所示,Redis中數據C是個熱點數據,當數據C在Redis中過期而被清除后,高并發請求數據C時,請求會直達Mysql。
存在以下解決方案:
方案1:加分布式鎖
查詢數據庫前,獲取分布式鎖,結合DCL可以保證每個熱點數據僅有一次查詢發送到數據庫。
方案2:熱點數據持續刷新
服務初始化時,將熱點數據刷入Redis中,同時啟動一個定時服務:定時更新熱點數據的過期時間。另外,對于特殊業務場景下,可以設置熱點數據永不過期。
3.緩存雪崩
當Redis中大量緩存過期或者Redis服務器宕機,會導致Redis對于這些數據的攔截失敗,請求會發送到Mysql.
根據不同的原因,存在如下對應解決方案:
方案1:緩存預熱
啟動時進行緩存預熱,將熱點數據提前寫入Redis緩存中,避免系統啟動時高并發訪問Mysql.
方案2:緩存時間添加隨機值
緩存時間添加指定范圍的隨機值,防止緩存集中失效。
方案3:部署Redis集群
Redis宕機會導致緩存數據全局失效,可通過部署Redis集群提高可用性。
另外,還可通過添加分布式鎖來壓縮請求速度,從而給數據庫爭取處理時間;由于嚴重影響吞吐量,使用較少。