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