springboot項(xiàng)目redis緩存異常實(shí)戰(zhàn)案例詳解(提供解決方案)
緩存異常實(shí)踐案例
redis基本上是高并發(fā)場(chǎng)景上會(huì)用到的一個(gè)高性能的key-value數(shù)據(jù)庫,屬于nosql類型,一般用作于緩存,一般是結(jié)合數(shù)據(jù)庫一塊使用的,但是在使用的過程中可能會(huì)出現(xiàn)異常的問題,就是面試常常嘮嗑的緩存異常問題
分別是緩存擊穿,緩存穿透和雪崩,簡單解釋如下:
緩存穿透:
就是當(dāng)用戶查詢數(shù)據(jù)時(shí),緩存和數(shù)據(jù)庫該數(shù)據(jù)都是不存在的,此時(shí)如果用戶不斷的請(qǐng)求,就會(huì)不斷的查詢緩存和數(shù)據(jù)庫,對(duì)數(shù)據(jù)庫造成很大壓力
緩存擊穿:
當(dāng)熱點(diǎn)key過期或者丟失時(shí),大量的請(qǐng)求訪問該數(shù)據(jù),緩存不存在,就會(huì)將大量的請(qǐng)求直接訪問數(shù)據(jù)庫,造成數(shù)據(jù)庫有大量連接,存在崩潰風(fēng)險(xiǎn)
緩存雪崩:
是指大量請(qǐng)求在緩存中沒有查到數(shù)據(jù),直接訪問數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫壓力增大,最終導(dǎo)致數(shù)據(jù)庫崩潰,從而波及整個(gè)系統(tǒng)不可用,好像雪崩一樣。
下面就講講案例,并提供解決方案
常規(guī)寫法
平常的寫法,未考慮異常時(shí)
現(xiàn)在是有一個(gè)查詢商戶的接口
這個(gè)是正常的,結(jié)合了redis的邏輯,
public TbShopEntity rawQuery(Long id) { TbShopEntity shop = null; //1.是否命中緩存 String shopJson = redisClient.get(CACHE_SHOP_KEY + id); //1.1 如果命中,且數(shù)據(jù)非空,則直接返回 if (!StrUtil.isEmpty(shopJson)) { return JSONObject.parseObject(shopJson, TbShopEntity.class); } //1.2 查詢數(shù)據(jù)庫 shop = getById(id); //1.2.1 如果數(shù)據(jù)庫不存在,則直接返回 if (shop == null) return null; //1.2.2 如果存在則設(shè)置到redis中 redisClient.set(CACHE_SHOP_KEY + id, JSON.toJSONString(shop)); //2.返回?cái)?shù)據(jù) return shop; }
現(xiàn)在使用一個(gè)不存在的商品id,然后使用jmeter進(jìn)行壓測(cè),例如使用id=0的商品,然后使用200個(gè)線程共發(fā)起200個(gè)請(qǐng)求進(jìn)行壓測(cè)
結(jié)果:
發(fā)現(xiàn)有大量的請(qǐng)求直接請(qǐng)求數(shù)據(jù)庫,如果時(shí)大量的請(qǐng)求,有可能會(huì)把數(shù)據(jù)庫搞崩,也就是我們的緩存穿透問題
緩存穿透問題
分析:
如果是這種情況,當(dāng)用戶并發(fā)測(cè)試訪問一個(gè)不存在的key時(shí),會(huì)有大量的不存在的key訪問數(shù)據(jù),導(dǎo)致數(shù)據(jù)庫壓力劇增,也就是緩存穿透問題
改進(jìn)方式
緩存穿透一般有兩種解決方式,分別是:
- 設(shè)置帶有過期時(shí)間的空值
- 布隆過濾器
設(shè)置帶有過期時(shí)間的空值
邏輯圖如下:主要是改進(jìn)這里
/** * 緩存穿透解決方案, 1、設(shè)置一個(gè)空值,且?guī)в休^短的過期時(shí)間 2、布隆過濾器 * <p> * 但是目前還是沒有解決穿透的問題的,因?yàn)樵搆ey不存在,所以會(huì)有多個(gè)請(qǐng)求去直接請(qǐng)求數(shù)據(jù)庫(并發(fā)問題),從而需要進(jìn)行優(yōu)化,解決緩存穿透問題 */ private TbShopEntity queryWithPassThrough(Long id) { //1.是否命中緩存 String shopJson = redisClient.get(CACHE_SHOP_KEY + id); //1.1 如果命中緩存,則直接返回 //這里不能使用!Strutil.isEmpty去判斷,因?yàn)樵谙逻厱?huì)使用”“去存空值 if (shopJson != null) { //如果返回的值是"",則直接返回null if (StrUtil.isEmpty(shopJson)) return null; //否則則返回結(jié)果 return JSONObject.parseObject(shopJson, TbShopEntity.class); } //1.2 如果沒有命中,查詢數(shù)據(jù)庫 TbShopEntity shop = getById(id); //1.2.1 如果數(shù)據(jù)庫查詢數(shù)據(jù)不存在,則直設(shè)置值為空,以及過期時(shí)間,現(xiàn)在是設(shè)置為10s,如果10s已經(jīng)過,再重新查詢 if (shop == null) { redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, StrUtil.EMPTY, SECKILL_SECONDS, TimeUnit.SECONDS); return null; } //1.2.2 如果數(shù)據(jù)庫存在,則設(shè)置到redis中 redisClient.set(CACHE_SHOP_KEY + id, JSON.toJSONString(shop)); //并轉(zhuǎn)成shop返回 //2.返回?cái)?shù)據(jù) return JSONObject.parseObject(shopJson, TbShopEntity.class); }
測(cè)試一:直接調(diào)用接口
之后進(jìn)行測(cè)試,首先是使用鏈接直接訪問一個(gè)不存在的商品
可以發(fā)現(xiàn),在第一次訪問的時(shí)候,會(huì)去查找數(shù)據(jù)庫,然后在之后的10s內(nèi),都不會(huì)進(jìn)行數(shù)據(jù)庫的查詢了,然后當(dāng)數(shù)據(jù)過期之后,才會(huì)進(jìn)行查詢,從這個(gè)結(jié)果來看,是沒有問題的,也就是平常我們可以這樣子使用的!
測(cè)試二:使用并發(fā)測(cè)試進(jìn)行
但是如果使用200個(gè)并發(fā)去測(cè)試,結(jié)果又是如何呢?
可以發(fā)現(xiàn),還是有大部分請(qǐng)求直接去查詢數(shù)據(jù)庫,
原因是由于線程的并發(fā)問題,大部分請(qǐng)求都執(zhí)行到這一步,這個(gè)時(shí)候redis的數(shù)據(jù)還沒有補(bǔ)充上去,所以導(dǎo)致了大量請(qǐng)求還是重新去查數(shù)據(jù)庫,其實(shí)也類似于緩存穿透問題,當(dāng)某個(gè)key失效時(shí),大量請(qǐng)求會(huì)去查詢數(shù)據(jù)庫,那么這個(gè)問題應(yīng)該如何解決呢?
緩存擊穿問題(其中也解決了穿透問題)
當(dāng)有一個(gè)訪問量較高的key,在失效時(shí),會(huì)導(dǎo)致大量的請(qǐng)求發(fā)向數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫崩潰
解決方式主要有:
- 加互斥鎖
- 邏輯過期
加互斥鎖
public TbShopEntity queryWithMutexLock(Long id) { TbShopEntity shop = null; //是否命中緩存 String shopJson = redisClient.get(CACHE_SHOP_KEY + id); //如果命中緩存,則先判斷緩存是否為”“,如果是返回null,否則返回結(jié)果 if (shopJson != null) { //如果緩存為空的話,則直接返回結(jié)果 if (StrUtil.isEmpty(shopJson)) return null; return JSONObject.parseObject(shopJson, TbShopEntity.class); } //獲取鎖,鎖的粒度需要精確到id,不能太大 RLock lock = redissonClient.getLock(LOCK_SHOP + id); try { //加鎖 boolean isLock = lock.tryLock(10,TimeUnit.SECONDS); //如果沒有獲取到鎖,則休眠50ms,然后重試 if (!isLock) { Thread.sleep(50); queryWithMutexLock(id); } //這里需要做doubleCheck,需要重新查詢緩存的數(shù)據(jù)是否存在,否則還會(huì)出現(xiàn)重復(fù)查詢數(shù)據(jù)庫的情況 shopJson = redisClient.get(CACHE_SHOP_KEY + id); if (shopJson != null) { if (StrUtil.isEmpty(shopJson)) return null; return JSONObject.parseObject(shopJson, TbShopEntity.class); } //如果緩存不存在,則查詢數(shù)據(jù)庫 shop = getById(id); //如果數(shù)據(jù)庫查詢數(shù)據(jù)不存在,則直設(shè)置值為空,以及過期時(shí)間,直接返回null if (shop == null) { redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, StrUtil.EMPTY, SECKILL_SECONDS, TimeUnit.SECONDS); return null; } //如果數(shù)據(jù)庫數(shù)據(jù)存在則設(shè)置到redis中 redisClient.set(CACHE_SHOP_KEY + id, JSON.toJSONString(shop)); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } //2.返回?cái)?shù)據(jù) return shop; }
壓測(cè):
使用200個(gè)線程測(cè)試,結(jié)果是只查詢了一次,是ok的
邏輯過期
邏輯過期的原理,指的是不使用redis自帶的expire進(jìn)行存儲(chǔ),而是在存儲(chǔ)的數(shù)據(jù)中,添加一個(gè)過期字段,然后在獲取數(shù)據(jù)的時(shí)候,進(jìn)行該字段的判斷,如果已經(jīng)過期了,則返回舊的數(shù)據(jù),啟動(dòng)一個(gè)線程去更新新的數(shù)據(jù)
數(shù)據(jù)結(jié)構(gòu)如下:
data用來存儲(chǔ)數(shù)據(jù),expired存儲(chǔ)過期時(shí)間,所以我們只要比較,如果expired小于當(dāng)前時(shí)間的話,就代表該數(shù)據(jù)是過期的了
邏輯圖如下:
這個(gè)邏輯圖稍微比較復(fù)雜,基本將空值和互斥鎖都加進(jìn)去了
queryWithLogicExpire
public TbShopEntity queryWithLogicExpire(Long id) { TbShopEntity shop = null; //查詢是否命中緩存 String shopJson = redisClient.get(CACHE_SHOP_KEY + id); //如果命中,則判斷結(jié)果是空置,還是過期的值,或者沒過期的值 if (shopJson != null) { //這里的代碼往下滑查看 return redisDTO2Entity(id, shopJson); } //獲取鎖,粒度具體到商戶 RLock lock = redissonClient.getLock("lock:shop:" + id); try { //加鎖,10s過期 boolean isLock = lock.tryLock(10,TimeUnit.SECONDS); // 如果沒有獲取到鎖,則休眠50ms,然后重試 if (!isLock) { Thread.sleep(50); queryWithLogicExpire(id); } //這里需要做doubleCheck,否則還會(huì)出現(xiàn)重復(fù)查詢數(shù)據(jù)庫的情況 //這里先不做判斷是否邏輯過期的邏輯 shopJson = redisClient.get(CACHE_SHOP_KEY + id); if (shopJson != null) { redisDTO2Entity(id,shopJson); } shop = getById(id); //1.2 如果數(shù)據(jù)庫查詢數(shù)據(jù)不存在,則直設(shè)置值為空,以及過期時(shí)間,直接返回null if (shop == null) { redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", SECKILL_SECONDS, TimeUnit.MINUTES); return null; } //1.3 如果存在則設(shè)置到redis中 redisClient.set(CACHE_SHOP_KEY + id, JSON.toJSONString(shop)); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } //2.返回?cái)?shù)據(jù) return shop; }
redisDTO2Entity
private TbShopEntity redisDTO2Entity(Long id,String shopJson){ //如果是空值,則直接返回null if (StrUtil.isEmpty(shopJson)) return null; //或者則轉(zhuǎn)換成redis實(shí)體類 RedisDTO redisDTO = JSONObject.parseObject(shopJson, RedisDTO.class); //獲取邏輯過期時(shí)間 LocalDateTime expired = redisDTO.getExpired(); // 判斷時(shí)間是否過期,如果過期,則啟動(dòng)線程更新數(shù)據(jù),其他直接返回 if (expired.isBefore(LocalDateTime.now())) { //使用異步線程,更新數(shù)據(jù) saveShop(id); } return JSONObject.parseObject(JSON.toJSONString(redisDTO.getData()), TbShopEntity.class); }
使用異步線程進(jìn)行數(shù)據(jù)的更新
saveShop
@Async public void saveShop(Long id){ TbShopEntity entity = getById(id); if(entity==null) return; redisClient.setLogicExpired(RedisConstant.CACHE_SHOP_KEY+id,entity,RedisConstant.SECKILL_SECONDS, TimeUnit.SECONDS); log.info("線程{},更新商戶信息",Thread.currentThread().getName()); }
測(cè)試
需要先添加一條數(shù)據(jù)
執(zhí)行下面的test方法,進(jìn)行添加數(shù)據(jù),添加成功后
數(shù)據(jù)如下:
package com.walker.dianping; import com.walker.dianping.common.constants.RedisConstant; import com.walker.dianping.common.utils.RedisClient; import com.walker.dianping.model.TbShopEntity; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.concurrent.TimeUnit; @SpringBootTest public class RedisTest { @Autowired private RedisClient redisClient; @Test void addShop() { TbShopEntity tbShopEntity = new TbShopEntity(); tbShopEntity.setId(1L); tbShopEntity.setName("邏輯過期測(cè)試商戶"); //該方法可以查看大綱,放在了完整代碼中 redisClient.setLogicExpired(RedisConstant.CACHE_SHOP_KEY + 1L, tbShopEntity, 20, TimeUnit.MINUTES); } }
調(diào)用接口發(fā)起測(cè)試
因?yàn)橐婚_始是有的,所以可以直接拿到數(shù)據(jù)
當(dāng)過期時(shí)間expired小于當(dāng)前時(shí)間時(shí),這個(gè)時(shí)候重新去調(diào)用接口(可以直接更改redis的數(shù)據(jù))
這個(gè)時(shí)候就會(huì)去更新商戶的數(shù)據(jù)了,這個(gè)時(shí)候就能拿到新的數(shù)據(jù)了
完整代碼
controller
package com.walker.dianping.controller; import com.walker.dianping.model.R; import com.walker.dianping.service.TbShopService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> * 前端控制器 * </p> * * @author walker * @since 2023-01-18 */ @RestController @RequestMapping("/tb-shop-entity") public class TbShopController { @Autowired private TbShopService tbShopService; /** * 獲取商鋪 */ @GetMapping("/shop/{id}") public R getShop(@PathVariable(value = "id") Long id){ return tbShopService.getShop(id); } }
service
package com.walker.dianping.service; import com.baomidou.mybatisplus.extension.service.IService; import com.walker.dianping.model.R; import com.walker.dianping.model.TbShopEntity; /** * <p> * 服務(wù)類 * </p> * * @author walker * @since 2023-01-18 */ public interface TbShopService extends IService<TbShopEntity> { R getShop(Long id); }
TbShopServiceImpl
package com.walker.dianping.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.walker.dianping.common.constants.RedisConstant; import com.walker.dianping.common.utils.RedisClient; import com.walker.dianping.mapper.TbShopMapper; import com.walker.dianping.model.R; import com.walker.dianping.model.TbShopEntity; import com.walker.dianping.model.dto.RedisDTO; import com.walker.dianping.service.TbShopService; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.concurrent.TimeUnit; import static com.walker.dianping.common.constants.RedisConstant.*; @Slf4j @Service public class TbShopServiceImpl extends ServiceImpl<TbShopMapper, TbShopEntity> implements TbShopService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private RedisClient redisClient; @Autowired private RedissonClient redissonClient; @Override public R getShop(Long id) { //初始查詢 // TbShopEntity shop = rawQuery(id); //緩存穿透 // TbShopEntity shop = queryWithPassThrough(id); //解決緩存擊穿問題 // TbShopEntity shop = queryWithMutexLock(id); //解決緩存擊穿+穿透問題,使用邏輯過期 TbShopEntity shop = queryWithLogicExpire(id); if (shop == null) { return R.fail("店鋪不存在"); } return R.ok(shop); } /** * 最初始的版本 */ public TbShopEntity rawQuery(Long id) { TbShopEntity shop = null; //1.是否命中緩存 String shopJson = redisClient.get(CACHE_SHOP_KEY + id); //1.1 如果命中,且數(shù)據(jù)非空,則直接返回 if (!StrUtil.isEmpty(shopJson)) { return JSONObject.parseObject(shopJson, TbShopEntity.class); } //1.2 查詢數(shù)據(jù)庫 shop = getById(id); //1.2.1 如果數(shù)據(jù)庫不存在,則直接返回 if (shop == null) return null; //1.2.2 如果存在則設(shè)置到redis中 redisClient.set(CACHE_SHOP_KEY + id, JSON.toJSONString(shop)); //2.返回?cái)?shù)據(jù) return shop; } /** * 緩存穿透解決方案, 1、設(shè)置一個(gè)空值,且?guī)в休^短的過期時(shí)間 2、布隆過濾器 * <p> * 但是目前還是沒有解決穿透的問題的,因?yàn)樵搆ey不存在,所以會(huì)有多個(gè)請(qǐng)求去直接請(qǐng)求數(shù)據(jù)庫(并發(fā)問題),從而需要進(jìn)行優(yōu)化,解決緩存穿透問題 */ private TbShopEntity queryWithPassThrough(Long id) { //1.是否命中緩存 String shopJson = redisClient.get(CACHE_SHOP_KEY + id); //1.1 如果命中緩存,則直接返回 //這里不能使用!Strutil.isEmpty去判斷,因?yàn)樵谙逻厱?huì)使用”“去存空值 if (shopJson != null) { //如果返回的值是"",則直接返回null if (StrUtil.isEmpty(shopJson)) return null; //否則則返回結(jié)果 return JSONObject.parseObject(shopJson, TbShopEntity.class); } //1.2 如果沒有命中,查詢數(shù)據(jù)庫 TbShopEntity shop = getById(id); //1.2.1 如果數(shù)據(jù)庫查詢數(shù)據(jù)不存在,則直設(shè)置值為空,以及過期時(shí)間,現(xiàn)在是設(shè)置為10s,如果10s已經(jīng)過,再重新查詢 if (shop == null) { redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, StrUtil.EMPTY, SECKILL_SECONDS, TimeUnit.SECONDS); return null; } //1.2.2 如果數(shù)據(jù)庫存在,則設(shè)置到redis中 redisClient.set(CACHE_SHOP_KEY + id, JSON.toJSONString(shop)); //并轉(zhuǎn)成shop返回 //2.返回?cái)?shù)據(jù) return JSONObject.parseObject(shopJson, TbShopEntity.class); } /** * 緩存擊穿,key失效,導(dǎo)致高并發(fā)的請(qǐng)求 * 加鎖:可以使用redisson */ public TbShopEntity queryWithMutexLock(Long id) { TbShopEntity shop = null; //是否命中緩存 String shopJson = redisClient.get(CACHE_SHOP_KEY + id); //如果命中緩存,則先判斷緩存是否為”“,如果是返回null,否則返回結(jié)果 if (shopJson != null) { //如果緩存為空的話,則直接返回結(jié)果 if (StrUtil.isEmpty(shopJson)) return null; return JSONObject.parseObject(shopJson, TbShopEntity.class); } //獲取鎖,鎖的粒度需要精確到id,不能太大 RLock lock = redissonClient.getLock(LOCK_SHOP + id); try { //加鎖 boolean isLock = lock.tryLock(10,TimeUnit.SECONDS); //如果沒有獲取到鎖,則休眠50ms,然后重試 if (!isLock) { Thread.sleep(50); queryWithMutexLock(id); } //這里需要做doubleCheck,需要重新查詢緩存的數(shù)據(jù)是否存在,否則還會(huì)出現(xiàn)重復(fù)查詢數(shù)據(jù)庫的情況 shopJson = redisClient.get(CACHE_SHOP_KEY + id); if (shopJson != null) { if (StrUtil.isEmpty(shopJson)) return null; return JSONObject.parseObject(shopJson, TbShopEntity.class); } //如果緩存不存在,則查詢數(shù)據(jù)庫 shop = getById(id); //如果數(shù)據(jù)庫查詢數(shù)據(jù)不存在,則直設(shè)置值為空,以及過期時(shí)間,直接返回null if (shop == null) { redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, StrUtil.EMPTY, SECKILL_SECONDS, TimeUnit.SECONDS); return null; } //如果數(shù)據(jù)庫數(shù)據(jù)存在則設(shè)置到redis中 redisClient.set(CACHE_SHOP_KEY + id, JSON.toJSONString(shop)); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } //2.返回?cái)?shù)據(jù) return shop; } private TbShopEntity redisDTO2Entity(Long id,String shopJson){ //如果是空值,則直接返回null if (StrUtil.isEmpty(shopJson)) return null; //或者則轉(zhuǎn)換成redis實(shí)體類 RedisDTO redisDTO = JSONObject.parseObject(shopJson, RedisDTO.class); //獲取邏輯過期時(shí)間 LocalDateTime expired = redisDTO.getExpired(); // 判斷時(shí)間是否過期,如果過期,則啟動(dòng)線程更新數(shù)據(jù),其他直接返回 if (expired.isBefore(LocalDateTime.now())) { //使用異步線程,更新數(shù)據(jù) saveShop(id); } return JSONObject.parseObject(JSON.toJSONString(redisDTO.getData()), TbShopEntity.class); } /** * 緩存擊穿解決方式二:邏輯過期 */ public TbShopEntity queryWithLogicExpire(Long id) { TbShopEntity shop = null; //查詢是否命中緩存 String shopJson = redisClient.get(CACHE_SHOP_KEY + id); //如果命中,則判斷結(jié)果是空置,還是過期的值,或者沒過期的值 if (shopJson != null) { return redisDTO2Entity(id, shopJson); } //獲取鎖,粒度具體到商戶 RLock lock = redissonClient.getLock("lock:shop:" + id); try { //加鎖,10s過期 boolean isLock = lock.tryLock(10,TimeUnit.SECONDS); // 如果沒有獲取到鎖,則休眠50ms,然后重試 if (!isLock) { Thread.sleep(50); queryWithLogicExpire(id); } //這里需要做doubleCheck,否則還會(huì)出現(xiàn)重復(fù)查詢數(shù)據(jù)庫的情況 //這里先不做判斷是否邏輯過期的邏輯 shopJson = redisClient.get(CACHE_SHOP_KEY + id); if (shopJson != null) { redisDTO2Entity(id,shopJson); } shop = getById(id); //1.2 如果數(shù)據(jù)庫查詢數(shù)據(jù)不存在,則直設(shè)置值為空,以及過期時(shí)間,直接返回null if (shop == null) { redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", SECKILL_SECONDS, TimeUnit.MINUTES); return null; } //1.3 如果存在則設(shè)置到redis中 redisClient.set(CACHE_SHOP_KEY + id, JSON.toJSONString(shop)); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } //2.返回?cái)?shù)據(jù) return shop; } @Async public void saveShop(Long id) { //查詢數(shù)據(jù) TbShopEntity entity = getById(id); if (entity == null) return; redisClient.setLogicExpired(RedisConstant.CACHE_SHOP_KEY + id, entity, RedisConstant.SECKILL_SECONDS, TimeUnit.SECONDS); log.info("線程{},更新商戶信息", Thread.currentThread().getName()); } }
RedisClient代碼
package com.walker.dianping.common.utils; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.walker.dianping.model.dto.RedisDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.convert.RedisData; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.concurrent.TimeUnit; @Slf4j @Component public class RedisClient { @Autowired private StringRedisTemplate redisTemplate; /** * 定義set方法 */ public void set(String key,Object value){ redisTemplate.opsForValue().set(key, JSON.toJSONString(value)); } /** * 定義set方法 */ public void setLogicExpired(String key,Object value,long timeout, TimeUnit unit){ RedisDTO dto = new RedisDTO(); dto.setData(value); dto.setExpired(LocalDateTime.now().plusSeconds(unit.toSeconds(timeout))); redisTemplate.opsForValue().set(key, JSON.toJSONString(dto)); } /** * get方法 */ public String get(String key){ String s = redisTemplate.opsForValue().get(key); return s; } }
RedisConstant
package com.walker.dianping.common.constants; public interface RedisConstant { String SECKILL_LUA_SCRIPT="seckill.lua"; String CACHE_SHOP_KEY= "cache:shop:"; Integer SECKILL_SECONDS=10; String LOCK_SHOP="lock:shop:"; }
到此這篇關(guān)于springboot項(xiàng)目redis緩存異常實(shí)戰(zhàn)案例詳解(提供解決方案)的文章就介紹到這了,更多相關(guān)springboot redis緩存異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+Redis實(shí)現(xiàn)分布式緩存的方法步驟
- springBoot整合redis做緩存具體操作步驟
- SpringBoot3.0集成Redis緩存的實(shí)現(xiàn)示例
- SpringBoot整合Redis實(shí)現(xiàn)token緩存
- SpringBoot結(jié)合Redis實(shí)現(xiàn)緩存管理功能
- SpringBoot整合MP通過Redis實(shí)現(xiàn)二級(jí)緩存方式
- SpringBoot中Redis的緩存更新策略詳解
- SpringBoot整合Redis實(shí)現(xiàn)緩存分頁數(shù)據(jù)查詢功能
- SpringBoot使用Redis實(shí)現(xiàn)分布式緩存
相關(guān)文章
redis適合場(chǎng)景八點(diǎn)總結(jié)
在本篇文章中我們給大家整理了關(guān)于redis適合什么場(chǎng)景的8點(diǎn)知識(shí)點(diǎn)內(nèi)容,需要的朋友們參考下。2019-06-06redis集群實(shí)現(xiàn)清理前綴相同的key
這篇文章主要介紹了redis集群實(shí)現(xiàn)清理前綴相同的key,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Redis實(shí)現(xiàn)數(shù)據(jù)的交集、并集、補(bǔ)集的示例
本文主要介紹了Redis實(shí)現(xiàn)數(shù)據(jù)的交集、并集、補(bǔ)集的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Redis序列化反序列化不一致導(dǎo)致String類型值多了雙引號(hào)問題
這篇文章主要介紹了Redis序列化反序列化不一致導(dǎo)致String類型值多了雙引號(hào)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Redis實(shí)現(xiàn)分布式事務(wù)的示例
Redis雖不支持傳統(tǒng)SQL數(shù)據(jù)庫ACID特性的事務(wù),但提供了事務(wù)特性,允許多命令捆綁執(zhí)行,通過命令MULTI、EXEC、DISCARD、WATCH實(shí)現(xiàn),感興趣的可以了解一下2024-10-10