前言缓存在现代计算机系统中无处不在。各种硬件和软件组合构成和管理缓存。一个编写良好的计算机程序往往会显示出良好的局部性。在高性能服务架构的设计中,缓存是必不可少的环节。以Java系统为例,我们从传统的硬编码缓存,转变为基于注解的spring-cache框架,极大的提升了我们的效率,代码也更加简洁易维护。但是随着越来越多的项目使用spring-cache,场景也越来越复杂。我们逐渐发现缓存配置代码重复,不能直接在注解上配置缓存策略,不支持多级缓存,不支持缓存自动刷新。逐渐突出。基于这些在业务中遇到的问题,我们构建了一套带注解的二级缓存服务框架。本人在实际设计和构建过程中积累了一些经验,借此机会分享给大家,希望能为业务中使用缓存,尤其是spring-cache场景的朋友们提供一些帮助。1.spring-cache介绍Spring3.1之后引入了注解缓存技术,本质上并不是具体的缓存实现,而是对缓存的一种抽象。不仅可以使用SpEL(SpringExpressionLanguage)定义缓存key和各种条件,还提供开箱即用的缓存暂存方案,还支持与主流专业缓存集成。任何事物都有两个方面。优点那么好,那么缺点或缺点是不是也那么突出呢?spring-cache的问题(在我们看来)不支持在注解上设置缓存策略,需要单独硬编码配置各个方法的缓存策略。它不支持多级缓存。它不支持缓存的自动刷新。它不支持缓存统计。支持数据压缩代码重复不易维护基于以上我们思考的问题,我们造了一个轮子来尝试解决这些问题。这个轮子就是“带注释的二级缓存框架”。2.注解式二级缓存框架介绍注解式二级缓存框架:通过注解实现声明式方法缓存,在用法上类似于spring-cache,提供了比spring-cache更强大的注解。全新的注解二级缓存框架:一级缓存使用本地缓存(目前只支持Caffeine,以后可以扩展),二级缓存使用集中式缓存(目前只支持Redis)).目前支持三种缓存策略:只使用一级缓存,只使用二级缓存,同时使用二级缓存。支持缓存Key的SpEL表达式,自定义生成策略(默认生成策略已经提供)只有在满足条件时才会缓存。仅支持一级缓存、二级缓存或二级缓存。支持值序列化策略配置。默认情况下,GenericJackson2JsonRedisSerializer支持异步加载缓存。它支持自动刷新缓存。Redis客户端选择支持熔断和降级---Delay支持支持缓存数据压缩---Delay支持支持缓存一致性---Delay支持支持缓存监控统计看板---Delay支持支持自定义缓存中间件---Delay支持支持cache手动缓存操作的接口---Delaysupport开胃菜吃完了,接下来开始正餐,一步步介绍设计思路,谈谈如何站在巨人的肩膀上的Spring-cache,努力解决上述问题。3.注解二级缓存框架架构设计3.1注解@EnableCache1,注解@EnableCache引入CacheConfigurationSelector。CacheConfigurationSelector向容器中注入两个Bean2,AutoProxyRegistrar和ProxyCacheAutoConfiguration。AutoProxyRegistrar会保证容器中有一个自动代理创建器(APC),缓存的代理对象最终委托给自动代理创建器。AutoProxyRegistrar在容器启动阶段处理每个bean的创建。如果bean中有一个标有缓存注解的方法,则为其创建一个代理对象,并通过包定义的CacheOperationSourceAdvisorbean3和ProxyCacheAutoConfiguration为容器定义如下基础设施bean。CacheOperationSourceAdvisor用于管理CacheOperationSource和CacheInterceptor。CacheOperationSource用于获取方法调用时最终应用的Cache注解的元数据。CacheInterceptor被包装在目标bean之外,用于操作Cache的AOPAdvice3.2拦截器。由于AutoProxyRegistrar会在容器启动阶段标记一个带有缓存注解的bean创建一个代理对象。这时候我们就可以获得具体的方法和注解元数据。我们把这两部分数据绑定起来,提前缓存起来,这样在调用目标方法的时候,就可以直接从缓存中获取元数据,避免低效的反射影响性能。1.根据目标方法和目标类获取注解元数据。元数据包括缓存名称、缓存键、过期时间、自动刷新时间、本地缓存容量、缓存类型、缓存条件等。2.根据缓存条件是否使用注解缓存,缓存条件支持SpEL表达式,如果为false,会直接执行target方法,true会使用缓存逻辑。3、生成密钥:支持SpEL表达式,可自定义生成规则。默认规则:命名空间、类名、方法名、方法参数用冒号连接。4、获取缓存:根据cacheName和cacheType获取缓存。对应的缓存有本地缓存??、远程缓存和二级缓存。根据key获取缓存结果。如果缓存结果为空,则执行目标方法并缓存结果。否则直接返回缓存结果。3.3获取缓存组件缓存实现类有3个:LocalCache、RemoteCache、TwoLevelCache。每个缓存实现类都集成了特定的缓存中间件。LocalCache可以集成Caffeine、Guava、ehCache等,RemoteCache可以集成Redis、Memcache等,TwoLevelCache是??LocalCache和RemoteCache的组合实现。1、CacheManagerContainer管理所有的CacheManager,每个cacheType对应一个CacheManager的实现。2、CacheManager提供一个缓存来创建bean,管理多个缓存。每个缓存都有一个对应的缓存名称。每个应用程序都可以使用cacheName来隔离缓存。如果cacheName对应的缓存不存在,则注册一个新的。缓存。3、Cache接口提供了缓存的具体操作,如放入、读取、清理等。3.4二级缓存创建二级缓存是因为远程缓存有网络开销,大量的缓存读取会导致远程缓存网络成为整个系统的瓶颈。本地缓存与应用同进程,请求缓存快。网络开销过大,添加本地缓存的目的是减少对远程缓存的读取次数,减少网络开销,进而再次提升程序的响应速度和服务性能。1、从本地缓存中读取数据,存在则直接返回,执行后续具体业务逻辑。2.如果本地缓存不存在,则读取远程缓存。如果远程缓存存在,则更新本地缓存。如果不存在,则从数据源读取,然后依次更新远程缓存和本地缓存,再执行后续的具体业务逻辑。3.5自动刷新缓存,防止某个缓存失效时访问量突然增加,所有访问数据库的请求都可能导致数据库挂掉;适用场景:key数量比较少,访问量大,加载开销高。1、如果读取缓存时元数据自动刷新时间有值,则根据缓存key、目标方法、刷新时间创建一个给定初始延迟的间隔任务。任务的自动执行间隔就是自动刷新时间。任务执行时,会根据缓存key和目标方法Reload缓存,保持缓存有效。2、根据自动刷新时间会产生一个停止刷新时间。如果缓存key访问间隔超过停止刷新时间或缓存key过期,定时任务将被删除,释放资源,避免无效缓存刷新。3、二级缓存刷新缓存顺序为:先刷新远程缓存,然后根据Redis的pub/sub模式监听并操作本地缓存的删除动作,然后第一次查看本地缓存——-->检查Redis缓存--->返回源码。4.远程缓存的自动刷新使用了分布式锁。同一个key,全球只有一台机器可以自动刷新。3.6注解@Cacheable1和@Cacheable可以应用于方法或标记在类上。当标记在方法上时,表示该方法支持缓存。当标记在一个类上时,表示该类的所有方法都支持缓存名默认值描述值空字符串缓存名,cacheName的别名cacheName空字符串缓存名key空字符串缓存key,支持SpEL表达式,提供默认生成策略ttl10minutes过期时间,d/h/m/s四个时间单位选项,分别代表日/时/分/秒,ttl="10m"表示10分钟过期时间refreshTime空字符串自动刷新时间,d/h/m/s四个时间单位选项,分别代表天/小时/分/秒maximumSize5000本地缓存容量cacheTypeREMOTE缓存类型,LOCAL/REMOTE/BOTH三个选项,分别代表本地缓存/集中缓存/二级缓存条件空字符串指定只有当满足条件,为空则认为无条件缓存,支持SpEL表达式。2.Key默认生成规则:Namespace、类名、方法名、方法参数用冒号连接。3.如果ttl设置为空:表示缓存永不过期。3.7缓存配置下面是一个例子。具体参数值可以根据自己的业务情况进行调整。auto:cache:local:type:caffeineremote:type:redishost:localhost#服务器地址port:6379#服务器连接端口timeout:2000#连接超时时间(毫秒)pool:min-idle:2#最小空闲连接数max-idle:10#最大空闲连接数max-active:20#连接池最大数据库连接数max-wait:200#建立连接的最大等待时间key-serializer:org.springframework.data.redis.serializer.StringRedisSerializer#key序列化策略value-serializer:org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer#value序列化策略namespace:testCache#缓存命名空间前缀,最终缓存格式为:testCache:xxx:xxallow-null-values:true#防止缓存穿透四、总结与展望本文主要记录商业资源组在使用spring-cache过程中遇到的问题以及注解二级缓存se的设计思路服务框架。通过一步步拆解,问题点一一分解。该框架也在实际项目中通过了数千万次的验证,为我们的在线服务提供了良好的性能。构建一套完整的服务框架需要不断的迭代功能开发,未来会逐步支持以下功能:增加熔断降级增加缓存数据压缩增加缓存一致性增加缓存监控统计看板增加自定义缓存中间件增加缓存接口Manual缓存操作参考https://github.com/ben-manes/caffeine/wiki/Benchmarkshttps://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/expressions.html作者介绍:王云鹏经销商技术部-业务资源组2017年加入汽车之家经销商事业部,目前负责智能展厅核心功能开发
