緩存用不好,Bug改到老
前言
日常工作中,緩存的使用隨處可見。緩存使用得當,對提升系統的性能,提高用戶體驗感有著至關重要的作用。但是如果使用不當,就會出現一些令人費解或者數據混亂的問題。本文將給大家普及常見的一些緩存使用與緩存使用過程中的踩坑點,希望能幫助大家更好的理解與使用緩存,文中如有寫的不對的地方,歡迎大家留言指正。
java緩存形式/介質
眾所周知,緩存之所以訪問速度快,是因為把緩存的交互介質是內存。而常規的例如mysql數據交互介質是磁盤。那么常見的java中或者中間件供我們可以用來做緩存的開發的工具有幾種呢?
jvm本地內存
jvm本地內存常見使用為定義一個全局靜態變量,保證后端服務在運行過程中,對應的對象空間直接保持被引用,不會被GC給回收。
guava緩存工具類
存儲數據的本質與jvm內存類似,內部依靠維護java集合的子類來存儲數據,但是提供了緩存數據的過期時間,過期策略等設置,像一個小型的中間件。
redis
Redis 是完全開源的,遵守 BSD 協議,是一個高性能的 key-value 數據庫。常用作數據庫、緩存和消息代理。Redis 提供了諸如字符串、散列、列表、集合、帶范圍查詢的排序集合、位圖、hyperloglogs、地理空間索引和流streams等數據結構。Redis 內建復制、支持 Lua 腳本、支持 LRU 緩存淘汰策略、事務和不同級別的磁盤持久化,并通過 Redis Sentinel 和 Redis Cluster 自動分區提供高可用性。同類型中間件中,Redis是最火的,沒有之一。
幾種常用緩存的對比
緩存常見的坑
在分析緩存的坑之前我們先來看一下緩存的增刪改查如何保證數據庫與緩存的數據一致性。
查詢
查詢時先查詢緩存,如果緩存存在直接返回,不存在則查詢數據庫,將數據庫查詢結果寫入緩存【此處默認數據庫存在數據,不存在的情況后面分析】,然后將緩存的結果數據返回。
增刪改
增刪改時先增刪改數據庫,保證數據庫數據先被修改,然后同步緩存內數據,如果中間發生異常,則調用數據庫事務回滾數據。
緩存穿透
概念
正常情況下,查詢的數據都存在,如果請求一個不存在的數據,也就是緩存和數據庫都查不到這個數據,每次都會去數據庫查詢,這種查詢不存在數據的現象我們稱為緩存穿透。如上圖,用戶一直請求接口查詢不存在的id數據【數據庫中只存在id>0的數據】,對應的數據永遠不可能在緩存中。在高并發場景下,大量請求打到了數據庫,數據庫可能被突發的流量給打掛。
3.1.2.解決方式
1.過濾垃圾數據
在知道查詢的id數據大于0或者基于id是某種規則【例如雪花id】生成的情況下。過濾掉數據庫中不可能的存在的請求。方法入口直接增加一個參數校驗。
2.緩存空值
發生穿透的原因是數據在數據庫中不存在,那我們把null值給緩存下來,當請求到達時直接返回null。當然這里對緩存是必須加上過期時間的,以免后續真的存在此id的數據。過期時間不宜過長,根據實際業務場景并發量來進行設置。
3.IP攔截
對于惡意的攻擊請求,一直請求無效的數據,可以設置ip請求策略。如果對應的ip短時間內發起了大量請求,且請求參數均為不存在的數據。則將ip進行封禁一段時間,不允許再次請求系統。
**4.布隆過濾器
布隆過濾器(BloomFilter)用來判斷某個元素(key)是否存在于某個集合中我們把有數據的key都放到BloomFilter中,每次查詢的時候都先去BloomFilter判斷,如果沒有就直接返回null 。
注意BloomFilter沒有刪除操作,對于刪除的key,查詢就會經過BloomFilter然后查詢緩存再查詢數據庫,所以BloomFilter可以結合緩存空值用,對于刪除的key,可以在緩存中緩存null
緩存擊穿
嚴格意義上說緩存穿透是緩存擊穿的一種。只不過緩存擊穿是查詢的有效數據。在高并發情況下,查詢緩存時,緩存中的數據不存在或者已經失效了。那么會導致大量的請求打到了數據庫,打掛數據庫
解決方式
先來分析一下場景,大量請求同時到了緩存,緩存不存在,再請求數據庫。然后再將請求結果寫入到緩存。問題就是多個線程幾乎同時讀取緩存,又幾乎同時重寫緩存。多線程并發下解決問題,當然是用鎖。單體應用可以使用synchronized關鍵字或者ReentrantLock進行加鎖,分布式服務則可使用分布式鎖的方式來實現加鎖。
大致的偽代碼如下:
public?Object?query(){ ????//查詢緩存,存在則直接返回 ????Object?value?=?queryCache; ????//這里為了防止緩存穿透,可以緩存下空對象,而不是null,保證null值是必須查詢緩存的 ????if(Objects.nonNull(value)){ ????????return?value; ????} ????//加鎖訪問數據庫 ????lock{ ????????//二次查詢緩存,避免在高并發的情況下,多個線程都到了爭搶鎖的這個環節,在此之前 ????????//已經有線程拿到鎖寫入緩存了,無需再次查詢數據庫,直接返回即可 ????????value?=?queryCache; ????????if(Objects.nonNull(value)){ ????????????return?value; ????????} ????????//查詢數據庫 ????????value?=?queryDb; ????????//設置緩存數據 ????????setCache; ????????//返回結果 ????????return?value; ????} } 復制代碼
緩存雪崩
概念
緩存雪崩也是緩存擊穿的一種,緩存設置了過期時間/淘汰策略的情況下,在某個時間點,大量的緩存失效。高并發情況下大量請求打到了數據庫。
解決方式
緩存雪崩時,請求方式與緩存擊穿一致,主要如何防護緩存雪崩,基本指導思想為:
熱點數據設置永不過期,緩存淘汰策略為淘汰最早過期數據
數據緩存過期時間設置高離散度隨機值,避免某個時間點,大量緩存同時過期。
性能問題
概念
使用了緩存,但是性能還是上不去的場景。例如雙十一場景下,訂單數據量比較大。如果新增修改刪除所有操作都要先操作一遍數據庫,再回寫緩存的話效率是很低的。
解決方式
把緩存當做數據庫來使用,當然這里需要使用redis這種高可用的持久化緩存中間件。數據存在redis中,數據交互都直接交互redis。扛過流量高峰之后,啟用定時任務,將redis的數據刷入至數據庫或者ES。當然這里也可以使用消息隊列,這里不具體展開。
總結
本文著重講述了緩存的增刪改查策略與日常坑點。
數據一致性
查詢操作,先走緩存再走數據庫,再更新緩存。
增刪改操作,先走數據庫再更新緩存。
坑點
從緩存雪崩去理解緩存穿透與緩存擊穿。
高性能
讀寫持久化緩存數據,異步刷盤mysql。
重磅消息:Spring 6 和Spring Boot 3
2021-09-03
一個SpringMVC接口能返回JSON又能返回XML? 安排!
2021-08-31
數據庫
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。