JetCache源码解析——配置加载和初始化

JetCache自动化配置加载

JetCache的配置加载主要是在jetcache-autoconfigure模块中完成的,其中加载配置的核心类是JetCacheAutoConfiguration,主要是用于创建全局性的一些Bean,例如全局缓存配置类GlobalCacheConfig,AutoConfigureBeans和SpringConfigProvider等,源码如下:

@Configuration
@ConditionalOnClass(GlobalCacheConfig.class)
@ConditionalOnMissingBean(GlobalCacheConfig.class)
@EnableConfigurationProperties(JetCacheProperties.class)
@Import({RedisAutoConfiguration.class,CaffeineAutoConfiguration.class,MockRemoteCacheAutoConfiguration.class,LinkedHashMapAutoConfiguration.class,RedisLettuceAutoConfiguration.class,RedisSpringDataAutoConfiguration.class,RedissonAutoConfiguration.class})
public class JetCacheAutoConfiguration {public static final String GLOBAL_CACHE_CONFIG_NAME = "globalCacheConfig";@Bean(destroyMethod = "shutdown")@ConditionalOnMissingBeanpublic SpringConfigProvider springConfigProvider(@Autowired ApplicationContext applicationContext,@Autowired GlobalCacheConfig globalCacheConfig,@Autowired(required = false) EncoderParser encoderParser,@Autowired(required = false) KeyConvertorParser keyConvertorParser,@Autowired(required = false) Consumer<StatInfo> metricsCallback) {return new JetCacheBaseBeans().springConfigProvider(applicationContext, globalCacheConfig,encoderParser, keyConvertorParser, metricsCallback);}@Bean(name = "jcCacheManager",destroyMethod = "close")@ConditionalOnMissingBeanpublic SimpleCacheManager cacheManager(@Autowired SpringConfigProvider springConfigProvider) {SimpleCacheManager cacheManager = new SimpleCacheManager();cacheManager.setCacheBuilderTemplate(springConfigProvider.getCacheBuilderTemplate());return cacheManager;}@Bean@ConditionalOnMissingBeanpublic AutoConfigureBeans autoConfigureBeans() {return new AutoConfigureBeans();}@Beanpublic static BeanDependencyManager beanDependencyManager() {return new BeanDependencyManager();}@Bean(name = GLOBAL_CACHE_CONFIG_NAME)public GlobalCacheConfig globalCacheConfig(AutoConfigureBeans autoConfigureBeans, JetCacheProperties props) {GlobalCacheConfig _globalCacheConfig = new GlobalCacheConfig();_globalCacheConfig = new GlobalCacheConfig();_globalCacheConfig.setHiddenPackages(props.getHiddenPackages());_globalCacheConfig.setStatIntervalMinutes(props.getStatIntervalMinutes());_globalCacheConfig.setAreaInCacheName(props.isAreaInCacheName());_globalCacheConfig.setPenetrationProtect(props.isPenetrationProtect());_globalCacheConfig.setEnableMethodCache(props.isEnableMethodCache());_globalCacheConfig.setLocalCacheBuilders(autoConfigureBeans.getLocalCacheBuilders());_globalCacheConfig.setRemoteCacheBuilders(autoConfigureBeans.getRemoteCacheBuilders());return _globalCacheConfig;}
}

无论是使用内存缓存LinkedHashMap和caffeine,亦或是通过lettuce、redisson和spring-data-redis来操作Redis服务缓存数据,其自动加载配置的操作基本上都是相似的。

例如LinkedHashMapAutoConfiguration完成LinkedHashMap类型的缓存初始化操作,源码如下:

/*** LinkedHashMap自动配置类*/
@Component
@Conditional(LinkedHashMapAutoConfiguration.LinkedHashMapCondition.class)
public class LinkedHashMapAutoConfiguration extends EmbeddedCacheAutoInit {public LinkedHashMapAutoConfiguration() {super("linkedhashmap");}@Override/*** 初始化缓存* @param ct            配置树* @param cacheAreaWithPrefix    缓存区域名称(包括前缀)* @return              初始化后的缓存*/protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {LinkedHashMapCacheBuilder builder = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder();parseGeneralConfig(builder, ct);return builder;}/*** LinkedHashMap条件类*/public static class LinkedHashMapCondition extends JetCacheCondition {public LinkedHashMapCondition() {super("linkedhashmap");}}
}

其中LinkedHashMapCondition是一个自定义的条件类,如果spring boot项目的application.yml文件中jetcache.local.${areaName} .type或jetcache.remote.${areaName}.type为linkedhashmap时,就会构建LinkedHashMapAutoConfiguration对应的bean,JetCacheCondition类的源码如下:

public abstract class JetCacheCondition extends SpringBootCondition {/*** 缓存类型数组*/private String[] cacheTypes;/*** 构造方法* @param cacheTypes 缓存类型数组*/protected JetCacheCondition(String... cacheTypes) {Objects.requireNonNull(cacheTypes, "cacheTypes can't be null");Assert.isTrue(cacheTypes.length > 0, "cacheTypes length is 0");this.cacheTypes = cacheTypes;}/*** 判断条件是否匹配* @param conditionContext 条件上下文* @param annotatedTypeMetadata 注解类型元数据* @return 匹配结果*/@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {ConfigTree ct = new ConfigTree((ConfigurableEnvironment) conditionContext.getEnvironment(), "jetcache.");if (match(ct, "local.") || match(ct, "remote.")) {return ConditionOutcome.match();} else {return ConditionOutcome.noMatch("no match for " + cacheTypes[0]);}}/*** 判断是否匹配指定前缀* @param ct 配置树* @param prefix 前缀* @return 是否匹配*/private boolean match(ConfigTree ct, String prefix) {Map<String, Object> m = ct.subTree(prefix).getProperties();Set<String> cacheAreaNames = m.keySet().stream().map((s) -> s.substring(0, s.indexOf('.'))).collect(Collectors.toSet());final List<String> cacheTypesList = Arrays.asList(cacheTypes);return cacheAreaNames.stream().anyMatch((s) -> cacheTypesList.contains(m.get(s + ".type")));}
}

同理,如果想要使用redisson或lettuce中间件来进行远程缓存的处理,则对应的自动化配置类的源码如下:

@Configuration
@Conditional(RedissonAutoConfiguration.RedissonCondition.class)
public class RedissonAutoConfiguration {private static final String CACHE_TYPE = "redisson";public static class RedissonCondition extends JetCacheCondition {public RedissonCondition() {super(CACHE_TYPE);}}@Beanpublic RedissonAutoInit redissonAutoInit() {return new RedissonAutoInit();}
}

在上面的RedissonAutoConfiguration.RedissonCondition条件类中对应的cacheTypes为redisson,即当spring boot项目的application.yml文件中jetcache.local.${areaName} .type或jetcache.remote.${areaName}.type为redisson时,就会构建RedissonAutoConfiguration对应的bean。

同理,加载lettuce对应的自动化加载的源码如下:

@Configuration
@Conditional(RedisLettuceAutoConfiguration.RedisLettuceCondition.class)
public class RedisLettuceAutoConfiguration {public static final String AUTO_INIT_BEAN_NAME = "redisLettuceAutoInit";public static class RedisLettuceCondition extends JetCacheCondition {public RedisLettuceCondition() {super("redis.lettuce");}}@Bean(name = {AUTO_INIT_BEAN_NAME})public RedisLettuceAutoInit redisLettuceAutoInit() {return new RedisLettuceAutoInit();}
}

JetCache自动初始化

上面介绍的都是各种缓存方式的自动化配置的类,可以发现当自动化配置的Bean被创建后,都会创建AbstractCacheAutoInit对应的Bean。JetCache的自动化的类图如下:

上面所有类型的缓存初始化类最终都继承了InitializingBean,InitializingBean是Spring框架中的一个接口,它定义了一个方法 afterPropertiesSet()。当一个bean实例化后,Spring容器会自动调用该方法来完成一些初始化操作。

抽象类AbstractCacheAutoInit在实现InitializingBean接口时,重写了afterPropertiesSet() 方法来实现一些特定的初始化逻辑。在 Spring 容器完成AbstractCacheAutoInit类型的bean的属性注入后,会调用AbstractCacheAutoInit类的afterPropertiesSet() 方法来执行这些初始化逻辑。

我们先看一下AbstractCacheAutoInit类的afterPropertiesSet()方法,源码如下:

/*** 在属性设置之后调用的方法*/
@Override
public void afterPropertiesSet() {// 如果还未初始化if (!inited) {// 上锁reentrantLock.lock();try{// 如果还未初始化if (!inited) {// 处理本地缓存构建器process("jetcache.local.", autoConfigureBeans.getLocalCacheBuilders(), true);// 处理远程缓存构建器process("jetcache.remote.", autoConfigureBeans.getRemoteCacheBuilders(), false);// 设置已初始化标志为trueinited = true;}}finally {// 解锁reentrantLock.unlock();}}
}

上面的代码相对比较简单,首先会判断是否已经进行初始化了,如果没有就先用锁,然后处理本地和远程缓存的处理器。实例autoConfigureBeans中的方法getLocalCacheBuilders()和getRemoteCacheBuilders()是用于获取本地和远程的缓存构建者,类AutoConfigureBeans的源码很简单,我们简单的看一下即可,不做详细介绍。AutoConfigureBeans源码如下:

public class AutoConfigureBeans {private Map<String, CacheBuilder> localCacheBuilders = new HashMap<>();private Map<String, CacheBuilder> remoteCacheBuilders = new HashMap<>();private Map<String, Object> customContainer = Collections.synchronizedMap(new HashMap<>());public Map<String, CacheBuilder> getLocalCacheBuilders() {return localCacheBuilders;}public void setLocalCacheBuilders(Map<String, CacheBuilder> localCacheBuilders) {this.localCacheBuilders = localCacheBuilders;}public Map<String, CacheBuilder> getRemoteCacheBuilders() {return remoteCacheBuilders;}public void setRemoteCacheBuilders(Map<String, CacheBuilder> remoteCacheBuilders) {this.remoteCacheBuilders = remoteCacheBuilders;}public Map<String, Object> getCustomContainer() {return customContainer;}public void setCustomContainer(Map<String, Object> customContainer) {this.customContainer = customContainer;}
}

其中AutoConfigureBeans类的Bean是在JetCacheAutoConfiguration类中被创建的。我们继续看AbstractCacheAutoInit类中process方法的逻辑,其源码如下:

    /*** 处理缓存区域** @param prefix 缓存前缀* @param cacheBuilders 缓存构建器的映射* @param local 是否为本地缓存*/private void process(String prefix, Map cacheBuilders, boolean local) {// 创建配置树解析器ConfigTree resolver = new ConfigTree(environment, prefix);// 获取缓存区域的属性集合Map<String, Object> m = resolver.getProperties();// 获取缓存区域的名称集合Set<String> cacheAreaNames = resolver.directChildrenKeys();// 遍历缓存区域名称集合for (String cacheArea : cacheAreaNames) {// 获取缓存区域类型final Object configType = m.get(cacheArea + ".type");// 判断缓存区域类型是否匹配指定的类型名称boolean match = Arrays.stream(typeNames).anyMatch((tn) -> tn.equals(configType));// 如果匹配失败,则继续下个循环if (!match) {continue;}// 获取缓存区域的子树ConfigTree ct = resolver.subTree(cacheArea + ".");// 初始化缓存区域的缓存logger.info("init cache area {} , type= {}", cacheArea, typeNames[0]);CacheBuilder c = initCache(ct, local ? "local." + cacheArea : "remote." + cacheArea);// 将缓存构建器添加到映射中cacheBuilders.put(cacheArea, c);}}

这个函数用于处理缓存区域。首先根据给定的前缀和环境创建一个配置树解析器,并获取缓存区域的属性和名称集合。然后遍历缓存区域名称集合,获取每个区域的类型,并与给定的类型名称进行比较。如果类型匹配,则初始化该缓存区域的缓存,并将缓存构建器添加到给定的映射中。

上面的方法会针对每一个areaName分别去调用initCache方法进行初始化操作,接下来,我们先看一下initCache方法的源码:

protected abstract CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix);

AbstractCacheAutoInit类的initCache方法是一个抽象方法,这就需要在子类中实现该方法,我们接下来就开始分析主要的几个内存和Redis缓存的自动初始化类的源码了。

内存缓存自动初始化

LinkedHashMapAutoConfiguration类是基于LinkedHashMap来创建的内存缓存自动配置类,该类继承自EmbeddedCacheAutoInit抽象类,该类重写了initCache方法,用于内存缓存的初始化操作,该方法会创建用于创建LinkedHashMap类型的内存缓存的创建者,并调用parseGeneralConfig方法解析JetCache内存缓存的配置。

为了便于理解,我们先看一下JetCache中关于内存缓存的配置信息:

jetcache:statIntervalMinutes: 15areaInCacheName: falsehidePackages: com.alibabalocal:default:type: caffeinelimit: 100keyConvertor: fastjson2 #其他可选:fastjson/jacksonexpireAfterWriteInMillis: 100000otherArea:type: linkedhashmaplimit: 100keyConvertor: noneexpireAfterWriteInMillis: 100000

配置通用说明如下:

属性默认值说明
jetcache.statIntervalMinutes0统计间隔,0表示不统计
jetcache.areaInCacheNametrue(2.6-) false(2.7+)jetcache-anno把cacheName作为远程缓存key前缀,2.4.3以前的版本总是把areaName加在cacheName中,因此areaName也出现在key前缀中。2.4.4以后可以配置,为了保持远程key兼容默认值为true,但是新项目的话false更合理些,2.7默认值已改为false。
jetcache.hiddenPackages@Cached和@CreateCache自动生成name的时候,为了不让name太长,hiddenPackages指定的包名前缀被截掉
jetcache.local.${area}.type缓存类型。tair、redis为当前支持的远程缓存;linkedhashmap、caffeine为当前支持的本地缓存类型
jetcache.local.${area}.keyConvertorfastjson2key转换器的全局配置,2.6.5+已经支持的keyConvertor:fastjson2/jackson
2.6.5-只有一个已经实现的keyConvertor:fastjson。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none,此时通过equals方法来识别key。方法缓存必须指定keyConvertor
jetcache.local.${area}.limit100每个缓存实例的最大元素的全局配置,仅local类型的缓存需要指定。注意是每个缓存实例的限制,而不是全部,比如这里指定100,然后用@CreateCache创建了两个缓存实例(并且注解上没有设置localLimit属性),那么每个缓存实例的限制都是100
jetcache.local.${area}.expireAfterWriteInMillis无穷大以毫秒为单位指定超时时间的全局配置(以前为defaultExpireInMillis)
jetcache.local.${area}.expireAfterAccessInMillis0需要jetcache2.2以上,以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。0表示不使用这个功能。

内存缓存中的常用的配置主要是type、keyConvertor、limit、expireAfterWriteInMillis和expireAfterAccessInMillis。由于系统所在的服务器对每个服务的占用内存比较敏感,所以需要对内存缓存中的数据量进行限制,这就需要配置limit,如果不配置则每个缓存实例就默认是100个,一旦某个缓存实例的缓存达到limit的限制后,新的缓存数据就无法保存到内存缓存中了,所以合理配置limit的值很重要,鉴于可以在查询函数中通过配置@Cached注解中的localLimit来设置每个缓存实例的limit,且@Cached注解中的localLimit优先于全局配置中的limit。keyConvertor可以使用默认的配置,即linkedhashmap类型的缓存使用none,caffeine类型的缓存使用fastjson2。@Cached注解中的keyConvertor来设置每个缓存实例的keyConvertor,且@Cached注解中的keyConvertor优先于全局配置中的keyConvertor。

initCache方法的源码如下:

    /*** 重写方法,用于初始化缓存* @param ct 缓存的配置树* @param cacheAreaWithPrefix 缓存区服(包括前缀)* @return 返回构建好的缓存*/@Overrideprotected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {LinkedHashMapCacheBuilder builder = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder();parseGeneralConfig(builder, ct);return builder;}

创建LinkerHashMap类型的缓存构建者的源码如下:

public class LinkedHashMapCacheBuilder<T extends EmbeddedCacheBuilder<T>> extends EmbeddedCacheBuilder<T> {/*** LinkedHashMapCacheBuilder的内部类*/public static class LinkedHashMapCacheBuilderImpl extends LinkedHashMapCacheBuilder<LinkedHashMapCacheBuilderImpl> {}/*** 创建一个LinkedHashMapCacheBuilderImpl实例* * @return 返回LinkedHashMapCacheBuilderImpl实例*/public static LinkedHashMapCacheBuilderImpl createLinkedHashMapCacheBuilder() {return new LinkedHashMapCacheBuilderImpl();}/*** 私有构造方法*/protected LinkedHashMapCacheBuilder() {buildFunc((c) -> new LinkedHashMapCache((EmbeddedCacheConfig) c));}
}

在上面构建的LinkedHashMapCacheBuilder实例时,LinkedHashMapCacheBuilder的构造函数会初始化相应的缓存类LinkedHashMapCache。这里紧急简单介绍一下,后面会详细就缓存构建者进行描述。

CaffeineAutoConfiguration类是基于Caffeine来创建的内存缓存自动配置类,该类继承自EmbeddedCacheAutoInit抽象类,该类重写了initCache方法,该方法会创建用于创建Caffeine类型的内存缓存的创建者,并调用parseGeneralConfig方法解析JetCache的配置。源码如下:

@Overrideprotected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {CaffeineCacheBuilder builder = CaffeineCacheBuilder.createCaffeineCacheBuilder();parseGeneralConfig(builder, ct);return builder;}

同样,关于构建CaffeineCacheBuilder相应的代码逻辑也放在后面再进行介绍。

内存缓存LinkedHashMap和caffeine的自动初始化类都是继承自EmbeddedCacheAutoInit抽象类,主要是解析配置的内存缓存的最大缓存数量。EmbeddedCacheAutoInit类的源码如下:

public abstract class EmbeddedCacheAutoInit extends AbstractCacheAutoInit {public EmbeddedCacheAutoInit(String... cacheTypes) {super(cacheTypes);}@Overrideprotected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {super.parseGeneralConfig(builder, ct);EmbeddedCacheBuilder ecb = (EmbeddedCacheBuilder) builder;ecb.limit(Integer.parseInt(ct.getProperty("limit", String.valueOf(CacheConsts.DEFAULT_LOCAL_LIMIT))));}
}

解析配置文件的逻辑放到下面的JetCache配置解析章节进行讲述,这里不做过多描述。

EmbeddedCacheAutoInit

我们上面提到内存缓存的自动化配置类的父类都是EmbeddedCacheAutoInit,所以这里我们需要先来看一下类EmbeddedCacheAutoInit的源码。

public abstract class EmbeddedCacheAutoInit extends AbstractCacheAutoInit {public EmbeddedCacheAutoInit(String... cacheTypes) {super(cacheTypes);}@Overrideprotected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {super.parseGeneralConfig(builder, ct);EmbeddedCacheBuilder ecb = (EmbeddedCacheBuilder) builder;ecb.limit(Integer.parseInt(ct.getProperty("limit", String.valueOf(CacheConsts.DEFAULT_LOCAL_LIMIT))));}
}

上面的parseGeneralConfig方法会先调用父类的parseGeneralConfig方法解析常用配置,然后将配置中的limit同步到EmbeddedCacheBuilder实例的limit属性中。

接下来,我们再来看一下EmbeddedCacheAutoInit的父类AbstractCacheAutoInit的parseGeneralConfig方法的源码如下:

    // 解析通用配置protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {// 获得缓存构建器AbstractCacheBuilder acb = (AbstractCacheBuilder) builder;// 设置key转换器acb.keyConvertor(new ParserFunction(ct.getProperty("keyConvertor", KeyConvertor.FASTJSON2)));// 获取expireAfterWriteInMillis配置String expireAfterWriteInMillis = ct.getProperty("expireAfterWriteInMillis");// 保持与2.1版本的兼容性,如果未找到expireAfterWriteInMillis,尝试获取defaultExpireInMillis配置if (expireAfterWriteInMillis == null) {expireAfterWriteInMillis = ct.getProperty("defaultExpireInMillis");}// 如果expireAfterWriteInMillis配置存在,则将其转换为long型并设置到acb对象中if (expireAfterWriteInMillis != null) {acb.setExpireAfterWriteInMillis(Long.parseLong(expireAfterWriteInMillis));}// 获取expireAfterAccessInMillis配置String expireAfterAccessInMillis = ct.getProperty("expireAfterAccessInMillis");// 如果expireAfterAccessInMillis配置存在,则将其转换为long型并设置到acb对象中if (expireAfterAccessInMillis != null) {acb.setExpireAfterAccessInMillis(Long.parseLong(expireAfterAccessInMillis));}}

这个函数用于解析通用配置信息。首先,将传入的builder对象转换为AbstractCacheBuilder类型,并设置keyConvertor为通过ct对象获取的属性值或默认值。然后,从ct对象获取expireAfterWriteInMillis属性值,如果该属性值为空,则使用defaultExpireInMillis属性值。最后,将获取到的expireAfterWriteInMillis和expireAfterAccessInMillis属性值转换为long类型,并设置到acb对象中。

Redis缓存自动初始化

在RedissonAutoConfiguration类中redissonAutoInit()函数会创建RedissonAutoInit对应的bean;在RedisLettuceAutoConfiguration类中redisLettuceAutoInit()函数会创建RedisLettuceAutoInit对应的bean。同理RedisSpringDataAutoConfiguration类中springDataRedisAutoInit()函数会创建SpringDataRedisAutoInit对应的bean。根据上面的JetCache的自动化的类图可以发现,RedissonAutoInit、RedisLettuceAutoInit和SpringDataRedisAutoInit都是ExternalCacheAutoInit的子类,所以主要是初始化函数initCache方法的实现会有所不同,我们在下面会着重的就RedissonAutoInit、RedisLettuceAutoInit和SpringDataRedisAutoInit的initCache方法进行详细的介绍一下。

鉴于下面的初始化方法中会获取JetCache的配置创建Redis连接,所以我们优先看一下JetCache中关于远程缓存的配置说明,会便于下面的源码的理解。

jetcache:statIntervalMinutes: 15areaInCacheName: falsehidePackages: com.alibabaremote:default:type: redis.lettucekeyConvertor: fastjson2broadcastChannel: projectAmode: cluster    #redis模式,cluster:集群模式#readFrom: slavePreferreduri:- redis://127.0.0.1:7000 #redis服务的//IP:Port- redis://127.0.0.1:7001- redis://127.0.0.1:7002otherArea:type: redis.lettucekeyConvertor: fastjson2 #其他可选:fastjson/jacksonbroadcastChannel: projectAvalueEncoder: java #其他可选:kryo/kryo5valueDecoder: java #其他可选:kryo/kryo5poolConfig:minIdle: 5maxIdle: 20maxTotal: 50host: ${redis.host}port: ${redis.port}

配置通用说明如下

属性默认值说明
jetcache.remote.${area}.type缓存类型。tair、redis为当前支持的远程缓存;linkedhashmap、caffeine为当前支持的本地缓存类型
jetcache.remote.${area}.keyConvertorfastjson2key转换器的全局配置,2.6.5+已经支持的keyConvertor:fastjson2/jackson
2.6.5-只有一个已经实现的keyConvertor:fastjson。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none,此时通过equals方法来识别key。方法缓存必须指定keyConvertor
jetcache.remote.${area}.valueEncoderjava序列化器的全局配置。仅remote类型的缓存需要指定,2.7+可选java/kryo/kryo5;2.6-可选java/kryo
jetcache.remote.${area}.valueDecoderjava序列化器的全局配置。仅remote类型的缓存需要指定,2.7+可选java/kryo/kryo5;2.6-可选java/kryo
jetcache.remote.${area}.expireAfterWriteInMillis无穷大以毫秒为单位指定超时时间的全局配置(以前为defaultExpireInMillis)
jetcache.remote.${area}.broadcastChannel

jetcahe2.7的两级缓存支持更新以后失效其他JVM中的local cache,但多个服务共用redis同一个channel可能会造成广播风暴,需要在这里指定channel,你可以决定多个不同的服务是否共用同一个channel。如果没有指定则不开启。

jetcache.remote.${area}.mode缓存模式,cluster:集群模式
jetcache.remote.${area}.uri缓存服务的URI,可以支持多个
jetcache.remote.${area}.ip缓存服务的IP地址
jetcache.remote.${area}.port缓存服务的端口

上表中${area}对应@Cached和@CreateCache的area属性。注意如果注解上没有指定area,默认值是"default"。

RedissonAutoInit

RedissonAutoInit继承自抽象类ExternalCacheAutoInit,在initCache方法中主要是获取所有RedissonClient的bean映射,如果找到了多个RedissonClient的bean,则根据配置决定使用哪个RedissonClient。解析spring boot项目的application.yml文件中jetcache.remote .${areaName}中的Redis配置,创建用于创建Redisson类型的内存缓存的创建者RedissonCacheBuilder,并调用parseGeneralConfig方法解析JetCache的配置。源码如下:

/*** 初始化缓存** @param ct            缓存配置树* @param cacheAreaWithPrefix 缓存区域名称(包括前缀)* @return 缓存构建器*/
@Override
protected CacheBuilder initCache(final ConfigTree ct, final String cacheAreaWithPrefix) {// 获取RedissonClient的beanfinal Map<String, RedissonClient> beans = this.context.getBeansOfType(RedissonClient.class);if (beans.isEmpty()) {throw new CacheConfigException("no RedissonClient in spring context");}RedissonClient client = beans.values().iterator().next();if (beans.size() > 1) {// 获取配置树中的redissonClient属性值final String redissonClientName = ct.getProperty("redissonClient");if (Objects.isNull(redissonClientName) || redissonClientName.isEmpty()) {throw new CacheConfigException("redissonClient is required, because there is multiple RedissonClient in Spring context");}if (!beans.containsKey(redissonClientName)) {throw new CacheConfigException("there is no RedissonClient named " + redissonClientName + " in Spring context");}client = beans.get(redissonClientName);}// 创建外部缓存构建器并设置RedissonClientfinal ExternalCacheBuilder<?> builder = RedissonCacheBuilder.createBuilder().redissonClient(client);// 解析通用配置并添加到构建器中parseGeneralConfig(builder, ct);return builder;
}

函数的功能是初始化缓存,具体实现如下:

  • 获取所有RedissonClient的bean映射,如果找到了多个RedissonClient的bean,则根据配置决定使用哪个RedissonClient。
  • 创建一个ExternalCacheBuilder对象,并将RedissonClient设置为其属性。
  • 解析通用配置并添加到CacheBuilder中。

RedisLettuceAutoInit

RedisLettuceAutoInit继承自抽象类ExternalCacheAutoInit,在initCache方法相较于RedissonAutoInit类的initCache方法方法的逻辑也比较复杂,解析spring boot项目的application.yml文件中jetcache.remote .${areaName}中的Redis配置,创建Redis连接,如果配置了集群模式,则需要创建集群模式的Redis连接;如果配置的是单节点模式,则会创建单节点的Redis连接,创建用于创建Lettuce类型的内存缓存的创建者RedisLettuceCacheBuilder,并调用parseGeneralConfig方法解析JetCache的配置。最后将客户端和连接对象存入自动配置的自定义容器中,并返回一个包含相关配置的ExternalCacheBuilder对象。源码如下:

@Override
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {// 获取Lettuce缓存的URI配置Map<String, Object> map = ct.subTree("uri"/*there is no dot*/).getProperties();// 获取读取模式String readFromStr = ct.getProperty("readFrom");String mode = ct.getProperty("mode");// 获取异步结果超时时间long asyncResultTimeoutInMillis = ct.getProperty("asyncResultTimeoutInMillis", CacheConsts.ASYNC_RESULT_TIMEOUT.toMillis());// 判断是否启用广播通道boolean enablePubSub = parseBroadcastChannel(ct) != null;ReadFrom readFrom = null;// 根据读取模式字符串获取ReadFrom枚举类型if (readFromStr != null) {readFrom = ReadFrom.valueOf(readFromStr.trim());}AbstractRedisClient client;StatefulConnection<byte[], byte[]> connection;StatefulRedisPubSubConnection<byte[], byte[]> pubSubConnection = null;// 判断是否配置了URIif (map == null || map.size() == 0) {// 如果未配置URI,则抛出异常throw new CacheConfigException("lettuce uri is required");} else {// 将配置的URI转换为RedisURI对象列表List<RedisURI> uriList = map.values().stream().map((k) -> RedisURI.create(URI.create(k.toString()))).collect(Collectors.toList());if ("Cluster".equalsIgnoreCase(mode)) {// 配置为集群模式client = RedisClusterClient.create(uriList);// 链接集群节点并获取连接对象connection = clusterConnection(ct, readFrom, (RedisClusterClient) client, false);if (enablePubSub) {// 如果启用了广播通道,则获取与集群节点的连接对象pubSubConnection = (StatefulRedisPubSubConnection) clusterConnection(ct, readFrom, (RedisClusterClient) client, true);}} else {// 配置为单节点模式client = RedisClient.create();((RedisClient) client).setOptions(ClientOptions.builder().disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS).build());// 链接单节点并获取连接对象StatefulRedisMasterReplicaConnection c = MasterReplica.connect((RedisClient) client, new JetCacheCodec(), uriList);if (readFrom != null) {// 如果指定了读取模式,则设置连接对象的读取模式c.setReadFrom(readFrom);}connection = c;if (enablePubSub) {// 如果启用了广播通道,则获取与单节点的连接对象pubSubConnection = ((RedisClient) client).connectPubSub(new JetCacheCodec(), uriList.get(0));}}}// 创建外部缓存构建器对象ExternalCacheBuilder externalCacheBuilder = RedisLettuceCacheBuilder.createRedisLettuceCacheBuilder().connection(connection).pubSubConnection(pubSubConnection).redisClient(client).asyncResultTimeoutInMillis(asyncResultTimeoutInMillis);// 解析通用配置parseGeneralConfig(externalCacheBuilder, ct);// eg: "remote.default.client"// 将Redis客户端对象存入自动配置的自定义容器中autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".client", client);// 获取Lettuce连接管理器对象LettuceConnectionManager m = LettuceConnectionManager.defaultManager();// 初始化Lettuce连接管理器m.init(client, connection);// 将Lettuce连接管理器中的连接对象存入自动配置的自定义容器中autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".connection", m.connection(client));// 将Lettuce连接管理器中的命令对象存入自动配置的自定义容器中autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".commands", m.commands(client));// 将Lettuce连接管理器中的异步命令对象存入自动配置的自定义容器中autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".asyncCommands", m.asyncCommands(client));// 将Lettuce连接管理器中的反应式命令对象存入自动配置的自定义容器中autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".reactiveCommands", m.reactiveCommands(client));// 返回外部缓存构建器对象return externalCacheBuilder;
}

在上面的方法中,如果配置的Redis配置是集群模式则会调用clusterConnection方法建立集群模式的Redis连接,相应的源码如下:

/*** 创建一个与Redis集群连接的状态保持连接对象。* @param ct 配置树对象,用于获取配置信息* @param readFrom 读取来源,用于设置连接的读取来源* @param client Redis集群客户端对象* @param pubsub 是否创建一个订阅连接* @return 状态保持连接对象*/
private StatefulConnection<byte[], byte[]> clusterConnection(ConfigTree ct, ReadFrom readFrom, RedisClusterClient client, boolean pubsub) {int enablePeriodicRefresh = ct.getProperty("enablePeriodicRefresh", 60);boolean enableAllAdaptiveRefreshTriggers = ct.getProperty("enableAllAdaptiveRefreshTriggers", true);ClusterTopologyRefreshOptions.Builder topologyOptionBuilder = ClusterTopologyRefreshOptions.builder();if (enablePeriodicRefresh > 0) {topologyOptionBuilder.enablePeriodicRefresh(Duration.ofSeconds(enablePeriodicRefresh));}if (enableAllAdaptiveRefreshTriggers) {topologyOptionBuilder.enableAllAdaptiveRefreshTriggers();}ClusterClientOptions options = ClusterClientOptions.builder().topologyRefreshOptions(topologyOptionBuilder.build()).disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS).build();client.setOptions(options);if (pubsub) {return client.connectPubSub(new JetCacheCodec());} else {StatefulRedisClusterConnection<byte[], byte[]> c = client.connect(new JetCacheCodec());if (readFrom != null) {c.setReadFrom(readFrom);}return c;}
}

SpringDataRedisAutoInit

SpringDataRedisAutoInit继承自抽象类ExternalCacheAutoInit,initCache方法的实现比较简单,先是获取RedisConnectionFactory的Bean,如果有多个就获取jetCache配置中connectionFactory来决定使用哪一个RedisConnectionFactory实例,创建用于创建Lettuce类型的内存缓存的创建者RedisLettuceCacheBuilder,并调用parseGeneralConfig方法解析JetCache的配置。最后将客户端和连接对象存入自动配置的自定义容器中,并返回一个包含相关配置的ExternalCacheBuilder对象。源码如下:

/*** 初始化缓存** @param ct 配置树* @param cacheAreaWithPrefix 缓存区域名称(包括前缀)* @return 缓存构建器*/
@Override
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {// 从应用上下文中获取 RedisConnectionFactory 的 bean 映射Map<String, RedisConnectionFactory> beans = applicationContext.getBeansOfType(RedisConnectionFactory.class);// 如果 bean 映射为空或为空,抛出异常if (beans == null || beans.isEmpty()) {throw new CacheConfigException("no RedisConnectionFactory in spring context");}// 获取一个可用的 RedisConnectionFactory 实例RedisConnectionFactory factory = beans.values().iterator().next();// 如果 Spring 上下文中有多个 RedisConnectionFactory,抛出异常if (beans.size() > 1) {String connectionFactoryName = ct.getProperty("connectionFactory");// 如果 connectionFactoryName 为空,抛出异常if (connectionFactoryName == null) {throw new CacheConfigException("connectionFactory is required, because there is multiple RedisConnectionFactory in Spring context");}// 检查 bean 映射中是否包含指定的 connectionFactoryName,如果不包含,抛出异常if (!beans.containsKey(connectionFactoryName)) {throw new CacheConfigException("there is no RedisConnectionFactory named "+ connectionFactoryName + " in Spring context");}// 使用指定的 connectionFactoryName 获取 RedisConnectionFactory 实例factory = beans.get(connectionFactoryName);}// 创建一个 ExternalCacheBuilder 实例,并使用 RedisSpringDataCacheBuilder 工具类创建构建器,设置 connectionFactoryExternalCacheBuilder builder = RedisSpringDataCacheBuilder.createBuilder().connectionFactory(factory);// 解析通用配置到构建器中parseGeneralConfig(builder, ct);// 返回构建器return builder;
}

ExternalCacheAutoInit

类ExternalCacheAutoInit作为Redis缓存自动化初始类的父类,实现逻辑还是比较简单的,源码如下:

/*** 外部缓存自动初始化抽象类*/
public abstract class ExternalCacheAutoInit extends AbstractCacheAutoInit {public ExternalCacheAutoInit(String... cacheTypes) {super(cacheTypes);}/*** 解析通用配置** @param builder 缓存构建器* @param ct      配置树*/@Overrideprotected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {super.parseGeneralConfig(builder, ct);ExternalCacheBuilder ecb = (ExternalCacheBuilder) builder;ecb.setKeyPrefix(ct.getProperty("keyPrefix"));ecb.setBroadcastChannel(parseBroadcastChannel(ct));ecb.setValueEncoder(new ParserFunction(ct.getProperty("valueEncoder", CacheConsts.DEFAULT_SERIAL_POLICY)));ecb.setValueDecoder(new ParserFunction(ct.getProperty("valueDecoder", CacheConsts.DEFAULT_SERIAL_POLICY)));}/*** 解析广播通道** @param ct 配置树* @return 解析后的广播通道,若为空则返回null*/protected String parseBroadcastChannel(ConfigTree ct) {String broadcastChannel = ct.getProperty("broadcastChannel");if (broadcastChannel != null && !"".equals(broadcastChannel.trim())) {return broadcastChannel.trim();} else {return null;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/603390.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据密集型应用系统设计--第2章 数据模型与查询语言

一、引言 数据模型可能是开发软件最重要的部分,而且还对如何思考待解决的问题都有深远的影响。 大多数应用程序是通过一层一层叠加数据模型来构建的。每一层都面临的关键问题是&#xff1a;如何将其用下一层来表示&#xff1f; 1.作为一名应用程序开发人员&#xff0c;观测现实…

yarn无法加载文件和‘vue-cli-service‘ 不是内部或外部命令解决方法

导致此错误的原因是&#xff0c;PowerShell 执行策略&#xff0c;默认设置为Restricted不加载配置文件或运行脚本。需变更设置为RemoteSigned&#xff0c;变更过程为&#xff1a; 1.运行 Windows PowerShell&#xff08;管理员&#xff09;&#xff0c;执行命令set-ExecutionPo…

使用redis时快速考虑的问题

使用场景 ap组件程序是否容忍极限丢失1s数据是否可以不依赖redis就能实现是否过度依赖redis 数据结构 5种结构选择不同结构有自己的限制&#xff0c;使用前需考虑限制考虑当前业务最适合那种解构&#xff0c;或多种解构混合使用 key设计 大keykey的格式热key敏感数据 过…

Redis的IO多路复用原理解析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

SpringCloud系列篇:入门讲解Spring Cloud是什么

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringCloud的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.Spring Cloud是什么 二.Spring …

通俗易懂的15个Java Lambda表达式案例

文章目录 1. **实现Runnable接口**&#xff1a;2. **事件监听器**&#xff08;如Swing中的ActionListener&#xff09;&#xff1a;3. **集合遍历**&#xff08;使用forEach方法&#xff09;&#xff1a;4. **过滤集合**&#xff08;使用Stream API&#xff09;&#xff1a;5. …

硬盘结构损坏且无法读取恢复方法

硬盘结构损坏且无法读取是计算机存储设备的一种常见故障。当硬盘出现此类问题时&#xff0c;用户往往无法正常访问存储在硬盘中的数据。本文将深入分析硬盘结构损坏且无法读取的潜在原因&#xff0c;并探讨有效的解决方法&#xff0c;以帮助用户恢复数据和正常使用硬盘。 硬盘结…

Vue3 结合typescript 组合式函数(1)

在App.vue文件中 实现鼠标点击文件&#xff0c;显示坐标值 第一种方法 第二种方法&#xff1a;组合式函数 结果&#xff1a; 官网推荐组合函数&#xff1a;https://vueuse.org

进阶学习——Linux系统——程序和进程

目录 一、程序和进程的关系 1.程序 2.进程 2.1线程 2.2协程 3.进程与线程的区别 4.总结 4.1延伸 5.进程使用内存的问题 5.1内存泄漏——Memory Leak 5.2内存溢出——Memory Overflow 5.3内存不足——OOM&#xff08;out of memory&#xff09; 5.4进程使用内存出现…

vue3 里的 ts 类型工具函数

目录 前言一、PropType\<T>二、MaybeRef\<T>三、MaybeRefOrGetter\<T>四、ExtractPropTypes\<T>五、ExtractPublicPropTypes\<T>六、ComponentCustomProperties七、ComponentCustomOptions八、ComponentCustomProps九、CSSProperties 前言 相关 …

报告解读:中国新一代终端安全市场洞察,2023

报告解读 中国新一代终端安全市场洞察 2023 安全防御的“最前线” 01 混沌的企业安全 以下来自CSO们最关注的安全热点问题&#xff1a; Q1我们如何看待当下泛化的终端安全&#xff0c;混合的IT环境企业面临的安全变化&#xff1f; IDC&#xff1a;伴随着全球数字化转型的快…

山西电力市场日前价格预测【2024-01-08】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-01-08&#xff09;山西电力市场全天平均日前电价为247.82元/MWh。其中&#xff0c;最高日前电价为373.22元/MWh&#xff0c;预计出现在18:00。最低日前电价为0.00元/MWh&#xff0c;预计出…

eureka工作原理是什么

EUREKA 是一个基于 RESTful 风格的服务发现系统&#xff0c;它主要用于帮助实现在微服务架构中的服务自动发现与注册。其工作原理主要包括以下几个步骤&#xff1a; 注册中心&#xff1a;EUREKA 中有一个集中的注册中心&#xff0c;所有的服务都将在此注册和发现。注册中心可以…

CentOS中开启mysql挂载

挂载的作用其实说白了就是备份。防止数据库文件损害或者数据库被误删导致数据丢失。 创建一个文件名为my.cnf内容如下 # Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modif…

数据结构第九弹---循环队列

循环队列 1、循环队列的定义2、循环队列的结构3、循环队列的实现3.1、初始化队列3.2、判断是否为空3.3、判断是否为满3.4、入队3.5、出队3.6、返回队头元素3.7、返回队尾元素3.8、销毁队列 4、代码汇总总结 1、循环队列的定义 顺序队列在使用过程中容易出现虚假的满状态&#x…

经典八股文之RocketMQ

核心概念 NameServer nameserver是整个rocketmq的大脑&#xff0c;是rocketmq的注册中心。broker在启动时向所有nameserver注册。生产者在发送消息之前先从 NameServer 获取 Broker 服务器地址列表(消费者一 样)&#xff0c;然后根据负载均衡算法从列表中选择一台服务器进行消…

用通俗易懂的方式讲解:万字长文带你入门大模型

告别2023&#xff0c;迎接2024。大模型技术已成为业界关注焦点&#xff0c;你是否也渴望掌握这一领域却又不知从何学起&#xff1f; 本篇文章将特别针对入门新手&#xff0c;以浅显易懂的方式梳理大模型的发展历程、核心网络结构以及数据微调等关键技术。 如果你在阅读中收获…

优化IP地址管理:实现高效、智能的IP资源监控与分配

在当今高度信息化的时代&#xff0c;IP地址管理已成为企业网络运营的核心环节。为了更好地应对不断增长的网络设备和应用需求&#xff0c;提高企业运营效率&#xff0c;监控易推出的IP地址管理工具&#xff0c;将助力企业实现更高效、更智能的IP地址监控与分配。 一、IP地址概…

Jupyter Lab 入门指南:基础篇

&#x1f31f;&#x1f30c; 欢迎来到知识与创意的殿堂 — 远见阁小民的世界&#xff01;&#x1f680; &#x1f31f;&#x1f9ed; 在这里&#xff0c;我们一起探索技术的奥秘&#xff0c;一起在知识的海洋中遨游。 &#x1f31f;&#x1f9ed; 在这里&#xff0c;每个错误都…

MySQL中的开发基于Python的SQL工具类操作数据库简单示例

操作数据库封装SQL工具类的两种方式 为了更方便的实现基于连接池和pymysql 连接数据库&#xff0c;需开发一个sql工具类来让sql操作更简洁用两张方式来封装SQL工具类 1 &#xff09;单例模式 封装 db.py 工具类 import pymysql from dbutils.pooled_db import PooledDBclas…