如何通過緩存來提升系統(tǒng)性能
今日推薦 Spring新版本拋棄JVM,可獨立部署,網(wǎng)友:要自立門戶??? Nginx 常用配置清單 這玩意比ThreadLocal叼多了,嚇得我趕緊分享出來。 推薦一些chrome瀏覽器必裝的插件! 40 個 SpringBoot 常用注解 VSCode 花式玩法(摸魚)收藏一下 !
緩存
在系統(tǒng)中最消耗性能的地方就是對數(shù)據(jù)庫的訪問了,一般來說,增、刪、改操作不會出現(xiàn)什么性能問題,除非索引太多,并且數(shù)據(jù)量有十分龐大的情況下,這三個操作才會導致性能問題。一般可以限制單表索引的數(shù)量來提升性能,比如單表的索引數(shù)量不能超過5個。
絕大多數(shù)情況下,性能問題都出在查詢上,select操作提供了非常豐富的語法,這些語法包括函數(shù),子查詢,like子句,where子句等,這些查詢都是非常消耗性能的。大部分應用都是讀多寫少的應用,所以查詢慢的問題會被放大了,導致慢查詢成為了系統(tǒng)性能的瓶頸,這是就需要用到緩存來提高系統(tǒng)的性能。
緩存為什么能提供系統(tǒng)性能?
緩存通過減少系統(tǒng)對數(shù)據(jù)庫的訪問量來提高系統(tǒng)性能。試想一下,如果有100個請求同時請求同一個數(shù)據(jù),沒有加緩存之前,需要訪問100次數(shù)據(jù)庫,而加了緩存之后,可能只需訪問1次數(shù)據(jù)庫,剩下的99個請求的數(shù)據(jù)從緩存中取,大大的較少了數(shù)據(jù)庫的訪問量,雖然同樣需要訪問100次,但數(shù)據(jù)庫的讀取性能和緩存的讀取性能不在一個級別上,所以對系統(tǒng)性能提升顯著。為什么說可能需要訪問1次數(shù)據(jù)庫呢,這個和過期有關,后面會講。
更新模式
既然知道了緩存對系統(tǒng)性能提升顯著,那下面先來了解一下緩存如何更新吧。
Cache Aside(推薦)
這應該是最常用的更新模式了,這種模式大致流程如下:
讀取
如果緩存中沒有,則再從數(shù)據(jù)庫中讀取數(shù)據(jù),得到數(shù)據(jù)之后,放入緩存。 如果緩存中有,取到后直接返回。
更新
先更新數(shù)據(jù)庫里的數(shù)據(jù),成功后,讓緩存失效。
為什么是讓緩存失效而不是更新緩存呢?
主要是因為兩個并發(fā)寫操作導致臟數(shù)據(jù)。試想一下,有兩個線程A和B,分別要將資源A的值修改為1和2,線程A先到達數(shù)據(jù)庫把數(shù)據(jù)更新為1,但還沒更新緩存,由于時間片用完了,此時線程B獲得了CPU,并在這期間把數(shù)據(jù)庫資源A的值和緩存的值都更新為2,線程B結束后,線程A重新獲得CPU,執(zhí)行更新緩存,把資源A的值改為1,線程A結束。此時,數(shù)據(jù)庫中A的值為2,而緩存中A的值為1,數(shù)據(jù)不一致。所以讓緩存失效就不會有這個問題,保證緩存中的數(shù)據(jù)和數(shù)據(jù)庫的保持一致。
那是不是Cache Aside模式就不會有并發(fā)問題了呢?
不是的。比如,一個讀操作,沒有命中緩存,就去數(shù)據(jù)的讀取數(shù)據(jù)(A=1),此時一個寫操作,更新數(shù)據(jù)庫數(shù)據(jù)(A=2)并讓緩存失效,此時讀操作把讀取到的數(shù)據(jù)(A=1)寫到緩存中,導致臟數(shù)據(jù)。
這種情況理論上會出現(xiàn),但現(xiàn)實情況中出現(xiàn)的幾率極低。要這種情況出現(xiàn)必須在一個讀操作發(fā)生時,有一個并發(fā)寫操作,并且既要讀操作要于寫操作寫入前讀取,又要后于寫操作寫入緩存。滿足這種條件的概率并不大。
基于出現(xiàn)上面所描述的問題,目前有兩種比較合理的解決方案:
通過2PC這種保證數(shù)據(jù)的一致性(復雜); 通過降低并發(fā)時臟數(shù)據(jù)的概率,并設置合理的過期時間(簡單,但存在一定時間內(nèi)的錯誤率,一般可以接受)。
Read/Write Through
在這種模式下,對于應用程序來說,所有的讀寫請求都是直接和緩存打交道,關于數(shù)據(jù)庫的數(shù)據(jù)完全由緩存服務來更新(更新同步為同步操作)。
這種模式下流程就相當簡單了,完全就是對緩存的讀寫。
缺點:這種模式對緩存服務有強依賴性,要求緩存具備高可用性。所以應有沒有上一種普遍。
Write Behind Caching
其實這個模式就是Read/Write Through的一個變種,區(qū)別就在于前者是異步更新,后者是同步更新。
既然是異步更新數(shù)據(jù)庫,他的相應速度比Read/Write Through還要高,并且還能合并對同一個數(shù)據(jù)的多次操作。這個有點像MySQL的buffer pool的刷盤操作。
缺點:異步就代表數(shù)據(jù)不是強一致性的,還存在數(shù)據(jù)丟失的風險,實現(xiàn)邏輯也較為復雜。
設計思路
無狀態(tài)的服務
在分布式系統(tǒng)中,無狀態(tài)的服務有利于橫向擴展,所以緩存也應該獨立于業(yè)務服務在外,設計成一個獨立的服務,使業(yè)務服務變成無狀態(tài)的。很多公司都選擇是用Redis來搭建他們的緩存系統(tǒng),取決于其高速的讀寫性能。
命中率
一個緩存服務的好壞主要看命中率,一般來說,命中率保存在80%以上已經(jīng)算很高了,但我們不能為了提高命中率而把數(shù)據(jù)庫的全部數(shù)據(jù)的寫到緩存了,這個是不符合緩存的設計理念,而且需要極大的內(nèi)存空間。通常來說,應該只有小部分熱點數(shù)據(jù)寫到緩存。 緩存是通過犧牲強一致性來換取性能的,并不是所有的業(yè)務的適合使用緩存。
有效時間
緩存數(shù)據(jù)的有效時間不易過短,不易過長,不易過于集中。
過短,會增加數(shù)據(jù)庫訪問的次數(shù)。 過長容易不使用的數(shù)據(jù)一直停留在緩存中,浪費空間,并且一旦產(chǎn)生臟數(shù)據(jù),過程的有效時間會導致臟數(shù)據(jù)遲遲無法失效,進而導致影響更多的業(yè)務。 過于集中,會導致緩存雪崩。
淘汰策略
當內(nèi)存不足時,緩存系統(tǒng)就要按照淘汰策略,把不適合留在緩存的數(shù)據(jù)淘汰掉,騰出位置給新數(shù)據(jù)。下面就以Redis為例,給出了淘汰策略:
noeviction: 不刪除策略, 達到最大內(nèi)存限制時, 如果需要更多內(nèi)存, 直接返回錯誤信息(有極少數(shù)會例外, 如 DEL )。allkeys-lru: 所有key通用,優(yōu)先刪除最近最少使用(less recently used ,LRU) 的 key。(推薦)volatile-lru: 只限于設置了 expire 的部分,優(yōu)先刪除最近最少使用(less recently used ,LRU) 的 key。allkeys-random: 所有key通用,隨機刪除一部分 key。volatile-random: 只限于設置了 expire 的部分,隨機刪除一部分 key。volatile-ttl: 只限于設置了 expire 的部分,優(yōu)先刪除剩余時間(time to live,TTL) 短的key。
可根據(jù)項目實際情況進行選擇。另外,歡迎關注公眾號Java筆記蝦,后臺回復“后端面試”,送你一份面試題寶典!
小結
緩存是為了加速數(shù)據(jù)的訪問,在數(shù)據(jù)庫之上的一直機制,并非所有業(yè)務都適合使用緩存,要根據(jù)具體情況選擇更新策略和淘汰策略。
Java網(wǎng)站推薦:www.java1000.com,網(wǎng)站包括Java基礎、進階、源碼、面試等各個系列文章,歡迎瀏覽! Github倉庫推薦: https://github.com/OUYANGSIHAI/JavaInterview,復制鏈接直達,該倉庫是本人面試一年的面試記錄與分享,相信對你有一定的幫助!
推薦文章
1、14個項目! 2、GitHub 上 6 款牛哄哄的后臺模板 3、推薦幾個前后端分離項目! 4、一個Github項目搞定微信、QQ、支付寶等第三方登錄 5、一款基于 Spring Boot 的現(xiàn)代化社區(qū)(論壇/問答/社交網(wǎng)絡/博客) 更多項目源碼 1、推薦兩個項目! 2、重磅推薦:一套開源的網(wǎng)校系統(tǒng),無論是自建網(wǎng)校還是接副業(yè)都很方便 3、一款基于 Spring Boot 的現(xiàn)代化社區(qū)(論壇/問答/社交網(wǎng)絡/博客) 4、13K點贊都基于 Vue+Spring 前后端分離管理系統(tǒng)ELAdmin,大愛 5、想接私活時薪再翻一倍,建議根據(jù)這幾個開源的SpringBoot
