在现代开发中,Redis已经成为缓存数据的利器。它提供了高性能和强大的功能,帮助我们处理各种缓存需求。今天,我想分享我基于Redis的Java工具类封装——CacheClient,这个工具类旨在简化缓存操作,并提供一些实用的功能,如缓存穿透和逻辑过期。希望对你有所帮助!
CacheClient的实现
CacheClient类封装了一些基本的缓存操作方法,例如set和setWithLogicalExpire。其中set方法用于普通的缓存设置,而setWithLogicalExpire方法则用于逻辑过期的缓存设置。此外,为了应对缓存穿透和缓存雪崩的问题,我们实现了queryWithPassThrough和queryWithLogicalExpire方法。为了避免缓存重建时的并发问题,我们还引入了分布式锁。下面是完整的代码实现:
@Slf4j
@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) {
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}
public void setWithLogicalExpire(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, Function<ID, R> dbFallback,
Long time, TimeUnit unit) {
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)) {
return JSONUtil.toBean(json, type);
}
if (json != null) {
return null;
}
R r = dbFallback.apply(id);
if (r == null) {
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
this.set(key, r, time, unit);
return r;
}
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback,
Long time, TimeUnit unit) {
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isBlank(json)) {
return null;
}
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
LocalDateTime expireTime = redisData.getExpireTime();
if (LocalDateTime.now().isBefore(expireTime)) {
return r;
}
boolean flag = tryLock(LOCK_SHOP_KEY + id);
if (flag) {
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
R r1 = dbFallback.apply(id);
this.setWithLogicalExpire(key, r1, time, unit);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
unlock(LOCK_SHOP_KEY + id);
}
});
}
return r;
}
private boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
private void unlock(String key) {
stringRedisTemplate.delete(key);
}
}