目錄
-
-
- 緩存概述
- springboot整合caffeine
- spring緩存注解的使用
- springboot整合ehcache
-
對于通路頻率高、一段時間内變動頻率低的資料,可以加緩存,以緩解伺服器cpu、資料庫的壓力。如果資料庫連接配接池max-active設定很大但還經常報連接配接不夠,可以考慮緩存部分結果集。
緩存可分為2大類
- 本地緩存:速度快、效率高,但分布式環境下容易出現資料不一緻的問題、且緩存的資料量一般不能太大,常見的比如guava、ehcache、caffeine
- 分布式緩存:速度比本地緩存慢,常用于多個服務、應用之間的資料共享,常見的比如redis、memcached
本地緩存
- 可以自己用ConcurrentLinkedHashMap實作
- ehcache:純java的程序内緩存架構,快速精幹,是hibernate預設的緩存管理器,緩存資料的存儲有記憶體、硬碟兩級,無需擔心容量問題
- caffeine:基于java8,高性能、高命中率、低記憶體占用,性能極高,在spring 2.x中取代guava成為預設的本地緩存元件,隻在記憶體中存儲緩存的資料
分布式緩存
- redis、memcached的相同點:都是把全量資料存儲在記憶體中,高性能,支援海量資料存儲
- redis:資料類型豐富,支援資料持久化,可備份資料至硬碟,容災能力強,可用于存儲緩存之外的其他資料,比如可當做隊列使用
- memcached:隻支援String類型;缺乏認證、安全管制機制,使用時需要将memcached伺服器放在防火牆後;不支援持久化,當機全部資料丢失,一般隻用于存儲緩存
- redis隻使用單核,IO多路複用+減少線程間切換,memcached可以使用多核,存儲少量資料時redis單核性能高于memcached,存儲大量資料時memcached整體性能高于redis。
如何選擇
- 本地緩存極大地提升了效率,如果隻有一台伺服器(一個服務節點),可以使用本地緩存
- 在分布式環境下,本地緩存容易出現節點之間資料不一緻的問題,盡量不要使用本地緩存,使用分布式緩存代替
依賴
建立項目時勾選 I/O -> Spring cache abstraction,并加上caffeine的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
引導類
加spring的緩存注解 @EnableCaching 開啟緩存
yml
spring:
cache:
type: caffeine
cache-names: caffeineCache
caffeine:
spec: initialCapacity=100,maximumSize=1000,expireAfterAccess=1800s
也可以用bean進行配置
/**
* 配置caffeine緩存管理器
*/
@Bean(name = "caffeineCache") //指定緩存管理器名稱
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
//初始容量
.initialCapacity(100)
//最大個數
.maximumSize(1000)
//寫入緩存或最後一次通路該緩存對象後,多長時間内沒通路就過期
.expireAfterAccess(30, TimeUnit.SECONDS));
return cacheManager;
}
caffeine提供三類驅逐政策:基于size(size-based)、基于time(time-based)、基于引用(reference-based),此處是size、time搭配使用。
實體類
@Data
public class Goods implements Serializable {
private Integer goodsId;
private String goodsName;
private BigDecimal goodsPrice;
}
緩存注解一般加在controller或service的方法上,controller常常要記錄請求日志(請求參數、傳回給前端的資料),建議加在service中的方法上。
@Service
public class GoodsService {
@Cacheable(cacheNames = "caffeineCache", key = "#goodsId")
public Goods findGoodsById(Integer goodsId) {
Goods goods = new Goods();
return goods;
}
@CachePut(cacheNames = "caffeineCache", key = "#goods.goodsId")
public Goods updateGoods(Goods goods) {
return goods;
}
@CachePut(cacheNames = "caffeineCache", key = "#goods.goodsId")
public Goods addGoods(Goods goods) {
return goods;
}
@CacheEvict(cacheNames = "caffeineCache", key = "#goodsId")
public void deleteGoods(Integer goodsId) {
}
}
spring提供了通用的緩存注解,由具體的緩存架構提供實作,spring常用的緩存注解如下
- @Cacheable:和查詢搭配使用,先查本地緩存,本地緩存中有就直接傳回結果,不再執行方法;本地緩存中沒有就執行方法,執行成功後将結果寫入本地緩存
- @CacheEvict:和删除搭配使用,如果本地有對應的緩存,執行成功後清除緩存
- @CachePut:執行成功後将傳回值寫入緩存,如果本地沒有對應的緩存則為寫入緩存,如果本地已有相應的緩存,則會更新緩存。insert、update更新緩存需要傳回最新的實體對象,也可以使用@CacheEvict清除緩存,後續查詢時@Cacheable寫入緩存。
這3個注解的寫入|清除緩存都是方法執行成功後才寫入|清除緩存,如果方法執行失敗|抛出異常,則不會寫入|清除緩存。
這3個注解都具有以下屬性
- cacheNames:指定要使用的緩存管理器名稱,也可以使用cacheNames屬性的别名value指定
- key:指定緩存的key
- condition:指定注解生效的條件,當方法執行成功且condition的值為true時才會清除緩存
key、condition可以使用SpEL取值,也可以使用内置對象root
- #root.methodName:目前方法名
- #root.method.name:目前方法
- #root.target:目前方法所屬的對象
- #root.targetClass:目前方法所屬的對象的Class對象
- #root.args:實參表, #root.args[0]表示第一個參數
建立項目時勾選 I/O -> Spring cache abstraction,并加上ehcache的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- ehcache3.x -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.0</version>
</dependency>
<!-- ehcahce 2.x,已不再維護 -->
<!--<dependency>-->
<!-- <groupId>net.sf.ehcache</groupId>-->
<!-- <artifactId>ehcache</artifactId>-->
<!-- <version>2.10.6</version>-->
<!--</dependency>-->
加@EnableCaching
resources下建立ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盤緩存位置。java.io.tmpdir是系統預設的臨時檔案目錄,也可以使用自定義的路徑 -->
<diskStore path="java.io.tmpdir" />
<!-- 預設緩存配置 -->
<defaultCache
overflowToDisk="true"
maxEntriesLocalDisk="5000"
memoryStoreEvictionPolicy="LRU"
eternal="false"
timeToLiveSeconds="3600"
timeToIdleSeconds="600" />
<!--自定義的緩存配置,name指定緩存管理器名稱,可以有多個自定義的緩存配置 -->
<cache name="ehcacheCache"
maxElementsInMemory="2000"
overflowToDisk="true"
memoryStoreEvictionPolicy="LRU"
eternal="false"
timeToLiveSeconds="3600"
timeToIdleSeconds="600" />
</ehcache>
- maxElementsInMemory:記憶體中的最大緩存個數
- overflowToDisk:記憶體最大緩存個數達到上限時是否緩存到硬碟
- maxEntriesLocalDisk:硬碟最大緩存個數
- memoryStoreEvictionPolicy:緩存個數達到上限時的緩存清除政策,FIFO:類似隊列,先進先出,優先清除先放入緩存的;LRU:Least Recently Used,優先清除最近未使用的;LFU:Least Frequently Used,優先清除使用頻率低的。
- eternal:緩存是否永久有效
- timeToLiveSeconds:緩存的有效期,到期自動清除
- timeToIdleSeconds:在有效期内緩存的閑置時間,如果在指定時間範圍内緩存從未被通路,會自動清除
這依然是ehcache2.x的配置檔案,3.x依然相容2.x的方式
spring:
cache:
#指定ehcache配置檔案位置
jcache:
config: classpath:ehcache.xml
# ehcache:
# config: classpath:ehcache.xml
3.x用jcache.config,2.x使用ehcache.config。3.x向下相容,也可以使用ehcache.config。
@Data //要實作序列化接口,緩存至硬碟時要用到
public class Goods implements Serializable{
private Integer goodsId;
private String goodsName;
private BigDecimal goodsPrice;
}
@Service
public class GoodsService {
@Cacheable(cacheNames = "ehcacheCache", key = "#goodsId")
public Goods findGoodsById(Integer goodsId) {
Goods goods = new Goods();
return goods;
}
@CachePut(cacheNames = "ehcacheCache", key = "#goods.goodsId")
public Goods updateGoods(Goods goods) {
return goods;
}
@CachePut(cacheNames = "ehcacheCache", key = "#goods.goodsId")
public Goods addGoods(Goods goods) {
return goods;
}
@CacheEvict(cacheNames = "ehcacheCache", key = "#goodsId")
public void deleteGoods(Integer goodsId) {
}
}