目录
一、本地缓存Caffeine和redis 依赖以及Redis配置
1、配置本地缓存Caffeine 配置类开关和redis Bean
(1)请求接口, 这个返回值 HotelUnit 可以根据 自己的业务定义返回值,我这里是用自己的业务返回值,你可以用String, Integer,对象接受,看你自己怎么存的业务。
注意:不过多介绍基本知识,需要先自行了解 本地缓存caffeine和 Redis 相关基本知识,主讲思路和代码。
总体思路
级别: L1 Caffeine JVM 级别缓存 , L2 Redis 缓存
1、服务启动。
2、读取配置文件中本地缓存开关是否。
A、本地缓存开关打开true,请求进来 --> 读写一级缓存L1 -->判断L1是否存在,存在那么返回结果。L1不存在,那么在进行 L2判断是否存在 -->存在,写入L1,返回结果;L2不存在 -->读取数据库-->写入L2-->写入L1-->返回结
B、本地缓存开关关闭false,请求进来 --> L2判断是否存在 -->存在,返回结果;L2不存在 -->读取数据库-->写入L2-->返回结果。
业务流程图
一、本地缓存Caffeine和redis 依赖以及Redis配置
<!-- Redis缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.1</version>
</dependency>
<!-- 咖啡因缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.1</version>
</dependency>
yml配置文件
redis: database: 14 # 几号库 host: 192.168.1.94 port: 6379 password: lettuce: pool: max-active: 8 # 最大连接 max-wait: -1ms # 连接等待时间 max-idle: 8 # 最大空闲连接 min-idle: 0 # 最小空闲连接 # 本地缓存开关 com: huwei: hotel: collector: cache: hotelunit: local-cache: enabled: true
二、代码逻辑及步骤
注意:redis中 put hash类型的数据,本文不做演示。
1、配置本地缓存Caffeine 配置类开关和redis Bean
@ConditionalOnProperty注解来控制@Configuration配置类是否生效,不懂请自行百度。
**
* @author ljy
* @date 2023/7/25
*/
@Configuration(proxyBeanMethods = false)
public class HotelUnitCacheConfiguration {
@Bean
public HotelUnitRedisCache hotelUnitRedisCache(){
return new HotelUnitRedisCache();
}
//配置本地缓存bean 开关
@Configuration
@ConditionalOnProperty(name = HotelUnitCacheProperties.LOCAL_CACHE_PREFIX + ".enabled", havingValue = "true", matchIfMissing = false)
@EnableConfigurationProperties(HotelUnitCacheProperties.class)
static class LocalCacheConfiguration{
@Primary
@Bean
HotelUnitCaffeineCache hotelUnitCaffeineCache(HotelUnitCacheProperties hotelUnitCacheProperties, HotelUnitRedisCache hotelUnitRedisCache) {
return new HotelUnitCaffeineCache(hotelUnitRedisCache, hotelUnitCacheProperties);
}
}
}
2、配置本地缓存用到的常量
@EnableConfigurationProperties注解的作用是:使使用 @ConfigurationProperties 注解的类生效。
/**
* @author ljy
* @date 2023/7/25
*/
@Data
@Component
@ConfigurationProperties(prefix = HotelUnitCacheProperties.LOCAL_CACHE_PREFIX)
public class HotelUnitCacheProperties implements Serializable {
private static final long serialVersionUID = 1L;
public static final String LOCAL_CACHE_PREFIX = "com.huwei.hotel.collector.cache.hotelunit.local-cache";
/** 生存时间(毫秒) **/
private Long expire;
/** 最大的缓存条目数 **/
private Long maximumSize;
/** 初始化容器大小 **/
private Integer initialCapacity;
public HotelUnitCacheProperties(){
super();
}
/** 默认的生存时间 (30天) **/
public static final long CACHE_EXPIRE = 30L;
/** 默认的最大缓存条目数 **/
public static final long CACHE_MAXIMUM_SIZE = 1000L;
/** 默认的初始化容量大小 **/
public static final int CACHE_INITIAL_CAPACITY = 150;
public void initCacheProperties() {
this.expire = (expire == null ? CACHE_EXPIRE : expire);
this.maximumSize = (maximumSize == null ? CACHE_MAXIMUM_SIZE : maximumSize);
this.initialCapacity = (initialCapacity == null ? CACHE_INITIAL_CAPACITY : initialCapacity);
}
3、本地缓存 初始化以及 接口实现方法。
(1)请求接口, 这个返回值 HotelUnit 可以根据 自己的业务定义返回值,我这里是用自己的业务返回值,你可以用String, Integer,对象接受,看你自己怎么存的业务。
/**
* @author ljy
* @date 2023/7/25
*/
@Data
public class HotelUnit implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
private Long id;
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "省份编码")
private String proviceCode;
@ApiModelProperty(value = "省份名称")
private String proviceName;
@ApiModelProperty(value = "缓存时间")
private String cacheTime;
}
/**
* @author ljy
* @date 2023/7/25
*/
public interface HotelUnitCache {
/**根据id查询**/
Optional<HotelUnit> getById(Long id);
/**获取多个**/
List<HotelUnit> getByIds(List<Long> ids);
}
(2)caffeine 实现类,以及初始化。
/**
* @author ljy
* @date 2023/7/25
*/
@Slf4j
public class HotelUnitCaffeineCache implements HotelUnitCache{
LoadingCache<Long, HotelUnit> hotelUnitCache;
final HotelUnitRedisCache redisCache;
final HotelUnitCacheProperties properties;
public HotelUnitCaffeineCache(HotelUnitRedisCache redisCache, HotelUnitCacheProperties properties){
this.properties = properties;
this.redisCache = redisCache;
properties.initCacheProperties();
}
//这个注解作用是服务启动时,加载init方法
@PostConstruct
public void init(){
this.hotelUnitCache = Caffeine.newBuilder().recordStats()
.maximumSize(properties.getMaximumSize())
.expireAfterAccess(properties.getExpire(), TimeUnit.DAYS)
.initialCapacity(properties.getInitialCapacity())
.build(id -> {
log.info("本地缓存 - 默认酒店采集单元缓存主动加载:" + id);
return redisCache.getById(id).orElse(null);
});
}
@Override
public Optional<HotelUnit> getById(Long id) {
return Optional.ofNullable(hotelUnitCache.get(id));
}
@Override
public List<HotelUnit> getByIds(List<Long> ids) {
Map<Long, HotelUnit> hotelUnitMap = hotelUnitCache.getAll(ids);
if (CollectionUtils.isEmpty(hotelUnitMap)) {
return new ArrayList<>();
}
return new ArrayList<>(hotelUnitMap.values());
}
/**
* 删除单个
* @param id
*/
public void invalidate(Long id){
hotelUnitCache.invalidate(id);
}
/**
* 删除多个
* @param ids
*/
public void invalidateAll(Iterable<Long> ids){
hotelUnitCache.invalidateAll(ids);
}
/**
* 清楚所有缓存
*/
public void invalidateAll(){
hotelUnitCache.invalidateAll();
}
(3)Redis实现类
/**
* @author ljy
* @date 2023/7/25
*/
public class HotelUnitRedisCache implements HotelUnitCache{
@Resource(name = RedisConfiguration.JSONRedisTemplate)
RedisTemplate<String, Object> redisTemplate;
/**采集管理缓存key**/
public static final String COLLECT_UNIT_CACHE_KEY = "com.huwei.hotel.collector.cache.hotelunit";
/**
* 单个查询
* @param id
* @return
*/
@Override
public Optional<HotelUnit> getById(Long id) {
HotelUnit hotelUnit = (HotelUnit)
redisTemplate.opsForHash().get(COLLECT_UNIT_CACHE_KEY,
ObjectUtils.getDisplayString(id));
//我这里省略了查询数据库以及存储 redis的步骤,如果有需要可以在 下面加判断 ,redis中不存对象,那么去数据库DB中查询结果返回。然后顺便存数据到redis中。
//
return Optional.ofNullable(hotelUnit);
}
/**
* 多个查询
* @param ids
* @return
*/
@Override
public List<HotelUnit> getByIds(List<Long> ids) {
if (CollectionUtils.isEmpty(ids)) {
return new ArrayList<>();
}
List<Object> list = ids.stream().map(Objects::toString).collect(Collectors.toList());
List<Object> data = redisTemplate.opsForHash().multiGet(COLLECT_UNIT_CACHE_KEY, list);
//我这里省略了查询数据库以及存储 redis的步骤,如果有需要可以在 下面加判断 ,redis中不存对象,那么去数据库DB中查询结果返回。然后顺便存数据到redis中。
if (CollectionUtils.isEmpty(data)) {
return new ArrayList<>();
}
return data.stream().map(info->{
if (info instanceof HotelUnit){
return (HotelUnit)info;
}
return null;
}).filter(Objects::nonNull).collect(Collectors.toList());
}
}
(4)测试类
@Api(tags = {"本地缓存API"})
@Slf4j
@RestController
public class TestHotel {
@Autowired
HotelUnitCache hotelUnitCache;
@ApiOperation(value = "本地缓存")
@PutMapping("/tests/caffeine/getById")
public HotelUnit getById(
@NotNull @RequestParam(value = "hotel_id") Long hotelId) {
//根据酒店 本地缓存查省份编码
HotelUnit hotelUnit = hotelUnitCache.getById(hotelId).orElse(null);
return hotelUnit;
}
}
(5)执行结果演示
1、postman
2、首次调用会经过下图中 的1和2。第二次调用 只会进1获取对应的key,不会去2。
3、最后结果