一.为何不用Map作为本地缓存库
- 内存管理
没有对使用的内存做限制,需要手动编写代码来控制内存,没有自动的内存管理机制。 - 缓存过期策略
没有缓存过期策略,如:LRU,LFU,FIFO - 容量规划
没有容量限制 - Map是否是线程安全的
可能会出现并发控制问题 - 持久化
服务器重启,Map中数据消失 - 多实例数据同步及一致性
Map是在服务器的一个实例中的,部署多个实例时,Map中数据会产生不一致的情况
二.本地缓存相较于第三方缓存有什么优势
- 不经过网络传输,响应速度快
其中本地缓存无法进行持久化,以及保证多实例数据一致性,第三方缓存相比于本地缓存又存在响应速度上的劣势。因此我们引入了JetCache,可以灵活的选择本地、第三方、或多级缓存来解决这个问题。
三、jetCache
1.功能简介
- 有接口与api两种缓存实现,接口使用方便,api使用类似于map,可以更细粒度的控制缓存。
- @CreateCache
- @Cached
- 可选择内存缓存,分布式缓存,或者同时存在。同时存在时优先访问内存
- 自动刷新策略,防止某个缓存失效,突然访问量增大,导致数据库挂掉(缓存雪崩)
- 有不严格的分布式锁,对同一key,全局只有一台机器自动刷新
2.缓存实现流程
2.1 配置部分
- 首先在spring.factories文件中配置了
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.alicp.jetcache.autoconfigure.JetCacheAutoConfiguration
作用是:由于jetcache是jar包引入,不在我们项目包下,因此需要通过@EnableAutoConfiguration来扫描解析spring.factories文件,完成自动装配。 - 在JetCacheAutoConfiguration配置类中完成了GlobalCacheConfig的配置注入,主要注入了两个实体类的参数
- JetCacheProperties 存储了springboot.yml中的配置信息,通过 @ConfigurationProperties(prefix = “jetcache”)注解注入属性。
- AutoConfigureBeans
存储了jetcache的local和remote配置,
是在AbstractCacheAutoInit类中完成注入,定义在afterPropertiesSet()方法中,这个方法是在初始化Bean时执行。
//在InitializingBean接口中定义的方法,这个方法将在所有的属性被初始化后,init-method方法前调用
@Override
public void afterPropertiesSet() {
if (!inited) {
synchronized (this) {
if (!inited) {
//注入local
process("jetcache.local.", autoConfigureBeans.getLocalCacheBuilders(), true);
process("jetcache.remote.", autoConfigureBeans.getRemoteCacheBuilders(), false);
inited = true;
}
}
}
}
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);
}
}
也就是说当我们实例化AutoConfigureBeans的子类时(如CaffeineAutoConfiguration或RedisAutoInit),会调用afterPropertiesSet()方法,完成相应属性的注入。
注入的配置信息存入到localCacheBuilders和remoteCacheBuilders两个属性中,map中的key对应配置的default。
//AutoConfigureBeans的属性
private Map<String, CacheBuilder> localCacheBuilders = new HashMap<>();
private Map<String, CacheBuilder> remoteCacheBuilders = new HashMap<>();
//springboot.yml中的jetcache配置实例
jetcache:
statIntervalMinutes: 15
areaInCacheName: false
local:
default:
type: caffeine
keyConvertor: fastjson
remote:
default:
type: redis.lettuce
keyConvertor: fastjson
valueEncoder: java
valueDecoder: java
uri: redis://${spring.redis.password}@${spring.redis.host}:${spring.redis.port}/
2.2 注解部分
- AOP Advisor是AOP的顶层抽象,用来管理Advice和PointCut。
Advisor有两个实现类,PointcutAdvisor和IntroductionAdvisor,其中IntroductionAdvisor只能应用于类级别的拦截,只能使用Introduction型的Advice。PointcutAdvisor可使用任何类型的PointCut,和几乎任何类型的Advice。
CacheAdvisor继承了AbstractBeanFactoryPointcutAdvisor。 - CachePointcut继承了抽象类StaticMethodMatcherPointcut,这里使用了静态方法切入点。
静态切入点只在代理创建时执行一次,而不是在运行期间每次调用方法时都执行,所以性能比动态切入点要好,是首选的切入点方式。 - 注解具体生效,是在CachePointcut的matchesImpl()方法中,调用了CacheUtils的parse方法。
public static boolean parse(CacheInvokeConfig cac, Method method) {
boolean hasAnnotation = false;
CachedAnnoConfig cachedConfig = parseCached(method);//反射获取@Cached注解信息
if (cachedConfig != null) {
cac.setCachedAnnoConfig(cachedConfig);
hasAnnotation = true;
}
boolean enable = parseEnableCache(method);
if (enable) {
cac.setEnableCacheContext(true);
hasAnnotation = true;
}
List<CacheInvalidateAnnoConfig> invalidateAnnoConfigs = parseCacheInvalidates(method);
if (invalidateAnnoConfigs != null) {
cac.setInvalidateAnnoConfigs(invalidateAnnoConfigs);
hasAnnotation = true;
}
CacheUpdateAnnoConfig updateAnnoConfig = parseCacheUpdate(method);
if (updateAnnoConfig != null) {
cac.setUpdateAnnoConfig(updateAnnoConfig);
hasAnnotation = true;
}
if (cachedConfig != null && (invalidateAnnoConfigs != null || updateAnnoConfig != null)) {
throw new CacheConfigException("@Cached can't coexists with @CacheInvalidate or @CacheUpdate: " + method);
}
return hasAnnotation;
}
在这里分别判断了,@Cached,@CacheInvalidate,@CacheUpdate注解是否存在,@CacheRefresh注解的解析在parseCached()方法中,因为它是基于@Cached注解的。
2.3 缓存注解生效
- JetCacheInterceptor会对代理的方法进行拦截,来完成注解对应的操作。
- 被拦截的方法最终会通过AOP的实现来调用JetCacheInterceptor的invoke方法来实现策略,最终会调用CacheHandler.doInvoke()方法。
private static Object doInvoke(CacheInvokeContext context) throws Throwable {
CacheInvokeConfig cic = context.getCacheInvokeConfig();
CachedAnnoConfig cachedConfig = cic.getCachedAnnoConfig();
if (cachedConfig != null && (cachedConfig.isEnabled() || CacheContextSupport._isEnabled())) {
return invokeWithCached(context);
} else if (cic.getInvalidateAnnoConfigs() != null || cic.getUpdateAnnoConfig() != null) {
return invokeWithInvalidateOrUpdate(context);
} else {
return invokeOrigin(context);
}
}
这里根据不同的注解,调用不同的invoke方法。