前言
說道緩存,大家想到的是一定是Redis,確實在國內Redis被大量應用,推上了新的高度!但是不一定所有的場合都要使用Redis,例如服務器資源緊缺,集成不方便的時候就可以考慮使用本地緩存。
簡介
緩存應該是每個系統都要考慮的架構,緩存不僅可以加速系統的訪問速度還可以提升系統的性能。如我們需要經常訪問的高頻熱點數據,如果把它緩存起來就能有效減少數據庫服務器的壓力。手機驗證碼等有一定的失效時間,我們就可以考慮使用緩存,等失效時間過了,就刪掉驗證碼。因此市面上緩存組件也層出不進,常見的有
- JCache:Java緩存API。由JSR107定義,定義了5個核心接口,分別是CachingProvider,CacheManager,Cache,Entry和Expriy
- EhCache:純Java的進程內緩存框架,jvm虛擬機中緩存、速度快,效率高,是Hibernate中默認的CacheProvider,但是共享緩存與集群分布式應用整合不方便
- Redis:生態完善,通過socket訪問緩存服務,效率上是比EhCache低的,但是在集群模式、分布式應用上就比較成熟,是大型應用首先中間件
- Caffeine:Caffeine是使用Java8對Guava緩存的重寫版本,有人稱它為緩存之王
優點
- 快速
- 簡單
- 多種緩存策略(設置有效期等)
- 緩存數據有兩級:內存和磁盤,因此無需擔心容量問題
- 緩存數據會在虛擬機重啟的過程中寫入磁盤
- 可以通過RMI、可插入API等方式進行分布式緩存
- 具有緩存和緩存管理器的偵聽接口
- 支持多緩存管理器實例,以及一個實例的多個緩存區域
- 提供Hibernate的緩存實現
jar
<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><version>2.10.6</version>
</dependency>
本質
就是簡單看下SpringBoot關于緩存處理源碼是怎么寫的。說到底就是上面所說AOP思想,通過動態代理實現,目標方法讓代理對象去調用,調用之前先看下緩存有沒有,如果有,則從緩存上獲取結果并直接返回。如果緩存中沒有則執行目標方法,并把方法執行結果緩存。
CacheManager->Cache->Element
Ehcache.xml
默認會加載calsspath下的ehcache.xml,加載失敗會加載Ehcache.jar下的ehcache-failsafe.xml
<cache name="account"eternal="false"diskPersistent="false"maxElementsInMemory="10000"timeToIdleSeconds="120"timeToLiveSeconds="120"memoryStoreEvictionPolicy="LRU"></cache><cache name="smsCache"eternal="false"diskPersistent="false"maxElementsInMemory="1000"timeToIdleSeconds="60"timeToLiveSeconds="600"memoryStoreEvictionPolicy="LRU"></cache><!--
name 緩存名稱
eternal true表示對象永不過期,此時會忽略timeToIdleSeconds和timeToLiveSeconds屬性,默認為false
timeToIdleSeconds 設定允許對象處于空閑狀態的最長時間,以秒為單位。當對象自從最近一次被訪問后,如果處于空閑狀態的時間超過了timeToIdleSeconds屬性值,這個對象就會過期,EHCache將把它從緩存中清空。只有當eternal屬性為false,該屬性才有效。如果該屬性值為0,則表示對象可以無限期地處于空閑狀態
timeToLiveSeconds 設定對象允許存在于緩存中的最長時間,以秒為單位。當對象自從被存放到緩存中后,如果處于緩存中的時間超過了 timeToLiveSeconds屬性值,這個對象就會過期,EHCache將把它從緩存中清除。只有當eternal屬性為false,該屬性才有效。如果該屬性值為0,則表示對象可以無限期地存在于緩存中。timeToLiveSeconds必須大于timeToIdleSeconds屬性,才有意義
maxElementsInMemory 內存中最大緩存對象數;maxElementsInMemory界限后,會把溢出的對象寫到硬盤緩存中。注意:如果緩存的對象要寫入到硬盤中的話,則該對象必須實現了Serializable接口才行
memoryStoreEvictionPolicy 當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。可選策略有:LRU(最近最少使用,默認策略)、FIFO(先進先出)、LFU(最少訪問次數)
maxElementsOnDisk 硬盤中最大緩存對象數,若是0表示無窮大
overflowToDisk 是否保存到磁盤,當系統宕機時
diskPersistent 是否緩存虛擬機重啟期數據,是否持久化磁盤緩存,當這個屬性的值為true時,系統在初始化時會在磁盤中查找文件名為cache名稱,后綴名為index的文件,這個文件中存放了已經持久化在磁盤中的cache的index,找到后會把cache加載到內存,要想把cache真正持久化到磁盤,寫程序時注意執行net.sf.ehcache.Cache.put(Element element)后要調用flush()方法
diskSpoolBufferSizeMB 這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區
diskExpiryThreadIntervalSeconds 磁盤失效線程運行時間間隔,默認為120秒
clearOnFlush 內存數量最大時是否清除
-->
策略
Ehcache 提供了多種緩存策略,可以根據實際需求選擇合適的策略。其中,最常用的包括:
- LRU(Least Recently Used):移除最近最少使用的緩存項。(時間)
- LFU(Least Frequently Used):移除最不經常使用的緩存項。(次數)
- FIFO(First In, First Out):先進先出,即移除最早加入的緩存項。
- TTL(Time To Live):根據緩存項的過期時間來判斷是否要移除該項。
隨機替換:隨機選擇一項進行移除。
緩存注解-自動
JCache(JSR-107)的注解去大大簡化我們的開發。
相關注解和概念 | 說明 |
---|---|
Cache | 緩存接口,定義緩存操作。實現有:RedistCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 緩存管理器,管理各種緩存(Cache)組件 |
@Cacheable | 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存 |
@CacheEvict | 清空緩存 |
@CachePut | 保證方法被調用,又希望結果被緩存 |
@EnableCaching | 開啟基于注解的緩存 |
keyGenerator | 緩存數據時key生成策略 |
serialize | 緩存數據時value序列化策略 |
JSR-107緩存注解使用
光知道SpringBoot集成Ehcache還不夠,我們還要知道怎么使用。然而ehcache在實際應用中可以抽離出來單獨使用,但是需要自己手動實例化與put數據,一般不推薦。推薦配合JSR-107緩存注解去使用(因為Spring都給我們封裝好了),Spring的緩存功能豐富,它還提供了很多注解,去完成不同的場景~其中使用最多的是@Cacheable注解。
@Cacheable (使用最多)
/*
@Cacheable幾個屬性:1.cacheNames/value:緩存組件的名字 2.key:指定緩存數據使用的key,默認使用方法參數的值,可以編寫SpEL進行指定,如#id就是參數的值3.keyGenerator:key的生成器,可以自己指定key的生成器的組件id,使用時key/keyGenerator只能二選一4.cacheManager:指定緩存管理器,或者cacheResolver指定獲取解析器5.condition:指定復合條件的情況下才緩存,如condition = "#id > 0"6.unless:否定緩存,當unless指定的條件為true,方法的返回值就不會被緩存,如 unless = "#result == null"7.sync:是否啟用異步模式,如果啟用unless就不支持了。默認為false8.緩存的值就是方法返回的結果
*/
// 使用示例@Override@Cacheable(value = "account",key = "#id")public Account getAccountById(int id) {final Account account = accountDao.selectById(id);return Objects.requireNonNull(account);}
@CachePut
即調用方法,又更新緩存數據。如修改了某個數據庫的某個數據,同時更新緩存
執行過程:
- 先調用目標方法
- 將目標方法的結果緩存起來
value:命名空間
key: 主鍵
@CachePut(value = "account",key = "#id")public Account getAccountById(int id) {final Account account = accountDao.selectById(id);return Objects.requireNonNull(account);}
@CacheEvict
緩存清除,一般在刪除數據方法中使用
@CacheEvict(value = "account",key = "#id")public boolean deleteAccountById(int id) {return accountDao.deleteById(id) > 0;}
CacheManager-手動
getCache
put
get
remove
@Autowiredprivate CacheManager cacheManager;@Overridepublic String sendSms(String phoneNumber) {final String uuid = UUID.randomUUID().toString().substring(0, 4);cacheManager.getCache("smsCache").put(new Element(phoneNumber, uuid));return uuid;}@Overridepublic boolean checkSms(String phoneNumber, String code) {final Cache smsCache = cacheManager.getCache("smsCache");smsCache.final Element element = smsCache.get(phoneNumber);final String smscode = (String) element.getObjectValue();return code.equals(smscode);}