一身报国有万死,双鬓向人无再青。 —— 陆游
封装了一个操作redis
的管理层,简单处理了缓存穿透、击穿、雪崩问题
Manager
package com.ruben.manager;
import com.alibaba.fastjson.TypeReference;
import java.util.function.Supplier;
/**
* redis管理层
*
* @author <achao1441470436@gmail.com>
* @since 2021/6/11 0011 21:55
*/
public interface RedisManager {
/**
* 从缓存中获取否则从mysql中查询
*
* @param key 缓存中的key
* @param mysqlSupplier 查询mysql操作
* @param typeReference 返回的类型
* @param <T> 数据类型
* @return 数据
*/
<T> T getFromRedisOrPutIntoMysql(String key, Supplier<T> mysqlSupplier, TypeReference<T> typeReference);
}
实现类
package com.ruben.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.ruben.utils.Opt;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* redis管理层实现类
*
* @author <achao1441470436@gmail.com>
* @since 2021/6/11 0011 21:55
*/
@Service
public class RedisManagerImpl implements RedisManager {
public static final SecureRandom SECURE_RANDOM = new SecureRandom();
/**
* 过期时间
*/
public static final int EXPIRE_SECONDS = 5 * 60 * 1000;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 从缓存中获取否则从mysql中查询
*
* @param key 缓存中的key
* @param mysqlSupplier 查询mysql操作
* @param typeReference 返回的类型
* @param <T> 数据类型
* @return 数据
*/
@Override
@Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED, isolation = Isolation.READ_UNCOMMITTED)
public <T> T getFromRedisOrPutIntoMysql(String key, Supplier<T> mysqlSupplier, TypeReference<T> typeReference) {
// 判断传入key是否为空
Opt.ofNullable(key).filter(StringUtils::isNotEmpty)
.orElseThrow(() -> new MybatisPlusException("key不能为空"));
// 如果redis中存在,直接返回
String value = stringRedisTemplate.opsForValue().get(key);
if (Objects.nonNull(value)) {
return JSON.parseObject(value, typeReference);
}
// 查数据库并放入缓存这个操作加锁【缓存击穿】
synchronized (this) {
// 否则从数据库中取出
return Opt.ofNullable(mysqlSupplier).map(Supplier::get)
// 如果有值则放入缓存,随机时间【缓存雪崩】
.peek(v -> stringRedisTemplate.opsForValue()
.set(key, JSON.toJSONString(v), SECURE_RANDOM.nextInt(EXPIRE_SECONDS), TimeUnit.SECONDS))
// 没值则放入空对象"{}"【缓存穿透】并返回null
.orElseGet(() -> {
stringRedisTemplate.opsForValue()
.set(key, "{}", SECURE_RANDOM.nextInt(EXPIRE_SECONDS), TimeUnit.SECONDS);
return null;
});
}
}
}
使用方式
@Resource
private RedisManager redisManager;
@Test
public void managerTest() {
List<String> userIds = redisManager.getFromRedisOrPutIntoMysql("userIds", () -> {
// 从数据库中查询...
return Arrays.asList("1", "2", "3");
}, new TypeReference<List<String>>() {
});
System.out.println(userIds);
}
目前自己开发中用着感觉还不错的,大伙可以拿去自定义