caffeine+redis实现二级缓存解决缓存雪崩
·
废话不多说直接上代码:
1.依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
这里版本java8所以用的2.9.3
2.配置类:过期时间redis30分钟+caffeine3分钟
package com.hmdp.config;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Scheduler;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
@Configuration
@Slf4j
public class CacheConfig {
@Autowired
private ShopMapper shopMapper; // 使用MyBatis-Plus Mapper
@Autowired
private StringRedisTemplate stringRedisTemplate; //注意stringRedisTemplate要配置序列化器
@Autowired
private Cache<Long, Shop> caffeineCache; // 上面定义的Bean
@Bean
public Cache<Long, Shop> caffeineCache() {
return Caffeine.newBuilder()
// 初始容量
.initialCapacity(100)
// 最大条目数(超过会按策略淘汰)
.maximumSize(10_000)
// 写入后过期时间(防雪崩关键1:本地TTL较短)
.expireAfterWrite(3, TimeUnit.MINUTES)
// 可选:定时清理过期条目
.scheduler(Scheduler.systemScheduler())
// 记录统计信息(用于监控)
.recordStats()
.build();
}
@PostConstruct
public void preloadHotShops() {
log.info("开始预热店铺缓存...");
// 一次性查出所有店铺(10条,不会有性能问题)
List<Shop> shops = shopMapper.selectList(null); //我这里数据库中只有十条数据,如果数据量过大,取hotK就行,不然小心内存爆掉
for (Shop shop : shops) { //遍历添加
if (shop != null) { //如果为空报空指针异常
caffeineCache.put(shop.getId(), shop);
String key = CACHE_SHOP_KEY + shop.getId();
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES); //注意反编译,
}
}
log.info("缓存预热完成,共 {} 家店铺", shops.size());
}
}
3.使用,这里直接写业务层
1.先从内存中查:
//从caffeine中查数据
Shop shop = caffeineCache.getIfPresent(id);
if (shop != null) {
return Result.ok(shop);
}
2.再从redis中查,能查到再回填内存
//从redis中查询店铺 shopstr是string类型的json字符串
String shopStr = stringRedisTemplate.opsForValue().get(key);
//存在,直接返回
if (StrUtil.isNotBlank(shopStr)){
caffeineCache.put(id, JSONUtil.toBean(shopStr, Shop.class)); //回填caffeine
return Result.ok(JSONUtil.toBean(shopStr, Shop.class)); //转换成对象
}
3.都没查到,从数据库中查,然后回填redis跟caffeine
//不存在,从数据库中查询
shop = getById(id); 从数据库中查询
if (shop == null){ //为什么还要判断,因为前面布隆过滤器可能误判 本来这个数据就不存在
//缓存穿透,,添加null值
stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
//数据库中不存在:返回错误
return null;
}
//数据库中存在,返回店铺,,,保存到redis中
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
stringRedisTemplate.expire(key, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
//保存到caffeine中
caffeineCache.put(shop.getId(), shop);
return Result.ok(shop);
4.总结思路,使用caffeine基于JVM本地缓存,再用redis做二次缓存,即使redis挂了还有caffeine不会打到db,一般来说这种都会有缓存数据的一致性问题,所以不适合强一致性业务
更多推荐




所有评论(0)