1、實時同步
對強一致要求比較高的,應采用實時同步方案,即查詢緩存查詢不到再從DB查詢,保存到緩存;
更新緩存時,先更新數據庫,再將緩存的設置過期(建議不要去更新緩存內容,直接設置緩存過期)。
為什么不去更新緩存內容,而是設置緩存過期呢?
答:我們先來了解兩個概念
1.1. 緩存穿透
緩存穿透是指查詢一個數據庫中一定不存在的數據,由於緩存是不命中時需要從數據庫中查詢,查不到數據則不寫入緩存,這就將導致這個不存在的數據每次請求都要到數據庫中查詢,造成緩存穿透。
你可以通俗的理解,直接把緩存穿透了,沒有利用到緩存。。。
舉例:
If(redis存在此key){
查詢redis值
}else{
查詢數據庫,如果能查到數據,就寫入到redis中
}
這一段代碼邏輯就會造成緩存穿透的惡果。。
你想,假設這個查詢數據庫中永遠沒有值,那么這個redis中這個key是不是就不會創建,那么代碼就永遠只走查詢數據庫這段代碼,跟redis沒關系了。。
解決緩存穿透很簡單,就是數據庫查詢不到也要緩存結果,不過緩存結果賦值為空而已,但它的過期時間會很短,最長不超過五分鍾。
If(redis存在此key){
Var redisValue=查詢redis值
返回值
}else{
查詢數據庫.
If(能查到數據){
就寫入到redis中
}else{
如果查詢不到,也寫到redis中,只不過值是空值
}
}
要注意的點:
第一,空值做了緩存,意味着緩存層中存了更多的鍵,需要更多的內存空間 ( 如果是攻擊,問題更嚴重 ),比較有效的方法是針對這類數據設置一個較短的過期時間,讓其自動剔除。
第二,緩存層和存儲層的數據會有一段時間窗口的不一致,可能會對業務有一定影響。例如過期時間設置為 5分鍾,如果此時存儲層添加了這個數據,那此段時間就會出現緩存層和存儲層數據的不一致,此時可以利用消息系統或者其他方式清除掉緩存層中的空對象。
第三,Insert時需要事先移除要查詢的key,否則即便DB中有值也查詢不到(當然也可以設置空緩存的過期時間)
1.2. 緩存雪崩
如果緩存集中在一段時間內失效,發生大量的緩存穿透,所有的查詢都落在數據庫上,造成了緩存雪崩。
這個沒有完美解決辦法,但可以分析用戶行為,盡量讓失效時間點均勻分布。大多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線程(進程)寫,從而避免失效時大量的並發請求落到底層存儲系統上。
解決方法
1. 加鎖排隊. 限流-- 限流算法. 1.計數 2.滑動窗口 3. 令牌桶Token Bucket 4.漏桶 leaky bucket [1]
在緩存失效后,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。
業界比較常用的做法,是使用mutex。簡單地來說,就是在緩存失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設緩存;否則,就重試整個get緩存的方法。
SETNX,是「SET if Not eXists」的縮寫,也就是只有不存在的時候才設置,可以利用它來實現鎖的效果。
2.數據預熱
可以通過緩存reload機制,預先去更新緩存,再即將發生大並發訪問前手動觸發加載緩存不同的key,設置不同的過期時間,讓緩存失效的時間點盡量均勻
3.做二級緩存,或者雙緩存策略。
A1為原始緩存,A2為拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置為短期,A2設置為長期。
4.緩存永遠不過期
這里的“永遠不過期”包含兩層意思:
(1) 從緩存上看,確實沒有設置過期時間,這就保證了,不會出現熱點key過期問題,也就是“物理”不過期。
(2) 從功能上看,如果不過期,那不就成靜態的了嗎?所以我們把過期時間存在key對應的value里,如果發現要過期了,通過一個后台的異步線程進行緩存的構建,也就是“邏輯”過期.
從實戰看,這種方法對於性能非常友好,唯一不足的就是構建緩存時候,其余線程(非構建緩存的線程)可能訪問的是老數據,但是對於一般的互聯網功能來說這個還是可以忍受。
1.3. 熱點Key
熱點key:某個key訪問非常頻繁,當key失效的時候有大量的線程來構建緩存,導致負載增加,系統崩潰。
解決辦法:
1、使用鎖,單機用synchronized,lock等,分布式用分布式鎖
2、緩存過期時間不設置,而是設置在key對應的value里。如果檢測到存的時間超過過期時間則異步更新緩存
3、在value設置一個比過期時間t0小的過期時間值t1,當t1過期的時候,延長t1並做更新緩存操作
4、設置標簽緩存,標簽緩存設置過期時間,標簽緩存過期后,需要異步更新實際緩存
2、異步隊列
對於並發程度較高的,可采用異步隊列的方式同步,可采用kafka等消息中間件處理消息生產和消費。
3、使用阿里的同步工具canal
canal是阿里巴巴旗下的一款開源項目,純Java開發。基於數據庫增量日志解析,提供增量數據訂閱&消費,目前主要支持了MySQL(也支持mariaDB)。
起源:早期,阿里巴巴B2B公司因為存在杭州和美國雙機房部署,存在跨機房同步的業務需求。不過早期的數據庫同步業務,主要是基於trigger的方式獲取增量變更,不過從2010年開始,阿里系公司開始逐步的嘗試基於數據庫的日志解析,獲取增量變更進行同步,由此衍生出了增量訂閱&消費的業務,從此開啟了一段新紀元。
基於日志增量訂閱&消費支持的業務:
數據庫鏡像
數據庫實時備份
多級索引 (賣家和買家各自分庫索引)
search build
業務cache刷新
價格變化等重要業務消息
簡單來說,Canal 會將自己偽裝成 MySQL 從節點(Slave),並從主節點(Master)獲取 Binlog,解析和貯存后供下游消費端使用。Canal 包含兩個組成部分:服務端和客戶端。服務端負責連接至不同的 MySQL 實例,並為每個實例維護一個事件消息隊列;客戶端則可以訂閱這些隊列中的數據變更事件,處理並存儲到數據倉庫中
原理相對比較簡單:
- canal模擬mysql slave的交互協議,偽裝自己為mysql slave,向mysql master發送dump協議
- mysql master收到dump請求,開始推送binary log給slave(也就是canal)
- canal解析binary log對象(原始為byte流)