我用Ehcache把查詢性能提升了100倍,真香!
今天給大家來(lái)分享一個(gè)知識(shí),那就是平時(shí)我們開(kāi)發(fā)系統(tǒng)的時(shí)候,如何運(yùn)用 Ehcache 這款本地緩存框架,把我們的查詢性能大幅度提升優(yōu)化,甚至讓很多查詢操作性能提升到 100 倍以上,下面就來(lái)講講這個(gè)話題。
業(yè)務(wù)場(chǎng)景
首先給大家引入一個(gè)場(chǎng)景,就是假設(shè)咱們寫(xiě)的一套 Java 系統(tǒng)要跑一個(gè)幾百行的大 SQL 從 MySQL 里查詢數(shù)據(jù),這個(gè)查詢是不是會(huì)速度非常的慢?
那肯定是了,這種幾百行大 SQL 往往都是那種超級(jí)復(fù)雜的查詢,可能涉及到了多表的關(guān)聯(lián),也有的是那種數(shù)據(jù)指標(biāo)的查詢,當(dāng)然這種數(shù)據(jù)指標(biāo)的查詢其實(shí)是會(huì)常見(jiàn)一些,就是針對(duì)各種數(shù)據(jù)表關(guān)聯(lián)起來(lái)查詢和統(tǒng)計(jì)一些指標(biāo)。
一般來(lái)說(shuō)的話,遇到這種超級(jí)大 SQL,往往會(huì)導(dǎo)致查詢 MySQL 性能很差,一般跑個(gè) 1s 甚至好幾秒那是很常見(jiàn)的了。
比如下圖:
所以 往往對(duì)于這種場(chǎng)景來(lái)說(shuō),如果想要優(yōu)化一下這個(gè)查詢的性能,我們一般會(huì)用緩存。
也就是說(shuō),這一次用幾百行 SQL 語(yǔ)句查詢出了結(jié)果,好不容易用了幾秒鐘特別特別慢,接著其實(shí)就把這個(gè)結(jié)果緩存起來(lái),下次請(qǐng)求過(guò)來(lái),直接就用這個(gè)緩存里的數(shù)據(jù)拿出來(lái)返回就可以了,從緩存里讀結(jié)果以及返回,最多就是個(gè) 1ms 的事兒,根本不用幾秒那么漫長(zhǎng)了。
如何通過(guò)緩存優(yōu)化查詢接口
那么問(wèn)題來(lái)了,這個(gè)緩存的結(jié)果是放哪里?可能很多兄弟說(shuō)可以放 Redis 里啊!但是,一定要每次用緩存就立馬上 Redis 嗎?
畢竟 Redis 還得額外部署集群,一旦引入 Redis,你還得考慮 Redis 是否會(huì)有故障,他的一些接入問(wèn)題,以及跟 Redis 進(jìn)行網(wǎng)絡(luò)通信畢竟也是要耗時(shí)的。
所以說(shuō),其實(shí)咱們優(yōu)先啊,可以先上本地緩存,也就是說(shuō),在業(yè)務(wù)系統(tǒng)運(yùn)行的 JVM 的堆內(nèi)存里,來(lái)緩存我們的查詢結(jié)果,下次請(qǐng)求來(lái)了,就從本地緩存里取出來(lái)直接返回就可以了。
如下圖:
基于大數(shù)據(jù)離線平臺(tái)進(jìn)行緩存預(yù)熱
那么下一個(gè)問(wèn)題又來(lái)了,很多查詢他可能當(dāng)天第一次查的時(shí)候,本地緩存里是沒(méi)有的,還是得去 MySQL 里花費(fèi)幾秒鐘來(lái)查詢,查完了以后才能放入到本地緩存里去,那這樣豈不是每天都有一些人第一次查詢很慢很慢嗎?
有沒(méi)有更好的辦法呢?當(dāng)然有了,那就是緩存預(yù)熱,我們的業(yè)務(wù)系統(tǒng)可以把每天的查詢請(qǐng)求和參數(shù)都記錄下來(lái)。
對(duì)于一些數(shù)據(jù)報(bào)表的復(fù)雜查詢,其實(shí)每天的查詢條件都是差不多的,只不過(guò)是當(dāng)天的日期會(huì)有變化而已,另外就是對(duì)于一些數(shù)據(jù)報(bào)表的數(shù)據(jù),往往是通過(guò)大數(shù)據(jù)平臺(tái)進(jìn)行離線計(jì)算的。
啥叫做離線計(jì)算呢?就是說(shuō)可能有一個(gè)大數(shù)據(jù)系統(tǒng)每天凌晨的時(shí)候會(huì)把昨天的數(shù)據(jù)算一遍,算好的數(shù)據(jù)結(jié)果寫(xiě)入到 MySQL 里去,然后每天更新數(shù)據(jù)就這一次,接著當(dāng)天就不更新數(shù)據(jù)了。
如下圖:
然后呢,用戶每天都會(huì)對(duì)我們的系統(tǒng)發(fā)起很多次復(fù)雜報(bào)表查詢語(yǔ)句,但是這個(gè) SQL 多表關(guān)聯(lián)的一些邏輯,以及附加的一些查詢條件幾乎都是有規(guī)律的是差不多的,就是每天選擇的當(dāng)天日期是不太一樣的。
所以此時(shí)我們就可以把這些查詢條件記錄下來(lái),然后每天凌晨的時(shí)候,趁著大家都睡覺(jué)了,就根據(jù)經(jīng)常查詢的條件和當(dāng)天日期,提前去查詢數(shù)據(jù),查詢結(jié)果提前寫(xiě)入本地緩存。
這樣用戶第一次來(lái)訪問(wèn),就可以直接從本地緩存里拿到最新的數(shù)據(jù)了,如下圖:
本地緩存框架:Ehcache
接著給大家講講咱們常用的本地緩存框架,Ehcache,這是大名鼎鼎的一個(gè)本地緩存框架,基本上 Ehcache 和 Guava 兩款本地緩存框架,用的是最多的,我們以 Ehcache 舉例來(lái)講講本地緩存框架是怎么用的。
首先得在咱們的項(xiàng)目 pom.xml 里引入對(duì)應(yīng)的依賴,如下所示:
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.2</version>
</dependency>
接著就惡意引入一個(gè) ehcache.xml 這種配置文件,對(duì)我們的緩存框架進(jìn)行一定的配置了。
如下所示:
下面給大家解釋一下 ehcache 框架運(yùn)行起來(lái)以后上述那些參數(shù)對(duì)他的影響,首先 maxElementsInMemory 說(shuō)的就是他在內(nèi)存里可以緩存多少條數(shù)據(jù)。
eternal 意思是說(shuō)這個(gè)緩存是否是永久有效的,如果要是永久有效了那么 timeToLiveSeconds 也就沒(méi)用了。
但是如果不是永久有效的,就可以設(shè)置 timeToLiveSeconds 了,比如說(shuō)可以設(shè)置緩存數(shù)據(jù)生存 24 小時(shí),然后就自動(dòng)過(guò)期,接著就必須要強(qiáng)制從數(shù)據(jù)庫(kù)里來(lái)查詢了。
overflowToDisk 是說(shuō)如果緩存的數(shù)據(jù)要是超過(guò)了 maxElementsInMemory 的時(shí)候,是不是把多余的數(shù)據(jù)刷寫(xiě)到磁盤里去。
diskPersistent 是說(shuō)在 JVM 重啟的時(shí)候,要不要把內(nèi)存里緩存的數(shù)據(jù)刷寫(xiě)到磁盤里去,然后 JVM 重啟后再把磁盤里的數(shù)據(jù)恢復(fù)到內(nèi)存里來(lái),這倆參數(shù),如果要是緩存的數(shù)據(jù)特別多的話,其實(shí)還是可以開(kāi)啟的。
一方面是內(nèi)存緩存不下了可以刷寫(xiě)到磁盤去,一方面是內(nèi)存里的數(shù)據(jù)重啟的時(shí)候還是持久化一下,然后重新加載到內(nèi)存里來(lái)。
還有一個(gè)是 memoryStoreEvictionPolixy 是緩存的回收策略,因?yàn)槿绻蔷彺鏀?shù)據(jù)量過(guò)多了,導(dǎo)致內(nèi)存和磁盤都放不下了,這個(gè)時(shí)候就必須回收掉一部分的數(shù)據(jù)了,一般都是用 LRU,最近最少使用策略來(lái)回收的。
下面是 Ehcache 在代碼里的使用示例:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<cache name="report"
maxElementsInMemory="1000"
eternal="false"
timeToLiveSeconds="86400"
overflowToDisk="false"
disPersistent="false"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
希望今天給大家分享的本地緩存知識(shí)可以幫助到大家以后遇到類似的復(fù)雜報(bào)表數(shù)據(jù)查詢場(chǎng)景的時(shí)候,可以利用這個(gè)知識(shí)點(diǎn)去優(yōu)化自己系統(tǒng)的性能!