天天看點

本地緩存的使用

目錄

      • 緩存概述
      • 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) {

    }

}
           

繼續閱讀