1、為什么要用緩存
現(xiàn)在我們系統(tǒng)里面的數(shù)據(jù)還比較少蜓耻,所以頁面看起來還是比較快蜡镶,但是隨著數(shù)據(jù)量的增長雾袱,我們很快就會發(fā)現(xiàn)頁面會越來越慢,這時我們就需要進(jìn)行優(yōu)化官还。之前我們很多接口服務(wù)做了多次查詢谜酒,一個服務(wù)執(zhí)行多次sql,當(dāng)然會慢妻枕,大部分情況下盡量我們最好執(zhí)行一次sql僻族,但是執(zhí)行一次sql,就會增加代碼的復(fù)雜度屡谐,本來邏輯很清楚的代碼述么,變成了一個復(fù)雜的sql語句,這時我們可以考慮使用緩存愕掏。再者sql的優(yōu)化也是有限的度秘,也許已經(jīng)優(yōu)化得最優(yōu)了,sql執(zhí)行還是比較長饵撑,這時我們用緩存可以緩存數(shù)據(jù)庫的壓力剑梳,以前需要幾秒才能返回的服務(wù),現(xiàn)在只需要毫秒級的時間滑潘。
2垢乙、緩存的缺點(diǎn)
雖然緩存可以大大提高服務(wù)的效率,但是他也有不少缺點(diǎn)语卤。
首先追逮,緩存一般第一次訪問的時候還是會比較慢酪刀,因?yàn)樗枰跏蓟鞘孪阮A(yù)知用戶會訪問哪些數(shù)據(jù)钮孵,事先緩存骂倘。
其次,緩存有時效性巴席,緩存的數(shù)據(jù)不可能一直有效历涝,一直有效的數(shù)據(jù)那是常量了,緩存的數(shù)據(jù)一般有兩種情況漾唉,一種是設(shè)置有效時間荧库,超過時間自動清除,另一種是需要開發(fā)人員編程手動去清除毡证,當(dāng)然可以兩者結(jié)合。如果我們緩存的數(shù)據(jù)更新非常頻繁蔫仙,那么緩存就沒太大意義料睛,除非業(yè)務(wù)可以接受數(shù)據(jù)的延遲。假如我們平均每秒鐘會發(fā)布一篇文章摇邦,我們將文章列表緩存起來恤煞,有效時間設(shè)為1秒鐘,這樣施籍,sql每秒鐘最多執(zhí)行1次居扒,發(fā)布的文章基本上可以實(shí)時看到,但是如果sql執(zhí)行效率比較低丑慎,1秒鐘執(zhí)行一次還是困難喜喂,我們可以將緩存設(shè)置為1分鐘,這樣1分鐘最多執(zhí)行一次竿裂,但是用戶得接受新發(fā)布的文章可能需要1分鐘以后才能看到玉吁。
最后,緩存增加了編程的復(fù)雜性腻异,我們不僅需要考慮如何設(shè)置緩存进副,還需要考慮如何清除緩存,往往我們看到的很多異常數(shù)據(jù)悔常,經(jīng)常都是因?yàn)榫彺鏇]有正確更新導(dǎo)致的影斑。
3、緩存的類型
我們有很多東西都可以用來作為緩存的容器机打,最簡單的矫户,我們直接使用容器的內(nèi)存就可以作為緩存,但這種情況只適合單機(jī)部署残邀。分布式應(yīng)用時吏垮,我們經(jīng)常使用Redis作為緩存介質(zhì)障涯。其余的還有很多如Guava,Caffeine等等膳汪。
4唯蝶、Spring Cache
Spring提供了緩存的通用接口,我們可以使用注解的方式遗嗽,或者編程的方式操作緩存粘我。但是由于各個緩存介質(zhì)支持的功能有所不同,所以Spring只能抽象出通用的接口痹换。例如Redis是支持設(shè)置緩存有效時間的征字,而我們的內(nèi)存緩存不支持,所以Spring緩存的通用接口不能進(jìn)行有效時間設(shè)置娇豫。如果需要用到這一特性匙姜,我們可以直接使用Spring Redis的接口進(jìn)行編程。
4.1注解緩存
spring提供了幾個注解用來操作緩存冯痢。首先我們需要使用@EnableCaching打開注解緩存
我們以CmsController為例氮昧,對getCategoryTree方法進(jìn)行緩存,因?yàn)槲覀兊姆诸悩浜苌侔l(fā)生改變
這里我們使用了@Cacheable注解浦楣。我們在方法內(nèi)部打個斷點(diǎn)袖肥,我們訪問頁面,第一次進(jìn)入斷點(diǎn)振劳,刷新界面椎组,發(fā)現(xiàn)不會再進(jìn)入斷點(diǎn),說明我們的緩存生效了历恐。
重啟應(yīng)用后寸癌,再次訪問頁面,第一次會再次進(jìn)入斷點(diǎn)弱贼,因?yàn)镾pring默認(rèn)使用的內(nèi)存介質(zhì)是Simple灵份,即容器內(nèi)存,容器內(nèi)存會隨著應(yīng)用的停止而釋放哮洽,所以緩存也會跟著釋放填渠。要想持久化可以選擇其他緩存,例如Redis鸟辅。
4.2編程緩存
上面使用注解加了緩存氛什,要想緩存的數(shù)據(jù)一直保持正確,我們必須在分類數(shù)據(jù)修改的時候清除緩存匪凉,清除緩存對應(yīng)注解@CacheEvict枪眉,但是我們修改分類的方法都是在基類完成的,不方便用注解控制再层,我們可以直接用編程的方式來控制贸铜。
我們首先去掉之前的@Cacheable注解堡纬,然后注入一個CacheManager
CacheManager就是Spring抽象出來的統(tǒng)一處理緩存的管理工具類。
從上面可以看到蒿秦,以前只需一個注解就可以完成的功能烤镐,現(xiàn)在我們需要更多的代碼去處理。
同樣棍鳖,現(xiàn)在我們需要在分類變更時處理緩存炮叶,
這里,我們選擇在分類數(shù)據(jù)新增渡处,修改和刪除的時候镜悉,直接清除緩存,這樣医瘫,下次訪問的時候侣肄,就會重新查詢數(shù)據(jù)庫。
5醇份、定時清除
不管是上面的注解緩存稼锅,還是編程緩存,因?yàn)槲覀儸F(xiàn)在緩存的時效性都是永久被芳,為了保證緩存的正確性缰贝,我們都必須確保在數(shù)據(jù)修改時進(jìn)行緩存更新馍悟。我們必須確保所有可能導(dǎo)致數(shù)據(jù)變化的接口服務(wù)都能監(jiān)聽處理到畔濒。隨著項(xiàng)目越來越大,可能涉及數(shù)據(jù)修改的接口也越來越多锣咒,很容易漏掉侵状。所以有時候選擇定時清除緩存可能更加簡單可靠。
上面說了毅整,緩存的時效性并不是所有緩存介質(zhì)都支持趣兄,我們的內(nèi)存緩存就不支持,但是我們還是可以通過自己編程來實(shí)現(xiàn)悼嫉。例如艇潭,上面的例子,我們完全就可以建立一個定時任務(wù)來定時清理緩存戏蔑。這里我們簡單實(shí)現(xiàn)一個定時清除的功能蹋凝。
我們在CmsController添加一個構(gòu)造方法,在構(gòu)造方法里面啟動一個線程总棵,該線程每過1分鐘鳍寂,將會自動清除緩存,這樣基本上可以保證我們最遲1分鐘以后看到最新數(shù)據(jù)情龄。
對于很多系統(tǒng)迄汛,首頁有很多統(tǒng)計(jì)報(bào)表捍壤,報(bào)表一般業(yè)務(wù)復(fù)雜,查詢慢鞍爱,如果報(bào)表的實(shí)時性要求不是那么高鹃觉,我們就可以采用這種方法。但是這種方法在緩存清除后硬霍,第一次查詢還是會有點(diǎn)慢帜慢,我們可以進(jìn)一步優(yōu)化,在緩存過期前唯卖,先查詢數(shù)據(jù)粱玲。
這樣,除了容器啟動第一次查詢慢拜轨,后面每次都會以最快的速度返回抽减。這種方式可以理解為饑餓式,之前的為懶漢式橄碾。
6卵沉、Redis緩存
在分布式部署的時候,我們一般必須使用Redis緩存法牲,使用Redis緩存也狠簡單史汗,我們只需要修改配置即可
7、總結(jié)
這節(jié)主要講解緩存的作用和使用方式拒垃,說到底緩存就是一個Map停撞,篇幅有限,很多地方?jīng)]有細(xì)講悼瓮,需要大家在實(shí)戰(zhàn)中理解戈毒。
代碼:
https://github.com/www15119258/springboot-study/tree/branch34