Redis緩存策略超詳細(xì)講解
Redis緩存中間件
緩存是什么
所謂緩存就是數(shù)據(jù)交換的緩沖區(qū)(稱作Cache [ kæ? ] ),是一個(gè)臨時(shí)存貯數(shù)據(jù)的地方,一般讀寫性能較高。CPU的運(yùn)算速度要遠(yuǎn)遠(yuǎn)大于內(nèi)存的讀寫速度,這樣會(huì)使CPU花費(fèi)很長(zhǎng)時(shí)間等待數(shù)據(jù)從內(nèi)存的獲取或者寫入,因此緩存的出現(xiàn)主要就是為了解決CPU運(yùn)算速度與內(nèi)存讀寫速度不匹配的矛盾
說了半天緩存和web開發(fā)有什么必要的聯(lián)系嘛?當(dāng)然有,在整個(gè)web開發(fā)的各個(gè)階段都可以使用到不同緩存,比如瀏覽器緩存頁面等靜態(tài)資源,tomcat服務(wù)器應(yīng)用層緩存查詢過的數(shù)據(jù),數(shù)據(jù)庫緩存索引信息等
緩存的優(yōu)點(diǎn)
- 降低后端負(fù)載
- 提高讀寫效率,降低響應(yīng)時(shí)間
緩存的缺點(diǎn)
- 數(shù)據(jù)更新前后緩存區(qū)中該數(shù)據(jù)的一致性難保證
- 解決數(shù)據(jù)一致性需要復(fù)雜的業(yè)務(wù)代碼,提高后續(xù)維護(hù)成本
- 集群模式下提高運(yùn)維成本
Redis緩存已查詢數(shù)據(jù)
在未使用緩存之前,用戶的所有請(qǐng)求都會(huì)直接訪問數(shù)據(jù)庫,但是使用redis作為緩存之后就不一樣了。用戶的請(qǐng)求會(huì)是先在redis中查找,如果查到也就是命中的話就直接返回客戶端,如果未命中的話就去數(shù)據(jù)庫中查找,查到有結(jié)果就將查詢到的結(jié)果寫入redis中,然后返回給客戶端;未查到結(jié)果就返回404狀態(tài)碼
redis緩存中間件實(shí)踐
黑馬點(diǎn)評(píng)中有這么一個(gè)業(yè)務(wù):點(diǎn)擊商鋪圖片會(huì)通過id查詢?cè)撋啼伒南嚓P(guān)信息,如果使用redis緩存的話,后期再訪問該商鋪的話就會(huì)直接到redis中查詢,可以大大縮短查詢所需時(shí)間
collector中定義與前端交互的方法,前端請(qǐng)求/shop-type/list?id=xx
@RestController @RequestMapping("/shop") public class ShopController { @Resource public IShopService shopService; /** * 根據(jù)id查詢商鋪信息 * @param id 商鋪id * @return 商鋪詳情數(shù)據(jù) */ @GetMapping("/{id}") public Result queryShopById(@PathVariable("id") Long id) { return shopService.queryById(id); } }
編寫typeService里業(yè)務(wù)邏輯方法getList的接口和實(shí)現(xiàn)類,邏輯參考Redis緩存已查詢數(shù)據(jù)的相關(guān)分析
@Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public Result queryById(Long id) { // 從redis查詢商鋪緩存 String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id); // 判斷該商鋪是否存在 if (StrUtil.isNotBlank(shopJson)) { // 存在直接返回 Shop shop = JSONUtil.toBean(shopJson, Shop.class); return Result.ok(shop); } // 不存在查詢數(shù)據(jù)庫 Shop shop = getById(id); if (shop == null) { // 數(shù)據(jù)庫中不存在直接返回錯(cuò)誤信息 return Result.fail("店鋪不存在"); } // 數(shù)據(jù)庫中存在寫入redis stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop)); // 返回 return Result.ok(shop); } }
經(jīng)實(shí)驗(yàn)驗(yàn)證得知,使用redis緩存未命中時(shí)查詢耗時(shí)將近200毫秒,后續(xù)查詢命中之后只需幾毫秒,可見redis作為緩存中間件對(duì)數(shù)據(jù)讀取的功效還是很高的
緩存更新
之前介紹redis的時(shí)候介紹過redis緩存的一些缺點(diǎn),比如數(shù)據(jù)庫中數(shù)據(jù)更新前后緩存區(qū)中該數(shù)據(jù)的一致性難保證,該怎么應(yīng)對(duì)redis緩存的這個(gè)缺點(diǎn)呢?這就引出接下來的學(xué)習(xí)內(nèi)容——緩存更新策略
緩存更新的三個(gè)策略
內(nèi)存淘汰: redis底層的內(nèi)存淘汰機(jī)制,無需我們自己維護(hù),當(dāng)內(nèi)存不足時(shí)自動(dòng)淘汰部分?jǐn)?shù)據(jù),下次查詢時(shí)更新緩存。這種機(jī)制的優(yōu)點(diǎn)是維護(hù)成本極低,但是缺點(diǎn)也很明顯,由于淘汰數(shù)據(jù)的不確定性導(dǎo)致很難保證數(shù)據(jù)的一致性
超時(shí)剔除: 向redis中添加緩存數(shù)據(jù)的時(shí)候設(shè)置TTL時(shí)間,到期后自動(dòng)刪除緩存,下次查詢時(shí)更新緩存。這種機(jī)制維護(hù)成本不是很高,但是數(shù)據(jù)一致性同樣無法做到很高的保證,因?yàn)樵O(shè)置之后數(shù)據(jù)的有效期就固定了,但是更新時(shí)間不固定,若是數(shù)據(jù)在超時(shí)剔除之前發(fā)生更新然后查詢,得到的仍是更新之前的數(shù)據(jù)
主動(dòng)更新: 使用代碼在修改數(shù)據(jù)庫的同時(shí)更新緩存。這種策略能夠保證很高的數(shù)據(jù)一致性,但是伴隨而來的就是更高的維護(hù)成本,要在每一個(gè)更改語句后面加上redis緩存更新
具體使用哪種策略取決于該業(yè)務(wù)對(duì)數(shù)據(jù)一致性的需求:一致性需求不高的話,可以使用內(nèi)存淘汰策略。一致性需求較高的話,可以使用主動(dòng)更新加上超時(shí)剔除策略,保證了較高的一致性
主動(dòng)更新策略的三種方案
代碼(Cache Aside Pattern):最直接的一種方案,使用代碼在修改數(shù)據(jù)庫的同時(shí)更新緩存
服務(wù)(Read/Warite Through Pattern):將redis緩存與數(shù)據(jù)庫整合為一個(gè)服務(wù),由這個(gè)服務(wù)來維護(hù)數(shù)據(jù)的一致性,在更新數(shù)據(jù)庫時(shí)只需要調(diào)用該服務(wù)即可,無需關(guān)心服務(wù)底層的業(yè)務(wù)邏輯,類似于封裝。但是市面上沒有現(xiàn)成的服務(wù)可以使用,自己封裝這么一個(gè)服務(wù)也很復(fù)雜,所以說這種方案可用性很差
寫回(Write Behind Caching Pattern):所有數(shù)據(jù)庫的CRUD操作都在redis緩存中完成,由另外一個(gè)獨(dú)立的線程異步的將緩存中的數(shù)據(jù)持久化到數(shù)據(jù)庫中,以此來保證數(shù)據(jù)的最終一致。這種方案有個(gè)很大的好處,那就是極大地減少了對(duì)數(shù)據(jù)庫的操作,如果主線程在另一個(gè)線程兩次持久化之間對(duì)redis中的數(shù)據(jù)操作多次,數(shù)據(jù)庫中只會(huì)執(zhí)行最后一次操作,而不是也操作多次。但是也有壞處,那就是如果還沒等到另一個(gè)線程持久化數(shù)據(jù)庫,此時(shí)redis緩存發(fā)生宕機(jī),緩存大多數(shù)在內(nèi)存中,此時(shí)發(fā)生宕機(jī)就會(huì)導(dǎo)致緩存中的數(shù)據(jù)消失,數(shù)據(jù)庫中的數(shù)據(jù)就與宕機(jī)前redis中的數(shù)據(jù)不一致
綜上所述,雖然Cache Aside Pattern方案是最復(fù)雜的一個(gè),但是他也同樣是最可靠的一個(gè),于是我們選擇它來進(jìn)行接下來的代碼學(xué)習(xí)
主動(dòng)更新策略注意項(xiàng)
數(shù)據(jù)庫發(fā)生更新的時(shí)候直接刪除緩存中的該數(shù)據(jù),而不是跟著更新緩存,因?yàn)槿绻l(fā)生連續(xù)修改多次的情況,更新緩存的話更新次數(shù)等于數(shù)據(jù)庫的更新次數(shù);如果是刪除緩存數(shù)據(jù)的話就只需要?jiǎng)h除一次,下一次查詢直接從數(shù)據(jù)庫中查詢?cè)賹懭刖彺妗?/p>
刪除緩存數(shù)據(jù)和數(shù)據(jù)庫操作應(yīng)該保證原子性,也就是說刪除緩存數(shù)據(jù)操作和數(shù)據(jù)庫操作應(yīng)該同時(shí)成功或者同時(shí)失敗,那么該如何實(shí)現(xiàn)呢?單體式系統(tǒng)中,可以通過將兩個(gè)操作放在一個(gè)事務(wù)中來完成;分布式系統(tǒng)中可以利用TCC等分布式事務(wù)方案來實(shí)現(xiàn)
刪除緩存數(shù)據(jù)操作和數(shù)據(jù)庫操作的先后順序是什么? 應(yīng)該是先寫數(shù)據(jù)庫再刪除緩存,原因是這種方式發(fā)生線程安全性問題的可能較小
主動(dòng)更新的代碼實(shí)現(xiàn)
controller層前端交互
/** * 更新商鋪信息 * @param shop 商鋪數(shù)據(jù) * @return 無 */ @PutMapping public Result updateShop(@RequestBody Shop shop) { // 寫入數(shù)據(jù)庫 return shopService.update(shop); }
需要server的update方法,創(chuàng)建接口和實(shí)現(xiàn)類完成業(yè)務(wù)邏輯代碼編寫。主動(dòng)更新+超時(shí)剔除的策略就只有兩步,那就是在寫緩存的時(shí)候設(shè)置超時(shí)時(shí)間,更新數(shù)據(jù)庫之后刪除緩存
// 數(shù)據(jù)庫中存在寫入redis的時(shí)候設(shè)置超時(shí)時(shí)間 stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES); /** * 更新商鋪信息 * @param shop 商鋪信息 * @return 前端返回?cái)?shù)據(jù) */ @Override @Transactional public Result update(Shop shop) { if (shop.getId() == null) { return Result.fail("店鋪id不能為空"); } // 更新數(shù)據(jù)庫 updateById(shop); // 刪除緩存 stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + shop.getId()); // 返回 return Result.ok(); }
到此這篇關(guān)于Redis緩存策略超詳細(xì)講解的文章就介紹到這了,更多相關(guān)Redis緩存策略內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中的位運(yùn)算符、移位運(yùn)算詳細(xì)介紹
這篇文章主要介紹了Java中的位運(yùn)算符、移位運(yùn)算,有需要的朋友可以參考一下2013-12-12SpringBoot實(shí)現(xiàn)Mysql使用MD5進(jìn)行密碼加密的示例
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)Mysql使用MD5進(jìn)行密碼加密的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04java整數(shù)與byte數(shù)組的轉(zhuǎn)換實(shí)現(xiàn)代碼
這篇文章主要介紹了java整數(shù)與byte數(shù)組的轉(zhuǎn)換實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-07-07javaweb圖書商城設(shè)計(jì)之用戶模塊(1)
這篇文章主要介紹了javaweb圖書商城設(shè)計(jì)之用戶模塊的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11springboot配置請(qǐng)求超時(shí)時(shí)間(Http會(huì)話和接口訪問)
本文主要介紹了springboot配置請(qǐng)求超時(shí)時(shí)間,包含Http會(huì)話和接口訪問兩種,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07spring事務(wù)Propagation及其實(shí)現(xiàn)原理詳解
這篇文章主要介紹了spring事務(wù)Propagation及其實(shí)現(xiàn)原理詳解,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02SpringBoot MockMvc單元測(cè)試的示例代碼
這篇文章主要介紹了SpringBoot MockMvc單元測(cè)試的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11