封裝Redis工具類(lèi)實(shí)踐
一、封裝Redis工具類(lèi)
基于StringRedisTemplate封裝一個(gè)緩存工具類(lèi),滿(mǎn)足下列需求:
- 方法1:將任意Java對(duì)象序列化為json并存儲(chǔ)在string類(lèi)型的key中,并且可以設(shè)置TTL過(guò)期時(shí)間
- 方法2:將任意Java對(duì)象序列化為json并存儲(chǔ)在string類(lèi)型的key中,并且可以設(shè)置邏輯過(guò)期時(shí)間,用于處理緩存擊穿問(wèn)題
- 方法3:根據(jù)指定的key查詢(xún)緩存,并反序列化為指定類(lèi)型,利用緩存空值的方式解決緩存穿透問(wèn)題
- 方法4:根據(jù)指定的key查詢(xún)緩存,并反序列化為指定類(lèi)型,需要利用邏輯過(guò)期解決緩存擊穿問(wèn)題
- 方法5:根據(jù)指定的key查詢(xún)緩存,并反序列化為指定類(lèi)型,需要利用互斥鎖解決緩存擊穿問(wèn)題
1. 使用構(gòu)造方法注入StringRedisTemplate
@Slf4j
@Component
public class CacheClient {
private final StringRedisTemplate stringRedisTemplate;
// 構(gòu)造方法注入StringRedisTemplate
public CacheClient(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
}
2. 方法1
將任意Java對(duì)象序列化為json并存儲(chǔ)在string類(lèi)型的key中,并且可以設(shè)置TTL過(guò)期時(shí)間
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}
3. 方法2
將任意Java對(duì)象序列化為json并存儲(chǔ)在string類(lèi)型的key中,并且可以設(shè)置邏輯過(guò)期時(shí)間,用于處理緩存擊穿問(wèn)題
public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
// 設(shè)置邏輯過(guò)期
RedisData redisData = new RedisData();
redisData.setData(value);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
// 寫(xiě)入Redis
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
4. 方法3
根據(jù)指定的key查詢(xún)緩存,并反序列化為指定類(lèi)型,利用緩存空值的方式解決緩存穿透問(wèn)題
/**
* @param keyPrefix 業(yè)務(wù)的緩存key前綴,例如:"cache:shop:"
* @param id
* @param type 對(duì)象類(lèi)型,例如:User.class、Shop.class
* @param dbFallback 傳入查詢(xún)數(shù)據(jù)庫(kù)的方法,例如:getById(id);
* @param time
* @param unit
* @param <R> 返回值類(lèi)型
* @param <ID> id的類(lèi)型,String、Integer、Long。。。
* @return
*/
public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
String key = keyPrefix + id;
// 1.從redis查詢(xún)商鋪緩存
String json = stringRedisTemplate.opsForValue().get(key);
// 2.判斷是否存在
if (StrUtil.isNotBlank(json)) {
// 3.存在,直接返回
return JSONUtil.toBean(json, type);
}
// 判斷命中的是否是空值
if (json != null) {
// 返回一個(gè)錯(cuò)誤信息
return null;
}
// 4.不存在,根據(jù)id查詢(xún)數(shù)據(jù)庫(kù)
R r = dbFallback.apply(id);
// 5.不存在,返回錯(cuò)誤
if (r == null) {
// 將空值寫(xiě)入redis
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
// 返回錯(cuò)誤信息
return null;
}
// 6.存在,寫(xiě)入redis
this.set(key, r, time, unit);
return r;
}
5. 方法4
根據(jù)指定的key查詢(xún)緩存,并反序列化為指定類(lèi)型,需要利用邏輯過(guò)期解決緩存擊穿問(wèn)題
@Data
public class RedisData {
// 邏輯過(guò)期時(shí)間
private LocalDateTime expireTime;
// 緩存預(yù)熱對(duì)象
private Object data;
}
public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
String key = keyPrefix + id;
// 1.從redis查詢(xún)商鋪緩存
String json = stringRedisTemplate.opsForValue().get(key);
// 2.判斷是否存在
if (StrUtil.isBlank(json)) {
// 3.存在,直接返回
return null;
}
// 4.命中,需要先把json反序列化為對(duì)象
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
LocalDateTime expireTime = redisData.getExpireTime();
// 5.判斷是否過(guò)期
if(expireTime.isAfter(LocalDateTime.now())) {
// 5.1.未過(guò)期,直接返回店鋪信息
return r;
}
// 5.2.已過(guò)期,需要緩存重建
// 6.緩存重建
// 6.1.獲取互斥鎖
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
// 6.2.判斷是否獲取鎖成功
if (isLock){
// 6.3.成功,開(kāi)啟獨(dú)立線程,實(shí)現(xiàn)緩存重建
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
// 查詢(xún)數(shù)據(jù)庫(kù)
R newR = dbFallback.apply(id);
// 重建緩存
this.setWithLogicalExpire(key, newR, time, unit);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
// 釋放鎖
unlock(lockKey);
}
});
}
// 6.4.返回過(guò)期的商鋪信息
return r;
}
6. 方法5
根據(jù)指定的key查詢(xún)緩存,并反序列化為指定類(lèi)型,需要利用互斥鎖解決緩存擊穿問(wèn)題
/**
* 自定義互斥鎖,利用redis的setnx方法來(lái)表示獲取鎖
* @param key
* @return
*/
private boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
/**
* 釋放互斥鎖
* @param key
*/
private void unlock(String key) {
stringRedisTemplate.delete(key);
}
public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
String key = keyPrefix + id;
// 1.從redis查詢(xún)商鋪緩存
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2.判斷是否存在
if (StrUtil.isNotBlank(shopJson)) {
// 3.存在,直接返回
return JSONUtil.toBean(shopJson, type);
}
// 判斷命中的是否是空值
if (shopJson != null) {
// 返回一個(gè)錯(cuò)誤信息
return null;
}
// 4.實(shí)現(xiàn)緩存重建
// 4.1.獲取互斥鎖
String lockKey = LOCK_SHOP_KEY + id;
R r = null;
try {
boolean isLock = tryLock(lockKey);
// 4.2.判斷是否獲取成功
if (!isLock) {
// 4.3.獲取鎖失敗,休眠并重試
Thread.sleep(50);
return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);
}
// 4.4.獲取鎖成功,根據(jù)id查詢(xún)數(shù)據(jù)庫(kù)
r = dbFallback.apply(id);
// 5.不存在,返回錯(cuò)誤
if (r == null) {
// 將空值寫(xiě)入redis
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
// 返回錯(cuò)誤信息
return null;
}
// 6.存在,寫(xiě)入redis
this.set(key, r, time, unit);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 7.釋放鎖
unlock(lockKey);
}
// 8.返回
return r;
}
二、調(diào)用Redis工具類(lèi)
- 在ShopServiceImpl中,調(diào)用Redis工具類(lèi)
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
@Resource
private CacheClient cacheClient;
@Override
public Result queryById(Long id) {
// 解決緩存穿透
Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
// 互斥鎖解決緩存擊穿
// Shop shop = cacheClient.queryWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
// 邏輯過(guò)期解決緩存擊穿
// Shop shop = cacheClient.queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);
if (shop == null) {
return Result.fail("店鋪不存在!");
}
// 返回
return Result.ok(shop);
}
}
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
利用Redis進(jìn)行數(shù)據(jù)緩存的項(xiàng)目實(shí)踐
在實(shí)際的業(yè)務(wù)場(chǎng)景中,Redis 一般和其他數(shù)據(jù)庫(kù)搭配使用,用來(lái)減輕后端數(shù)據(jù)庫(kù)的壓力,本文就介紹了利用Redis進(jìn)行數(shù)據(jù)緩存的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2022-06-06
redis與memcached的區(qū)別_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Memcached是以LiveJurnal旗下Danga Interactive公司的Bard Fitzpatric為首開(kāi)發(fā)的高性能分布式內(nèi)存緩存服務(wù)器。那么redis與memcached有什么區(qū)別呢?下面小編給大家介紹下redis與memcached的區(qū)別,感興趣的朋友參考下吧2017-08-08
手把手教你用Redis 實(shí)現(xiàn)點(diǎn)贊功能并且與數(shù)據(jù)庫(kù)同步
本文主要介紹了Redis 實(shí)現(xiàn)點(diǎn)贊功能并且與數(shù)據(jù)庫(kù)同步,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Redis定時(shí)任務(wù)原理的實(shí)現(xiàn)
本文主要是基于?redis?6.2?源碼進(jìn)行分析定時(shí)事件的數(shù)據(jù)結(jié)構(gòu)和常見(jiàn)操作,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
gem install redis報(bào)錯(cuò)的解決方案
今天小編就為大家分享一篇關(guān)于gem install redis報(bào)錯(cuò)的解決方案,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
Redis節(jié)省內(nèi)存的十個(gè)技巧分享
你是否在工作中遇到過(guò)Redis的bigkey導(dǎo)致的內(nèi)存占用嚴(yán)重、查詢(xún)耗時(shí)大大增加?同時(shí)bigKey還可能導(dǎo)致Redis實(shí)例的崩潰,因?yàn)閮?nèi)存不夠用了,所以本文給大家介紹了Redis極大節(jié)省內(nèi)存的10個(gè)技巧,需要的朋友可以參考下2024-04-04

