Redis緩存工具封裝實(shí)現(xiàn)
將 StringRedisTemplate 封裝成一個(gè)緩存工具類,方便以后重復(fù)使用。
1. 方法要求
在這個(gè)工具類中我們完成四個(gè)方法:
- 方法①:將任意Java對(duì)象序列化為json并存儲(chǔ)在string類型的key中,并且可以設(shè)置TTL過(guò)期時(shí)間
- 方法②:將任意Java對(duì)象序列化為json并存儲(chǔ)在string類型的key中,并且可以設(shè)置邏輯過(guò)期時(shí)間,用于處理緩存擊穿問(wèn)題
- 方法③:根據(jù)指定的key查詢緩存,并反序列化為指定類型,利用緩存空值的方式解決緩存穿透問(wèn)題
- 方法④:根據(jù)指定的key查詢緩存,并反序列化為指定類型,需要利用邏輯過(guò)期解決緩存擊穿問(wèn)題
我們新建一個(gè)類,先把大致框架寫出來(lái),方法的參數(shù)可以邊寫邊完善,但是我的方法參數(shù)已經(jīng)完善好了:
@Component public class CacheClient { ? ? private final StringRedisTemplate stringRedisTemplate; ? ? public CacheClient(StringRedisTemplate stringRedisTemplate) { ? ? ? ? this.stringRedisTemplate = stringRedisTemplate; ? ? } ? ? //方法一 ? ? public void set(String key, Object value, Long time, TimeUnit unit) { ? ? } ? ? //方法二 ? ? public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) { ? ? } ? ? //方法三 ? ? public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Long time, TimeUnit unit, Function<ID, R> dbFallback) { ? ? } ? ? //方法四 ? ? public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Long time, TimeUnit unit, Function<ID, R> dbFallback) { ? ? } ? ? //線程池 ? ? private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10); ? ? //獲取鎖 ? ? private boolean tryLock(String key) { ? ? ? ? Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS); ? ? ? ? return BooleanUtil.isTrue(flag); ? ? } ? ? //釋放鎖 ? ? private void unLock(String key) { ? ? ? ? stringRedisTemplate.delete(key); ? ? } }
接下來(lái)我們可以不斷完善這些方法。
1.1 方法一
public void set(String key, Object value, Long time, TimeUnit unit) { ? ? stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit); }
1.2 方法二
public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) { ? ? RedisData redisData = new RedisData(); ? ? redisData.setData(value); ? ? redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))); ? ? stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData)); }
1.3 方法三
public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Long time, TimeUnit unit, Function<ID, R> dbFallback) { String key = keyPrefix + id; //1.從redis中查詢商鋪緩存 String json = stringRedisTemplate.opsForValue().get(key); //2.判斷是否存在 if (StrUtil.isNotBlank(json)) { //2.1.存在 return JSONUtil.toBean(json, type); } //2.2.不存在 //判斷是否為空值 if (json != null) { //不為null,則必為空 return null; } //3.查詢數(shù)據(jù)庫(kù) R r = dbFallback.apply(id); if (r == null) { //3.1.不存在,緩存空值 stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); } else { //3.2.存在,緩存數(shù)據(jù) this.set(key, r, time, unit); } return r; }
方法三用到了函數(shù)式編程,這里非常巧妙,順便再貼一下調(diào)用方法是怎樣調(diào)用的:
Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id,Shop.class,CACHE_SHOP_TTL,TimeUnit.MINUTES,this::getById);
1.4 方法四
public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type, Long time, TimeUnit unit, Function<ID, R> dbFallback) { //1.從redis查詢商鋪緩存 String key = prefix + id; String json = stringRedisTemplate.opsForValue().get(key); //2.判斷是否存在 if (StrUtil.isBlank(json)) { //未命中,直接返回空 return null; } //3.命中,判斷是否過(guò)期 RedisData redisData = JSONUtil.toBean(json, RedisData.class); R r = JSONUtil.toBean((JSONObject) redisData.getData(), type); if (redisData.getExpireTime().isAfter(LocalDateTime.now())) { //3.1未過(guò)期,直接返回店鋪信息 return r; } //3.2.已過(guò)期,緩存重建 //3.3.獲取鎖 String lockKey = lockPre + id; boolean flag = tryLock(lockKey); if (flag) { //3.4.獲取成功 //4再次檢查redis緩存是否過(guò)期,做double check json = stringRedisTemplate.opsForValue().get(key); //4.1.判斷是否存在 if (StrUtil.isBlank(json)) { //未命中,直接返回空 return null; } //4.2.命中,判斷是否過(guò)期 redisData = JSONUtil.toBean(json, RedisData.class); r = JSONUtil.toBean((JSONObject) redisData.getData(), type); if (redisData.getExpireTime().isAfter(LocalDateTime.now())) { //4.3.未過(guò)期,直接返回店鋪信息 return r; } //4.4過(guò)期,返回舊數(shù)據(jù) CACHE_REBUILD_EXECUTOR.submit(() -> { //5.重建緩存 try { R r1 = dbFallback.apply(id); this.setWithLogicExpire(key, r1, time, unit); } catch (Exception e) { throw new RuntimeException(e); } finally { //釋放鎖 unLock(lockKey); } }); } //7.獲取失敗,返回舊數(shù)據(jù) return r; }
2. 完整工具類代碼
@Component @Slf4j public class CacheClient { ? ? private final StringRedisTemplate stringRedisTemplate; ? ? public CacheClient(StringRedisTemplate stringRedisTemplate) { ? ? ? ? this.stringRedisTemplate = stringRedisTemplate; ? ? } ? ? public void set(String key, Object value, Long time, TimeUnit unit) { ? ? ? ? stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit); ? ? } ? ? public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) { ? ? ? ? RedisData redisData = new RedisData(); ? ? ? ? redisData.setData(value); ? ? ? ? redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))); ? ? ? ? stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData)); ? ? } ? ? public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Long time, TimeUnit unit, Function<ID, R> dbFallback) { ? ? ? ? String key = keyPrefix + id; ? ? ? ? //1.從redis中查詢商鋪緩存 ? ? ? ? String json = stringRedisTemplate.opsForValue().get(key); ? ? ? ? //2.判斷是否存在 ? ? ? ? if (StrUtil.isNotBlank(json)) { ? ? ? ? ? ? //2.1.存在 ? ? ? ? ? ? return JSONUtil.toBean(json, type); ? ? ? ? } ? ? ? ? //2.2.不存在 ? ? ? ? //判斷是否為空值 ? ? ? ? if (json != null) { ? ? ? ? ? ? //不為null,則必為空 ? ? ? ? ? ? return null; ? ? ? ? } ? ? ? ? //3.查詢數(shù)據(jù)庫(kù) ? ? ? ? R r = dbFallback.apply(id); ? ? ? ? if (r == null) { ? ? ? ? ? ? //3.1.不存在,緩存空值 ? ? ? ? ? ? stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); ? ? ? ? } else { ? ? ? ? ? ? //3.2.存在,緩存數(shù)據(jù) ? ? ? ? ? ? this.set(key, r, time, unit); ? ? ? ? } ? ? ? ? return r; ? ? } ? ? public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Long time, TimeUnit unit, Function<ID, R> dbFallback) { ? ? ? ? //1.從redis查詢商鋪緩存 ? ? ? ? String key = prefix + id; ? ? ? ? String json = stringRedisTemplate.opsForValue().get(key); ? ? ? ? //2.判斷是否存在 ? ? ? ? if (StrUtil.isBlank(json)) { ? ? ? ? ? ? //未命中,直接返回空 ? ? ? ? ? ? return null; ? ? ? ? } ? ? ? ? //3.命中,判斷是否過(guò)期 ? ? ? ? RedisData redisData = JSONUtil.toBean(json, RedisData.class); ? ? ? ? R r = JSONUtil.toBean((JSONObject) redisData.getData(), type); ? ? ? ? if (redisData.getExpireTime().isAfter(LocalDateTime.now())) { ? ? ? ? ? ? //3.1未過(guò)期,直接返回店鋪信息 ? ? ? ? ? ? return r; ? ? ? ? } ? ? ? ? //3.2.已過(guò)期,緩存重建 ? ? ? ? //3.3.獲取鎖 ? ? ? ? String lockKey = lockPre + id; ? ? ? ? boolean flag = tryLock(lockKey); ? ? ? ? if (flag) { ? ? ? ? ? ? //3.4.獲取成功 ? ? ? ? ? ? //4再次檢查redis緩存是否過(guò)期,做double check ? ? ? ? ? ? json = stringRedisTemplate.opsForValue().get(key); ? ? ? ? ? ? //4.1.判斷是否存在 ? ? ? ? ? ? if (StrUtil.isBlank(json)) { ? ? ? ? ? ? ? ? //未命中,直接返回空 ? ? ? ? ? ? ? ? return null; ? ? ? ? ? ? } ? ? ? ? ? ? //4.2.命中,判斷是否過(guò)期 ? ? ? ? ? ? redisData = JSONUtil.toBean(json, RedisData.class); ? ? ? ? ? ? r = JSONUtil.toBean((JSONObject) redisData.getData(), type); ? ? ? ? ? ? if (redisData.getExpireTime().isAfter(LocalDateTime.now())) { ? ? ? ? ? ? ? ? //4.3.未過(guò)期,直接返回店鋪信息 ? ? ? ? ? ? ? ? return r; ? ? ? ? ? ? } ? ? ? ? ? ? //4.4過(guò)期,返回舊數(shù)據(jù) ? ? ? ? ? ? CACHE_REBUILD_EXECUTOR.submit(() -> { ? ? ? ? ? ? ? ? //5.重建緩存 ? ? ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? ? ? R r1 = dbFallback.apply(id); ? ? ? ? ? ? ? ? ? ? this.setWithLogicExpire(key, r1, time, unit); ? ? ? ? ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? ? ? ? ? throw new RuntimeException(e); ? ? ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? ? ? //釋放鎖 ? ? ? ? ? ? ? ? ? ? unLock(lockKey); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? }); ? ? ? ? } ? ? ? ? //7.獲取失敗,返回舊數(shù)據(jù) ? ? ? ? return r; ? ? } ? ? private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10); ? ? //獲取鎖 ? ? private boolean tryLock(String key) { ? ? ? ? Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS); ? ? ? ? return BooleanUtil.isTrue(flag); ? ? } ? ? //釋放鎖 ? ? private void unLock(String key) { ? ? ? ? stringRedisTemplate.delete(key); ? ? } }
到此這篇關(guān)于Redis緩存工具封裝實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Redis緩存工具封裝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis中事務(wù)機(jī)制及樂(lè)觀鎖的實(shí)現(xiàn)
這篇文章主要介紹了redis中事務(wù)機(jī)制及樂(lè)觀鎖的相關(guān)內(nèi)容,通過(guò)事務(wù)的執(zhí)行分析Redis樂(lè)觀鎖,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10Window下對(duì)Redis進(jìn)行開(kāi)啟與關(guān)閉的操作方法
這篇文章主要介紹了Window下對(duì)Redis進(jìn)行開(kāi)啟與關(guān)閉的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-11-11Windows系統(tǒng)安裝redis數(shù)據(jù)庫(kù)
這篇文章介紹了Windows系統(tǒng)安裝redis數(shù)據(jù)庫(kù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03MyBatis緩存和二級(jí)緩存整合Redis的解決方案
這篇文章主要介紹了MyBatis緩存和二級(jí)緩存整合Redis,將MyBatis緩存和二級(jí)緩存整合Redis,可以提高查詢效率,同時(shí)也能保證數(shù)據(jù)的可靠性和一致性,需要的朋友可以參考下2023-07-07基于?Spring?Aop?環(huán)繞通知實(shí)現(xiàn)?Redis?緩存雙刪功能(示例代碼)
基于 spring aop 常規(guī)應(yīng)用場(chǎng)景多是用于日志記錄以及實(shí)現(xiàn) redis 分布式鎖,在 github 中也有項(xiàng)目是把它拿來(lái)當(dāng)作緩存的異常捕捉,這篇文章主要介紹了基于?Spring?Aop?環(huán)繞通知實(shí)現(xiàn)?Redis?緩存雙刪,需要的朋友可以參考下2022-08-08利用redis實(shí)現(xiàn)聊天記錄轉(zhuǎn)存功能的全過(guò)程
社交類軟件聊天功能必不可少,聊天記錄存儲(chǔ)的方式也比較多,比如文本,數(shù)據(jù)庫(kù),云等等,但是最好的選擇還是redis進(jìn)行存儲(chǔ),這篇文章主要給大家介紹了關(guān)于如何利用redis實(shí)現(xiàn)聊天記錄轉(zhuǎn)存功能的相關(guān)資料,需要的朋友可以參考下2021-08-08