緩存的定義
緩存(Cache),就是數(shù)據(jù)交換的緩沖區(qū),俗稱的緩存就是緩沖區(qū)內(nèi)的數(shù)據(jù),一般從數(shù)據(jù)庫中獲取,存儲(chǔ)于本地代碼。防止過高的數(shù)據(jù)訪問猛沖系統(tǒng),導(dǎo)致其操作線程無法及時(shí)處理信息而癱瘓,這在實(shí)際開發(fā)中對(duì)企業(yè)講,對(duì)產(chǎn)品口碑,用戶評(píng)價(jià)都是致命的;所以企業(yè)非常重視緩存技術(shù),redis作為最常用的緩存中間件,也是面試的高頻考點(diǎn)。
使用緩存的目的
緩存數(shù)據(jù)存儲(chǔ)于代碼中,而代碼運(yùn)行在內(nèi)存中,內(nèi)存的讀寫性能遠(yuǎn)高于磁盤,緩存可以大大降低用戶訪問并發(fā)量帶來的服務(wù)器讀寫壓力。實(shí)際開發(fā)過程中,企業(yè)的數(shù)據(jù)量,少則幾十萬,多則幾千萬,這么大數(shù)據(jù)量,如果沒有緩存,系統(tǒng)是幾乎撐不住的,所以企業(yè)會(huì)大量運(yùn)用到緩存技術(shù)。
如何使用緩存
實(shí)際開發(fā)中,會(huì)構(gòu)筑多級(jí)緩存來使系統(tǒng)運(yùn)行速度進(jìn)一步提升,例如:本地緩存與redis中的緩存并發(fā)使用
瀏覽器緩存:主要是存在于瀏覽器端的緩存
應(yīng)用層緩存:可以分為tomcat本地緩存,比如之前提到的map,或者是使用redis作為緩存
數(shù)據(jù)庫緩存:在數(shù)據(jù)庫中有一片空間是 buffer pool,增改查數(shù)據(jù)都會(huì)先加載到mysql的緩存中
CPU緩存:當(dāng)代計(jì)算機(jī)最大的問題是 cpu性能提升了,但內(nèi)存讀寫速度沒有跟上,所以為了適應(yīng)當(dāng)下的情況,增加了cpu的L1,L2,L3級(jí)的緩存
緩存商鋪信息
?商鋪信息接口具有很高的并發(fā)量,查詢數(shù)據(jù)不能每次都從數(shù)據(jù)庫查詢,要將商鋪數(shù)據(jù)緩存到redis中,來應(yīng)對(duì)高并發(fā)
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
//這里是直接查詢數(shù)據(jù)庫
return shopService.queryById(id);
}
緩存模型和思路
標(biāo)準(zhǔn)的操作方式就是查詢數(shù)據(jù)庫之前先查詢緩存,如果緩存數(shù)據(jù)存在,則直接從緩存中返回,如果緩存數(shù)據(jù)不存在,再查詢數(shù)據(jù)庫,然后將數(shù)據(jù)存入redis。
?代碼實(shí)現(xiàn)
注意此時(shí)緩存數(shù)據(jù)不設(shè)置過期時(shí)間,為了減輕數(shù)據(jù)庫壓力,緩存應(yīng)該常駐在內(nèi)存中,但也會(huì)帶來一個(gè)那就是緩存數(shù)據(jù)與數(shù)據(jù)庫數(shù)據(jù)不一致的問題,這就引出了緩沖更新策略這一問題
@Override
public Result queryById(Long id) {
//根據(jù)業(yè)務(wù)代碼組裝key
String key = CACHE_SHOP_KEY + id;
//從redis中獲取商鋪信息
String shopJson = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(shopJson)) {
//將json轉(zhuǎn)化為shop對(duì)象直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
Shop shop = getById(id);
if (shop == null) {
return Result.fail("店鋪不存在");
}
//將數(shù)據(jù)庫查詢的數(shù)據(jù)寫入緩存
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
//返回
return Result.ok(shop);
緩存更新策略
更新策略主要根據(jù)業(yè)務(wù)來選擇,在本項(xiàng)目中采用的是主動(dòng)更新+超時(shí)剔除的更新策略,超時(shí)剔除主要是作為保底的更新策略,保證緩存在沒有觸發(fā)主動(dòng)更新的情況下,每隔一段時(shí)間就會(huì)清理緩存
緩存更新是redis為了節(jié)約內(nèi)存而設(shè)計(jì)出來的一個(gè)東西,主要是因?yàn)閮?nèi)存數(shù)據(jù)寶貴,當(dāng)我們向redis插入太多數(shù)據(jù),此時(shí)就可能會(huì)導(dǎo)致緩存中的數(shù)據(jù)過多,所以redis會(huì)對(duì)部分?jǐn)?shù)據(jù)進(jìn)行更新,或者把他叫為淘汰更合適。
內(nèi)存淘汰:redis自動(dòng)進(jìn)行,當(dāng)redis內(nèi)存達(dá)到咱們?cè)O(shè)定的max-memery的時(shí)候,會(huì)自動(dòng)觸發(fā)淘汰機(jī)制,淘汰掉一些不重要的數(shù)據(jù)(可以自己設(shè)置策略方式)
超時(shí)剔除:當(dāng)我們給redis設(shè)置了過期時(shí)間ttl之后,redis會(huì)將超時(shí)的數(shù)據(jù)進(jìn)行刪除,方便咱們繼續(xù)使用緩存
主動(dòng)更新:我們可以手動(dòng)調(diào)用方法把緩存刪掉,通常用于解決緩存和數(shù)據(jù)庫不一致問題
?數(shù)據(jù)庫緩存數(shù)據(jù)不一致解決方案
由于我們的緩存的數(shù)據(jù)源來自于數(shù)據(jù)庫,而數(shù)據(jù)庫的數(shù)據(jù)是會(huì)發(fā)生變化的,因此,如果當(dāng)數(shù)據(jù)庫中數(shù)據(jù)發(fā)生變化,而緩存卻沒有同步,此時(shí)就會(huì)有一致性問題存在,其后果是:
用戶使用緩存中的過時(shí)數(shù)據(jù),就會(huì)產(chǎn)生類似多線程數(shù)據(jù)安全問題,從而影響業(yè)務(wù),產(chǎn)品口碑等;怎么解決呢?有如下幾種方案
Cache Aside Pattern 人工編碼方式:緩存調(diào)用者在更新完數(shù)據(jù)庫后再去更新緩存,也稱之為雙寫方案
Read/Write Through Pattern : 由系統(tǒng)本身完成,數(shù)據(jù)庫與緩存的問題交由系統(tǒng)本身去處理
Write Behind Caching Pattern :調(diào)用者只操作緩存,其他線程去異步處理數(shù)據(jù)庫,實(shí)現(xiàn)最終一致
使用方案二增加了系統(tǒng)復(fù)雜度,不利于調(diào)用者排查有關(guān)問題,方案三會(huì)有一系列線程安全,造成數(shù)據(jù)庫緩存不一致的情況,經(jīng)過綜合考慮選用人工編碼的方式較為穩(wěn)妥
人工編碼步驟
- 刪除緩存:更新數(shù)據(jù)庫時(shí)讓緩存失效,查詢時(shí)再更新緩存,(如果是更新數(shù)據(jù)庫的同時(shí),更新緩存,會(huì)有太多更新動(dòng)作,無法保證性能)
- 在單體系統(tǒng)中,將緩存與數(shù)據(jù)庫操作放在一個(gè)事務(wù),保證更新數(shù)據(jù)庫成功時(shí),緩存也要添加成功,即保證兩個(gè)操作同時(shí)成功或失敗
-
先操作數(shù)據(jù)庫,再刪除緩存,在多線程的情況下,操作數(shù)據(jù)庫的時(shí)間要比操作redis緩存的時(shí)間多得多,出現(xiàn)數(shù)據(jù)庫寫完,緩存失效的可能性較小
實(shí)現(xiàn)商鋪和緩存與數(shù)據(jù)庫雙寫一致
- 根據(jù)id查詢店鋪時(shí),如果緩存未命中,則查詢數(shù)據(jù)庫,將數(shù)據(jù)庫結(jié)果寫入緩存,并設(shè)置超時(shí)時(shí)間
- 根據(jù)id修改店鋪時(shí),先修改數(shù)據(jù)庫,再刪除緩存
添加緩存時(shí)設(shè)置redis緩存時(shí)添加過期時(shí)間文章來源:http://www.zghlxwxcb.cn/news/detail-660129.html
@Override
public Result queryById(Long id) {
//根據(jù)業(yè)務(wù)代碼組裝key
String key = CACHE_SHOP_KEY + id;
//從redis中獲取商鋪信息
String shopJson = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(shopJson)) {
//將json轉(zhuǎn)化為shop對(duì)象直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
Shop shop = getById(id);
if (shop == null) {
return Result.fail("店鋪不存在");
}
//將數(shù)據(jù)庫查詢的數(shù)據(jù)寫入緩存,并設(shè)置過期時(shí)間
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L,TimeUnit.MINUTES);
//返回
return Result.ok(shop);
}
我們確定了采用刪除策略,來解決雙寫問題,當(dāng)我們修改了數(shù)據(jù)之后,然后把緩存中的數(shù)據(jù)進(jìn)行刪除,查詢時(shí)發(fā)現(xiàn)緩存中沒有數(shù)據(jù),則會(huì)從mysql中加載最新的數(shù)據(jù),從而避免數(shù)據(jù)庫和緩存不一致的問題,此方法需要加@Transactional注解來聲明事務(wù)文章來源地址http://www.zghlxwxcb.cn/news/detail-660129.html
@Transactional
@Override
public Result update(Shop shop) {
Long id = shop.getId();
//判斷id是否為空,因?yàn)榭梢岳@過前端直接發(fā)送請(qǐng)求,此步必須判斷
if (id == null) {
return Result.fail("店鋪id不能為空");
}
//更新數(shù)據(jù)庫
updateById(shop);
//刪除緩存
stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
return Result.ok();
}
到了這里,關(guān)于redis實(shí)戰(zhàn)-緩存數(shù)據(jù)&解決緩存與數(shù)據(jù)庫數(shù)據(jù)一致性的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!