视频地址:https://www.bilibili.com/video/BV1Ha411c7hB
代码地址:https://gitee.com/crazyliyang/video-teaching.git
1. 缓存穿透 解决案例
使用布隆过滤器
核心代码, 使用 Redisson 库的布隆过滤器:org.redisson.api.RBloomFilter
<!-- Redis 客户端工具 redisson 实现对 Redisson 的自动化配置-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.4</version>
</dependency>
配置类, 实例化布隆过滤器RBloomFilter
@Configuration
public class RedisConfiguration {
/**
* 预计要插入多少数据
*/
private static int size = 100000; // 十万
/**
* 期望的误判率
*/
private static double fpp = 0.001;
@Value("${spring.redis.host}")
private String redistHost;
@Value("${spring.redis.port}")
private Integer redistPort;
@Autowired
private ProductService productService; // 商品服务
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置开启事务支持
template.setEnableTransactionSupport(true);
// 设置 RedisConnection 工厂。 它就是实现多种 Java Redis 客户端接入的秘密工厂
template.setConnectionFactory(factory);
// 使用 String 序列化方式,序列化 KEY 。
template.setKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
template.setValueSerializer(RedisSerializer.json());
return template;
}
/**
* 实例化布隆过滤器
*
*/
@Bean
public RBloomFilter redisBloomFilter(){
String redisAddress = "redis://"+redistHost+ ":"+redistPort;
Config config = new Config();
config.useSingleServer().setAddress(redisAddress);
RedissonClient redisson = Redisson.create(config);
// 创建RedisBloomFilter
RBloomFilter<String> redisBloomFilter = redisson.getBloomFilter("productBloomFilter03");
//初始化布隆过滤器:预计元素为10000L ( 十万), 误差率为 0.001
redisBloomFilter.tryInit(size, fpp);
return redisBloomFilter;
}
}
使用隆过滤器RBloomFilter
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService {
//redisKey前缀
private static final String KEY_PREFIX = "product:";
// 格式化一下
private static String buildKey(String id) {
return KEY_PREFIX + id; // product:商品ID
}
@Resource(name = "redisTemplate")
private ValueOperations<Serializable, String> redisOperations;
@Autowired
private RBloomFilter<String> redisBloomFilter;
/**
* 重载 getById(id) 做第一点增强
*
*/
@Override
public ProductEntity getById(Serializable id) {
String redisKey = buildKey(String.valueOf(id)); // 构建rediskey
if(!redisBloomFilter.contains(redisKey)){ // 布隆过滤器判断 redisKey 不存在
return null;
}
String redisValue = redisOperations.get(redisKey); // 从缓存中去查
if (redisValue != null) { // 查到了
return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回;
} else { // 没查到
ProductEntity product = getBaseMapper().selectById(id); // 从数据库中查, 然后放到缓存中;
if(product !=null){
String jsonString = JSONUtil.toJSONString(product);
redisOperations.set(redisKey, jsonString); // 放到缓存中
return product;
}
return null;
}
}
}
2. 缓存雪崩 解决案例
核心代码: 使用一个过期时间的随机数, 让 key 的过期时间分散化, 防止 key 大批集中过期;
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService {
private static final String KEY_PREFIX = "product:";
private static String buildKey(String id) {
return KEY_PREFIX + id; // product:商品ID
}
@Resource(name = "redisTemplate")
private ValueOperations<Serializable, String> redisOperations;
@Autowired
private RBloomFilter<String> redisBloomFilter;
/**
* 重载 getById(id) 做第一点增强
*/
@Override
public ProductEntity getById(Serializable id) {
String redisKey = buildKey(String.valueOf(id)); // 构建rediskey
if(!redisBloomFilter.contains(redisKey)){ // 布隆过滤器判断 redisKey 不存在
return null;
}
String redisValue = redisOperations.get(redisKey); // 从缓存中去查
if (redisValue != null) { // 查到了
return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回;
} else { // 没查到
ProductEntity product = getBaseMapper().selectById(id); // 从数据库中查, 然后放到缓存中;
if(product !=null){
String jsonString = JSONUtil.toJSONString(product);
long radomLong = radomLong(); // 随机产生一个数 ( 24 ~ 72 )
redisOperations.set(redisKey, jsonString, radomLong, TimeUnit.HOURS); // 放到缓存中, 时间取随机, 时间单位小时
return product;
}
return null;
}
}
public static long radomLong(){
long min = 24;
long max = 72;
long rangeLong = min + (((long) (new Random().nextDouble() * (max - min))));
return rangeLong;
}
}
3. 缓存并发 解决案例
使用Redis 分布式锁, 核心代码:
封装 RedisLock
@Component
public class RedisLock {
@Autowired
private RedissonClient redissonClient;
/**
* 加锁
* @param lockKey
* @return
*/
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
/**
* 释放锁
* @param lockKey
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
/**
* 释放锁
* @param lock
*/
public void unlock(RLock lock) {
lock.unlock();
}
/**
* 带超时的锁
* @param lockKey
* @param timeout 超时时间 单位:秒
*/
public RLock lock(String lockKey, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, TimeUnit.SECONDS);
return lock;
}
/**
* 带超时的锁
* @param lockKey
* @param unit 时间单位
* @param timeout 超时时间
*/
public RLock lock(String lockKey, TimeUnit unit , int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* 尝试获取锁
* @param lockKey
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public boolean tryLock(String lockKey, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
/**
* 尝试获取锁
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
}
配置类 实例化 注册RedissonClient
@Configuration
public class RedisConfiguration {
@Value("${spring.redis.host}")
private String redistHost;
@Value("${spring.redis.port}")
private Integer redistPort;
@Autowired
private ProductService productService; // 商品服务
/**
* RedissonClient 注册
*/
@Bean
public RedissonClient redissonClient(){
String redisAddress = "redis://"+redistHost+ ":"+redistPort;
Config config = new Config();
config.useSingleServer().setAddress(redisAddress);
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
使用Redis 分布式锁:
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService {
//前缀
private static final String KEY_PREFIX = "product:";
private static String buildKey(String id) {
return KEY_PREFIX + id; // product:商品ID
}
@Resource(name = "redisTemplate")
private ValueOperations<Serializable, String> redisOperations;
@Autowired
private RBloomFilter<String> redisBloomFilter;
@Autowired
private RedisLock redisLock; // 自己封装的 redisLock
/**
* 重载 getById(id) 做第一点增强
*/
@Override
public ProductEntity getById(Serializable id) {
String redisKey = buildKey(String.valueOf(id)); // 构建rediskey
if (!redisBloomFilter.contains(redisKey)) { // 布隆过滤器判断 redisKey 不存在
return null;
}
String redisValue = redisOperations.get(redisKey); // 从缓存中去查
if (redisValue != null) { // 查到了
return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回;
}
// 在查询 DB 之前加 分布式锁
redisLock.lock(String.valueOf(id));
try{
// 再一次查询缓存, 是为了分布式并发情况下, 其他并发的请求, 能在锁解开之后, 再次查缓存
String redisValue2 = redisOperations.get(redisKey);
if (redisValue2 != null) {
return JSONUtil.parseObject(redisValue, ProductEntity.class);
}
ProductEntity product = getBaseMapper().selectById(id); // 从数据库中查
if (product != null) {
String jsonString = JSONUtil.toJSONString(product);
redisOperations.set(redisKey, jsonString); // 放到缓存中
return product;
}
return null;
}finally {
redisLock.unlock(String.valueOf(id)); // 释放锁
}
}
}