配置
TB支持两种缓存:Caffeine和Redis,通过配置cache.type来指定使用哪种缓存。
位于 org.thingsboard.server.cache
Caffeine
配置类:CaffeineCacheConfiguration
@Configuration
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@ConfigurationProperties(prefix = "caffeine")
@EnableCaching
@Data
@Slf4j
public class CaffeineCacheConfiguration {
// 具体缓存的配置
private Map<String, CacheSpecs> specs;
// 构造CacheManager对象,被其管理的缓存可以在Cacheable等注解中使用
@Bean
public CacheManager cacheManager() {
log.trace("Initializing cache: {} specs {}", Arrays.toString(RemovalCause.values()), specs);
SimpleCacheManager manager = new SimpleCacheManager();
if (specs != null) {
// 加载配置文件中定义的具体缓存配置
List<CaffeineCache> caches = specs.entrySet().stream()
.map(entry -> buildCache(entry.getKey(), entry.getValue())).collect(Collectors.toList());
manager.setCaches(caches);
}
//SimpleCacheManager is not a bean (will be wrapped), so call initializeCaches manually
manager.initializeCaches();
// 支持事务
return new TransactionAwareCacheManagerProxy(manager);
}
// 构造一个缓存对象
private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) {
final Caffeine<Object, Object> caffeineBuilder
= Caffeine.newBuilder()
// 使用基于权重的回收策略:设置最大权重,当累计权重超过该值时,会触发驱逐
// 相比基于容量(key的数量)的回收策略,权重能考虑值的大小,
// 尤其是集合类型的值,可以更精确的控制缓存的大小
.maximumWeight(cacheSpec.getMaxSize())
// 设置值的权重计算方法
.weigher(collectionSafeWeigher())
// 设置基于时间的回收策略:在写入之后指定时间时删除
.expireAfterWrite(cacheSpec.getTimeToLiveInMinutes(), TimeUnit.MINUTES)
.ticker(ticker());
return new CaffeineCache(name, caffeineBuilder.build());
}
@Bean
public Ticker ticker() {
return Ticker.systemTicker();
}
// 计算值的权重,如果值的类型是集合,则权重是集合的大小
private Weigher<? super Object, ? super Object> collectionSafeWeigher() {
return (Weigher<Object, Object>) (key, value) -> {
if (value instanceof Collection) {
return ((Collection) value).size();
}
return 1;
};
}
}
配置类上的 @ConditionalOnProperty(prefix = “cache”, value = “type”, havingValue = “caffeine”, matchIfMissing = true)说明当配置使用caffeine缓存时,该配置类才生效.
回收策略
public static void main(String[] args) throws InterruptedException {
Cache<Integer, Integer> cache = Caffeine.newBuilder()
//设置最大权重为20
.maximumWeight(20)
//设置值的权重等于值
.weigher((key, value) -> (int) value)
.removalListener((Integer key, Object value, RemovalCause cause) ->
System.out.printf("Key %s was removed because (%s)%n", key, cause))
.build();
// 插入权重为19的键值对
cache.put(0, 19);
//打印缓存个数,结果为1
System.out.println(cache.estimatedSize());
// 插入权重为2的键值对,累计权重为21,会触发空间回收,基于TinyLRU算法驱逐key=0的键值对
cache.put(1, 2);
// 继续插入,累计权重为4,不会触发空间回收
cache.put(2, 2);
//稍微休眠一秒
Thread.sleep(1000);
//打印缓存个数,结果为2
System.out.println(cache.estimatedSize());
}
// 控制台打印:
//1
//Key 0 was removed because (SIZE) key 0 被驱逐因为缓存大小限制
//2
支持事务
假设方法updateUser被@CachePut修饰,则更新User后更新缓存
在一个事务方法中先调用updateUser方法,然后调用addUpdateLog方法;
在实际执行时,执行addUpdateLog方法发生异常,导致事务回滚;
如果缓存不支持事务,则会导致缓存脏数据的问题,流程如下:
Redis
配置类:TBRedisCacheConfiguration
// 连接池
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return loadFactory();
}
// 支持Redis多种部署模式,因此由子类定义
protected abstract JedisConnectionFactory loadFactory();
/**
* 传入连接工厂Bean定义RedisCacheManager,支持事务
* Transaction aware RedisCacheManager.
* Enable RedisCaches to synchronize cache put/evict operations with ongoing Spring-managed transactions.
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory cf) {
DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService();
RedisCacheConfiguration.registerDefaultConverters(redisConversionService);
registerDefaultConverters(redisConversionService);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().withConversionService(redisConversionService);
return RedisCacheManager.builder(cf)
.cacheDefaults(configuration)
.transactionAware()
.build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
return template;
}
支持Redis的两种模式:Standalone和Cluster;因此构造连接方法在两个子类中定义:
使用
因为使用了CacheManager因此可以直接使用注解来使用缓存
cacheNmaes指的是使用哪个缓存,当使用Caffeine缓存时会根据配置文件加载多个缓存Bean: