緩存技術(shù)被認(rèn)為是減輕服務(wù)器負(fù)載、降低網(wǎng)絡(luò)擁塞、增強(qiáng)Web可擴(kuò)展性的有效途徑之一,其基本思想是利用客戶(hù)訪問(wèn)的時(shí)間局部性(Temproral Locality)原理, 將客戶(hù)訪問(wèn)過(guò)的內(nèi)容在Cache中存放一個(gè)副本,當(dāng)該內(nèi)容下次被訪問(wèn)時(shí),不必連接到駐留網(wǎng)站,而是由Cache中保留的副本提供。
在企業(yè)Web應(yīng)用中,通過(guò)緩存技術(shù)能夠提高請(qǐng)求的響應(yīng)速度;減少系統(tǒng)IO開(kāi)銷(xiāo);降低系統(tǒng)數(shù)據(jù)讀寫(xiě)壓力...
緩存的意義
首先我們要知道,在我們開(kāi)發(fā)過(guò)程中,為什么要使用緩存,緩存能夠?yàn)槲覀儙?lái)哪些好處!
優(yōu)點(diǎn)
- 通過(guò)緩存承載系統(tǒng)壓力,減少對(duì)系統(tǒng)或網(wǎng)絡(luò)資源訪問(wèn)而引起的性能消耗,在流量較大時(shí)能夠很好地減少系統(tǒng)擁塞
- 緩存一般都是使用存取非??斓慕M件實(shí)現(xiàn),通過(guò)緩存能夠快速響應(yīng)客戶(hù)端請(qǐng)求,從而降低客戶(hù)訪問(wèn)延遲,提審系統(tǒng)響應(yīng)速度
- 在配備負(fù)載均衡的應(yīng)用架構(gòu)中,通過(guò)緩存靜態(tài)資源能夠有效減少服務(wù)器負(fù)載壓力
- 當(dāng)下游應(yīng)用故障時(shí),通過(guò)返回緩存數(shù)據(jù)能夠在一定程度上增強(qiáng)應(yīng)用容錯(cuò)性
缺點(diǎn)
- 緩存數(shù)據(jù)與實(shí)際數(shù)據(jù)不一致問(wèn)題問(wèn)題
- 高并發(fā)場(chǎng)景時(shí)存在緩存擊穿、緩存穿透、緩存雪崩等問(wèn)題
總的來(lái)說(shuō),緩存主要是針對(duì)高頻訪問(wèn)但低頻更新的數(shù)據(jù),從而加快服務(wù)器響應(yīng)與原資源訪問(wèn)壓力
Guava Cache是一個(gè)相對(duì)比較簡(jiǎn)單并且容易理解的本地緩存框架,今天主要以此為開(kāi)端來(lái)認(rèn)識(shí)并學(xué)習(xí)如何使用緩存
Guava Cache特色
本地緩存我們可以簡(jiǎn)單的理解為Map,將數(shù)據(jù)保存到Map(內(nèi)存)中,下次使用該數(shù)據(jù)時(shí),通過(guò)key直接從Map中取即可。但是使用Map會(huì)有一些幾個(gè)問(wèn)題需要考慮:
- 緩存的容量。不可能無(wú)限制的對(duì)數(shù)據(jù)進(jìn)行緩存,當(dāng)數(shù)據(jù)較大時(shí)占用系統(tǒng)資源會(huì)導(dǎo)致主業(yè)務(wù)受影響
- 緩存的清理。有些緩存使用頻率很低,如果一直占用資源也是一種浪費(fèi)
- 并發(fā)訪問(wèn)時(shí)的效率問(wèn)題。緩存更新時(shí)瞬時(shí)對(duì)系統(tǒng)、網(wǎng)絡(luò)資源的訪問(wèn)導(dǎo)致故障
- 緩存使用情況評(píng)估
當(dāng)然以上問(wèn)題我們通過(guò)我們對(duì)Map包裝下即可實(shí)現(xiàn),當(dāng)然Guava Cache也就是基于這種思想,底層原理則是基于Map實(shí)現(xiàn),我們看下其有哪些特色:
- 緩存過(guò)期和淘汰機(jī)制
通過(guò)設(shè)置Key的過(guò)期時(shí)間,包括訪問(wèn)過(guò)期和創(chuàng)建過(guò)期;設(shè)置緩存容量大小,采用LRU的方式,選擇最近最久的緩存進(jìn)行刪除。
- 并發(fā)處理能力
Cache主要基于CurrentHashMap實(shí)現(xiàn)線程安全;通過(guò)對(duì)key的計(jì)算,基于分段鎖,提高緩存讀寫(xiě)效率,降低鎖的粒度,提升并發(fā)能力
- 更新鎖定
在緩存中查詢(xún)某個(gè)key,如果不存在,則查源數(shù)據(jù),并回填緩存。在高并發(fā)下會(huì)出現(xiàn),多次查詢(xún)?cè)獢?shù)據(jù)并重復(fù)回填緩存,可能會(huì)造成系統(tǒng)故障,最明顯的DB服務(wù)器宕機(jī),性能下降等。GuavaCache通過(guò)在CacheLoader調(diào)用load方法時(shí),對(duì)同一個(gè)key同一時(shí)刻只會(huì)有一個(gè)請(qǐng)求去讀源數(shù)據(jù)并回填緩存,后面的請(qǐng)求則直接繼續(xù)從緩存讀取,有效阻斷并發(fā)請(qǐng)求對(duì)資源服務(wù)的影響。
- 集成數(shù)據(jù)源
一般我們?cè)跇I(yè)務(wù)中操作緩存,都會(huì)操作緩存和數(shù)據(jù)源兩部分GuavaCache的get可以集成數(shù)據(jù)源,在從緩存中讀取不到時(shí)可以從數(shù)據(jù)源中讀取數(shù)據(jù)并回填緩存
- 監(jiān)控統(tǒng)計(jì)
監(jiān)控緩存加載次數(shù)、命中率、失誤率以及數(shù)據(jù)加載時(shí)長(zhǎng)等
API介紹
- 緩存構(gòu)建
- ManualCache
此時(shí)Cache相當(dāng)于一個(gè)Map,對(duì)數(shù)據(jù)進(jìn)行CRUD操作時(shí),需要同步操作緩存Map; 高并發(fā)情況時(shí),可以使用get(k,loader)讀緩存,通過(guò)Cache鎖機(jī)制,防止對(duì)系統(tǒng)資源(DB)的并發(fā)訪問(wèn) 通過(guò)put方法實(shí)現(xiàn)緩存的存入與更新; - LoadingCache
此時(shí)構(gòu)建的是一個(gè)實(shí)現(xiàn)了Cache接口的LoadingCache,相比ManualCache,提供了緩存回填機(jī)制,即當(dāng)緩存不存在時(shí),會(huì)基于CacheLoader查詢(xún)數(shù)據(jù)并將結(jié)果回填到緩存, 在高并發(fā)時(shí),可以有效地基于緩存鎖減少對(duì)系統(tǒng)資源的調(diào)用。此時(shí)僅需要關(guān)注緩存的使用,緩存的更新與存入都是基于CacheLoader實(shí)現(xiàn);
- ManualCache
- 緩存獲取
- get(k)
根據(jù)key查詢(xún),沒(méi)有則觸發(fā)load;如果load為空則拋出異常 - getUnchecked(k)
緩存不存在或返回為null會(huì)拋出檢查異常 - get(k,loader)
根據(jù)key查詢(xún),沒(méi)有則調(diào)用loader方法,且對(duì)結(jié)果緩存;如果loader返回null則拋出異常,此時(shí)不會(huì)調(diào)用默認(rèn)的load方法 - getIfPresent(k)
有緩存則返回,否則返回null,不會(huì)觸發(fā)load
- get(k)
- 緩存更新
- put(k,v)
如果緩存已經(jīng)存在,則會(huì)先進(jìn)行一次刪除
- put(k,v)
- 緩存刪除
- invalidate(k)
根據(jù)key使緩存失效 - 過(guò)期
通過(guò)配置的過(guò)期參數(shù),比如expireAfterAccess、expireAfterWrite、refreshAfterWrite - 過(guò)載
當(dāng)緩存數(shù)據(jù)量超過(guò)設(shè)置的最大值時(shí),根據(jù)LRU算法進(jìn)行刪除 - 引用
構(gòu)建緩存時(shí)將鍵值設(shè)置為弱引用、軟引用,基于GC機(jī)制來(lái)清理緩存
- invalidate(k)
- 統(tǒng)計(jì)
Builder配置
配置 | 描述 |
---|---|
expireAfterAccess | 多久沒(méi)有讀寫(xiě)則過(guò)期 |
expireAfterWrite | 寫(xiě)入后多久沒(méi)更新自動(dòng)過(guò)期,先刪除,后load |
refreshAfterWrite | 上一次更新后多久自動(dòng)刷新,先reload后刪除,并發(fā)時(shí)會(huì)取到老的數(shù)據(jù) |
removalListener | 設(shè)置緩存刪除監(jiān)聽(tīng) |
initialCapacity | 緩存初始化大小 |
concurrencyLevel | 最大的并發(fā)數(shù),可以理解為并發(fā)線程數(shù)量 |
maximumSize | 最大緩存數(shù)量,超過(guò)時(shí)會(huì)根據(jù)策略清除 |
maximumWeight | 最大權(quán)重容量數(shù),僅用于確定緩存是否超過(guò)容量 |
recordStats | 緩存命中統(tǒng)計(jì) |
簡(jiǎn)單示例
- ManualCache模式
下面以用戶(hù)服務(wù)為例,我們看下如何在增刪改查方法中使用緩存:
private Cache< String, User > cache = CacheBuilder.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)//寫(xiě)入多久沒(méi)更新自動(dòng)過(guò)期,先刪除,后load
.removalListener(new RemovalListener< Object, Object >() {
@Override
public void onRemoval(RemovalNotification< Object, Object > notification) {
LOGGER.info("{} remove {}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),notification.getKey());
}
})
.initialCapacity(20) //初始化容量
.concurrencyLevel(10) // 并發(fā)
.maximumSize(100) //最多緩存數(shù)量
.recordStats() // 開(kāi)啟統(tǒng)計(jì)
.build();
@Override
public User getUser(String id){
// 緩存不存在時(shí),通過(guò)LocalCache鎖機(jī)制,防止對(duì)數(shù)據(jù)庫(kù)的高頻訪問(wèn)
User user;
try {
user = cache.get(id,()- > {
LOGGER.info("緩存不存在,從loader加載數(shù)據(jù)");
return userDao.get(id);
});
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
return user;
}
@Override
public User saveOrUpdateUser(User user){
userDao.saveOrUpdate(user);
cache.put(user.getId(),user);
return user;
}
@Override
public void removeUser(String id){
userDao.remove(id);
cache.invalidate(id);
}
- LoadingCache模式
private LoadingCache< String, User > cache = CacheBuilder.newBuilder()
// 省略
.build(new CacheLoader< String, User >() {
@Override
public User load(String key) throws Exception {
LOGGER.info("{} load {}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),key);
return userDao.get(key);
}
@Override
public ListenableFuture< User > reload(String key, User oldUser) throws Exception {
LOGGER.info("{} reload {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),key);
ListenableFutureTask< User > listenableFutureTask = ListenableFutureTask.create(() - > userDao.get(key));
CompletableFuture.runAsync(listenableFutureTask);
return listenableFutureTask;
}
});
@SneakyThrows
@Override
public User getUser(String id){
// 緩存不存在或返回為null會(huì)拋出異常
try {
return cache.getUnchecked(id);
} catch (Exception e) {
return null;
}
}
@Override
public User saveOrUpdateUser(User user){
cache.invalidate(user.getId());
return userDao.saveOrUpdate(user);
}
@Override
public void removeUser(String id){
cache.invalidate(id);
userDao.remove(id);
}
總結(jié):第一種寫(xiě)法更像是前面說(shuō)到的Map,在對(duì)數(shù)據(jù)進(jìn)行CRUD操作時(shí),需要用戶(hù)手動(dòng)對(duì)緩存進(jìn)行同步的更新或刪除操作,所以叫ManualCache(手動(dòng)),當(dāng)然Guava Cache對(duì)Map的加強(qiáng)依然有效,比如過(guò)期清除,緩存容量限制。第二種方式寫(xiě)法差不多,主要是引入了CacheLoader接口,在讀數(shù)據(jù)時(shí)緩存數(shù)據(jù)不存在時(shí),通過(guò)CacheLoader的load方法先寫(xiě)緩存后返回?cái)?shù)據(jù)
注意
- expireAfterWrite、refreshAfterWrite的區(qū)別
在refreshAfterWrite導(dǎo)致緩存失效時(shí),并不會(huì)因?yàn)楦戮彺娑枞彺鏀?shù)據(jù)的返回,只不過(guò)是返回老的數(shù)據(jù)
- 不能緩存null
有時(shí)候?yàn)榱藢⒅禐閚ull的數(shù)據(jù)統(tǒng)一緩存,這樣就不會(huì)因?yàn)闆](méi)有緩存數(shù)據(jù)而訪問(wèn)數(shù)據(jù)庫(kù)造成壓力
- 讀寫(xiě)時(shí)才進(jìn)行刪除
Guava Cache的緩存數(shù)據(jù)刪除是在更新或?qū)懭霑r(shí)才會(huì)觸發(fā),沒(méi)有單獨(dú)的調(diào)度服務(wù)完成這一工作
本地緩存
類(lèi)似的本地緩存還有,有興趣的可以自己嘗試,其實(shí)實(shí)現(xiàn)思想應(yīng)該也差不多
-
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
7256瀏覽量
91923 -
服務(wù)器
+關(guān)注
關(guān)注
13文章
9796瀏覽量
88016 -
網(wǎng)絡(luò)
+關(guān)注
關(guān)注
14文章
7815瀏覽量
90985 -
緩存
+關(guān)注
關(guān)注
1文章
246瀏覽量
27196 -
key
+關(guān)注
關(guān)注
0文章
53瀏覽量
13091
發(fā)布評(píng)論請(qǐng)先 登錄
硬盤(pán)緩存
Mybatis緩存之一級(jí)緩存

什么是Web緩存,HTTP緩存和瀏覽器緩存的區(qū)別

緩存的基本原理 緩存的分類(lèi)
緩存如何工作,如何設(shè)計(jì)CPU緩存
到底是更新緩存還是刪緩存

弄懂HTTP緩存機(jī)制及原理
聊聊本地緩存和分布式緩存

mybatis一級(jí)緩存和二級(jí)緩存的原理
Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透要點(diǎn)簡(jiǎn)析

緩存之美——如何選擇合適的本地緩存?

HTTP緩存頭的使用 本地緩存與遠(yuǎn)程緩存的區(qū)別
高性能緩存設(shè)計(jì):如何解決緩存偽共享問(wèn)題

評(píng)論