layering-cache是在Spring Cache基础上扩展而来的一个缓存框架,主要目的是在使用注解的时候支持配置过时时间。layering-cache实际上是一个两级缓存,一级缓存使用Caffeine做为本地缓存,二级缓存使用redis做为集中式缓存。而且基于redis的Pub/Sub作缓存的删除,因此它是一个适用于分布式环境下的一个缓存系统。html
<dependency> <groupId>com.github.xiaolyuh</groupId> <artifactId>layering-cache-aspectj</artifactId> <version>${layering.version}</version> </dependency>
compile 'com.github.xiaolyuh:layering-cache:${layering.version}'
声明RedisTemplate 声明RedisTemplatejava
声明CacheManager和LayeringAspectgit
/** * 多级缓存配置 * * @author yuhao.wang3 */ @Configuration @EnableAspectJAutoProxy public class CacheConfig { @Bean public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { return new LayeringCacheManager(redisTemplate); } @Bean public LayeringAspect layeringAspect() { return new LayeringAspect(); } }
引入layering-cache 就能够了github
<dependency> <groupId>com.github.xiaolyuh</groupId> <artifactId>layering-cache-starter</artifactId> <version>${layering.version}</version> </dependency>
直接在须要缓存的方法上加上Cacheable、CacheEvict、CachePut注解。web
@Cacheable(value = "user:info", depict = "用户信息缓存", firstCache = @FirstCache(expireTime = 4, timeUnit = TimeUnit.SECONDS), secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 3, forceRefresh = true, timeUnit = TimeUnit.SECONDS)) public User getUser(User user) { logger.debug("调用方法获取用户名称"); return user; }
@CachePut(value = "user:info", key = "#userId", depict = "用户信息缓存", firstCache = @FirstCache(expireTime = 4, timeUnit = TimeUnit.SECONDS), secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 3, forceRefresh = true, timeUnit = TimeUnit.SECONDS)) public User putUser(long userId) { User user = new User(); user.setUserId(userId); user.setAge(31); user.setLastName(new String[]{"w", "y", "h"}); return user; }
@CacheEvict(value = "user:info", key = "#userId") public void evictUser(long userId) { } @CacheEvict(value = "user:info", allEntries = true) public void evictAllUser() { }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {CacheConfig.class}) public class CacheCoreTest { private Logger logger = LoggerFactory.getLogger(CacheCoreTest.class); @Autowired private CacheManager cacheManager; @Test public void testCacheExpiration() { FirstCacheSetting firstCacheSetting = new FirstCacheSetting(10, 1000, 4, TimeUnit.SECONDS, ExpireMode.WRITE); SecondaryCacheSetting secondaryCacheSetting = new SecondaryCacheSetting(10, 4, TimeUnit.SECONDS, true); LayeringCacheSetting layeringCacheSetting = new LayeringCacheSetting(firstCacheSetting, secondaryCacheSetting); String cacheName = "cache:name"; String cacheKey = "cache:key1"; LayeringCache cache = (LayeringCache) cacheManager.getCache(cacheName, layeringCacheSetting); cache.get(cacheKey, () -> initCache(String.class)); cache.put(cacheKey, "test"); cache.evict(cacheKey); cache.clear(); } private <T> T initCache(Class<T> t) { logger.debug("加载缓存"); return (T) "test"; } }
表示用的方法的结果是能够被缓存的,当该方法被调用时先检查缓存是否命中,若是没有命中再调用被缓存的方法,并将其返回值放到缓存中。redis
名称 | 默认值 | 说明 |
---|---|---|
value | 空字符串数组 | 缓存名称,cacheNames的别名 |
cacheNames | 空字符串数组 | 缓存名称 |
key | 空字符串 | 缓存key,支持SpEL表达式 |
depict | 空字符串 | 缓存描述(在缓存统计页面会用到) |
ignoreException | true | 是否忽略在操做缓存中遇到的异常,如反序列化异常 |
firstCache | 一级缓存配置 | |
secondaryCache | 二级缓存配置 |
一级缓存配置项spring
名称 | 默认值 | 说明 |
---|---|---|
initialCapacity | 10 | 缓存初始Size |
maximumSize | 5000 | 缓存最大Size |
expireTime | 9 | 缓存有效时间 |
timeUnit | TimeUnit.MINUTES | 时间单位,默认分钟 |
expireMode | ExpireMode.WRITE | 缓存失效模式,ExpireMode.WRITE:最后一次写入后到期失效,ExpireMode.ACCESS:最后一次访问后到期失效 |
二级缓存配置项数据库
名称 | 默认值 | 说明 |
---|---|---|
expireTime | 5 | 缓存有效时间 |
preloadTime | 1 | 缓存主动在失效前强制刷新缓存的时间,建议是 expireTime * 0.2 |
timeUnit | TimeUnit.HOURS | 时间单位,默认小时 |
forceRefresh | false | 是否强制刷新(直接执行被缓存方法) |
isAllowNullValue | false | 是否容许缓存NULL值 |
magnification | 1 | 非空值和null值之间的时间倍率,默认是1。isAllowNullValue=true才有效 |
将数据放到缓存中json
名称 | 默认值 | 说明 |
---|---|---|
value | 空字符串数组 | 缓存名称,cacheNames的别名 |
cacheNames | 空字符串数组 | 缓存名称 |
key | 空字符串 | 缓存key,支持SpEL表达式 |
depict | 空字符串 | 缓存描述(在缓存统计页面会用到) |
ignoreException | true | 是否忽略在操做缓存中遇到的异常,如反序列化异常 |
firstCache | 一级缓存配置 | |
secondaryCache | 二级缓存配置 |
删除缓存数组
名称 | 默认值 | 说明 |
---|---|---|
value | 空字符串数组 | 缓存名称,cacheNames的别名 |
cacheNames | 空字符串数组 | 缓存名称 |
key | 空字符串 | 缓存key,支持SpEL表达式 |
allEntries | false | 是否删除缓存中全部数据,默认状况下是只删除关联key的缓存数据,当该参数设置成 true 时 key 参数将无效 |
ignoreException | true | 是否忽略在操做缓存中遇到的异常,如反序列化异常 |
Layering Cache 的监控统计功能默认是开启的
直接在声明CacheManager Bean的时候将stats设置成true。
/** * 多级缓存配置 * * @author yuhao.wang3 */ @Configuration @EnableAspectJAutoProxy public class CacheConfig { @Bean public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { LayeringCacheManager layeringCacheManager = new LayeringCacheManager(redisTemplate); // 默认开启统计功能 layeringCacheManager.setStats(true); return layeringCacheManager; } ... }
在application.properties文件中添加如下配置便可
layering-cache.stats=true
Layering Cache内置提供了一个LayeringCacheServlet用于展现缓存的统计信息。
这个LayeringCacheServlet的用途包括:
日志格式:
Layering Cache 统计信息:{"cacheName":"people1","depict":"查询用户信息1","firstCacheMissCount":3,"firstCacheRequestCount":4575,"hitRate":99.9344262295082,"internalKey":"4000-15000-8000","layeringCacheSetting":{"depict":"查询用户信息1","firstCacheSetting":{"allowNullValues":true,"expireMode":"WRITE","expireTime":4,"initialCapacity":10,"maximumSize":5000,"timeUnit":"SECONDS"},"internalKey":"4000-15000-8000","secondaryCacheSetting":{"allowNullValues":true,"expiration":15,"forceRefresh":true,"preloadTime":8,"timeUnit":"SECONDS","usePrefix":true},"useFirstCache":true},"missCount":3,"requestCount":4575,"secondCacheMissCount":3,"secondCacheRequestCount":100,"totalLoadTime":142}
- 若是项目集成了ELK之类的日志框架,那咱们能够直接基于以上日志作监控和告警。
- 统计数据每隔一分钟采集一次
LayeringCacheServlet是一个标准的javax.servlet.http.HttpServlet,须要配置在你web应用中的WEB-INF/web.xml中。
<servlet> <servlet-name>layeringcachestatview</servlet-name> <servlet-class>com.github.xiaolyuh.tool.servlet.layeringcacheservlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>layeringcachestatview</servlet-name> <url-pattern>/layering-cache/*</url-pattern> </servlet-mapping>
根据配置中的url-pattern来访问内置监控页面,若是是上面的配置,内置监控页面的首页是/layering-cache/index.html。例如: http://localhost:8080/layering-cache/index.html http://localhost:8080/xxx/layering-cache/index.html
须要配置Servlet的 loginUsername 和 loginPassword这两个初始参数。 示例以下:
<!-- 配置监控信息显示页面 --> <servlet> <servlet-name>LayeringCacheStatView</servlet-name> <servlet-class>com.github.xiaolyuh.tool.servlet.LayeringCacheServlet</servlet-class> <init-param> <!-- 用户名 --> <param-name>loginUsername</param-name> <param-value>admin</param-value> </init-param> <init-param> <!-- 密码 --> <param-name>loginPassword</param-name> <param-value>admin</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>LayeringCacheStatView</servlet-name> <url-pattern>/layering-cache/*</url-pattern> </servlet-mapping>
LayeringCacheStatView展现出来的监控信息比较敏感,是系统运行的内部状况,若是你须要作访问控制,能够配置allow和deny这两个参数。好比:
<servlet> <servlet-name>LayeringCacheStatView</servlet-name> <servlet-class>com.github.xiaolyuh.tool.servlet.LayeringCacheServlet</servlet-class> <!--配置白名单--> <init-param> <param-name>allow</param-name> <param-value>128.242.127.1/24,128.242.128.1</param-value> </init-param> <!--配置黑名单--> <init-param> <param-name>deny</param-name> <param-value>128.242.127.4</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>LayeringCacheStatView</servlet-name> <url-pattern>/layering-cache/*</url-pattern> </servlet-mapping>
判断规则
- deny优先于allow,若是在deny列表中,就算在allow列表中,也会被拒绝。
- 若是allow没有配置或者为空,则容许全部访问
ip配置规则 配置的格式
128.242.127.1,128.242.127.1/24/24表示,前面24位是子网掩码,比对的时候,前面24位相同就匹配。
不支持IPV6 因为匹配规则不支持IPV6,配置了allow或者deny以后,会致使IPV6没法访问。
须要配置Servlet的 enableUpdate参数。若是设置成false,那么将不能重置统计数据和删除缓存。 示例以下:
<!-- 配置监控信息显示页面 --> <servlet> <servlet-name>LayeringCacheStatView</servlet-name> <servlet-class>com.github.xiaolyuh.tool.servlet.LayeringCacheServlet</servlet-class> <init-param> <!-- 是否开启更新数据权限 --> <param-name>enableUpdate</param-name> <param-value>false</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>LayeringCacheStatView</servlet-name> <url-pattern>/layering-cache/*</url-pattern> </servlet-mapping>
#是否开启缓存统计默认值true spring.layering-cache.stats=true #是否启用LayeringCacheServlet默认值true spring.layering-cache.layering-cache-servlet-enabled=true spring.layering-cache.url-pattern=/layering-cache/* #用户名 spring.layering-cache.login-username=admin #密码 spring.layering-cache.login-password=admin #是否容许更新数据 spring.layering-cache.enable-update=true # IP白名单(没有配置或者为空,则容许全部访问) spring.layering-cache.allow=127.0.0.1,192.168.163.1/24 # IP黑名单 (存在共同时,deny优先于allow) spring.layering-cache.deny=192.168.1.73
咱们能够发现Caffeine和Redis的优缺点正好相反,因此他们能够有效的互补。
基于redis pub/sub 实现一级缓存的更新同步。主要缘由有两点:
该框架最核心的接口有两个,一个是Cache接口:主要负责具体的缓存操做,如对缓存的增删改查;一个是CacheManager接口:主要负责对Cache的管理,最经常使用的方法是经过缓存名称获取对应的Cache。
Cache接口:
public interface Cache { String getName(); Object getNativeCache(); Object get(Object key); <T> T get(Object key, Class<T> type); <T> T get(Object key, Callable<T> valueLoader); void put(Object key, Object value); Object putIfAbsent(Object key, Object value); void evict(Object key); void clear(); CacheStats getCacheStats(); }
CacheManager接口:
public interface CacheManager { Collection<Cache> getCache(String name); Cache getCache(String name, LayeringCacheSetting layeringCacheSetting); Collection<String> getCacheNames(); List<CacheStatsInfo> listCacheStats(String cacheName); void resetCacheStat(); }
在CacheManager里面Cache容器默认使用ConcurrentMap<String, ConcurrentMap<String, Cache>> 数据结构,以此来知足同一个缓存名称能够支持不一样的缓存过时时间配置。外层key就是缓存名称,内层key是"一级缓存有效时间-二级缓存有效时间-二级缓存自动刷新时间"缓存时间所有转换成毫秒值,如"1111-2222-3333"。
简单思路就是缓存的命中和未命中使用LongAdder先暂存到内存,在经过定时任务同步到redis,并重置LongAdde,集中计算缓存的命中率等。监控统计API直接获取redis中的统计数据作展现分析。
由于多是集群环境,为了保证数据准确性在同步数据到redis的时候须要加一个分布式锁。
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.8.3.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.18.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.31</version> </dependency> <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo-shaded</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version> </dependency>
做者博客:https://www.jianshu.com/u/4e6e80b98daa
做者邮箱: xiaolyuh@163.com