天天看點

緩存不止redis,學會使用本地緩存ehcache

作者:二哥學Java

0. 引言

随着redis的普及,更多的同學對redis分布式緩存更加熟悉,但在一些實際場景中,其實并不需要用到redis,使用更加簡單的本地緩存即可實作我們的緩存需求。

今天,我們一起來看看本地緩存元件ehcache

1. ehcache簡介

1.1 簡介

ehcache是基于java開發的本地緩存元件,無需單獨安裝部署,隻要引入jar包就可利用它來實作緩存。

所謂本地緩存,就是指存儲在JVM堆記憶體中的臨時緩存資料,當然ehcache本身也支援Off-Heap Store機制來使用堆外記憶體,本地緩存相較于redis性能和響應速度更高。

Ehcache的本地緩存還支援過期時間、最大容量、持久化等特性,使得它可以适用于各種不同的緩存場景。

官方文檔位址:www.ehcache.org/documentati…

1.2 本地緩存與redis的差別

本地緩存與redis的差別在于:

  • 架構:
  • 本地緩存基于單機架構,即資料僅本機可用,無法共享給其他服務。除非使用服務調用來擷取。而redis本身基于分布式架構,支援跨服務調取。 是以當資料需要分布式調用時,則适用于redis,如果資料隻需要本地擷取,則可考慮本地緩存
  • 性能:
  • 本地緩存本身基于本機記憶體,沒有網絡IO消耗,是以性能上大大高于redis,但是如果資料量較大,則還是要考慮使用redis,本地緩存僅适用于資料量小、結構簡單的資料場景,不适合複雜的業務資料
  • 功能拓展:
  • redis支援持久化、訂閱模式、叢集、主從模式等,而ehcache更傾向于簡單的緩存功能場景,雖然也支援持久化,但是本身并不建議用它來做大型或複雜場景的緩存。如果場景比較簡單輕量,對延遲有較高要求,則可選擇本地緩存

2. ehcache使用

1、建立一個springboot項目,這裡我的springboot版本為2.6.13

2、引入ehcahe元件依賴

這裡需要注意的是net.sf.ehcache是ehcache2.X 與 org.ehcache是echcache3.X,兩個版本配置有差別

xml複制代碼        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
            <version>2.6.13</version>
        </dependency>
           

3、在啟動類上添加@EnableCaching注解,開啟緩存

java複制代碼@SpringBootApplication
@EnableCaching
public class LocalCacheDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(LocalCacheDemoApplication.class, args);
    }

}
           

4、在配置檔案application.yml中添加配置

yml複制代碼spring:
  profiles: 
  	active: dev
  cache:
    type: ehcache
    ehcache:
      config: classpath:ehcache.xml
           

5、在resources檔案夾下建立配置檔案ehcache.xml,注意這裡單獨建立了一個name為user的緩存,用于後續儲存使用者資訊緩存。如果有不同的緩存需要使用不同的name的,需要單獨建立cache标簽

标簽介紹:

defaultCache: 預設緩存配置标簽 cache 指定緩存标簽,name表示緩存名稱 diskStore 資料存儲磁盤路徑

屬性介紹:

eternal: 緩存是否永久有效,如果為 true 則忽略timeToIdleSeconds 和 timeToLiveSeconds maxElementsInMemory:最多緩存多少個key overflowToDisk: 緩存超限時是否寫入磁盤,預設為true overflowToOffHeap: 堆記憶體超限時是否使用堆外記憶體,企業版功能,收費 diskPersistent:緩存是否持久化 timeToLiveSeconds:緩存多久過期 timeToIdleSeconds:緩存多久沒有被通路就過期 diskExpiryThreadIntervalSeconds:磁盤緩存過期檢查線程運作時間間隔 memoryStoreEvictionPolicy:緩存淘汰政策, LFU:最近最少使用的元素先移出; FIFO:最先進入的元素被移出; LRU:使用越少的元素被移出 maxBytesLocalHeap:緩存最大占用JVM堆記憶體,0表示不限制,機關支援K、M或G maxBytesLocalOffHeap: 緩存最大占用堆外記憶體,0表示不限制,機關支援K、M或G,企業版功能,收費 maxBytesLocalDisk:緩存最大占用磁盤,0表示不限制,機關支援K、M或G
xml複制代碼<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
         
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToLiveSeconds="3600"
            timeToIdleSeconds="0"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"/>

    <cache
            name="user"
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToLiveSeconds="3600"
            timeToIdleSeconds="0"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"/>

<!--    存儲到磁盤時的路徑-->
    <diskStore path="/Users/wuhanxue/Downloads/ehcache" />

</ehcache>
           

6、緩存使用,在擷取方法中使用@Cacheable注解,在更新方法中使用@CachePut注解。 我這裡模拟就沒有通路資料庫查詢資料了,大家在實際書寫的時候可以連接配接上資料源測試

java複制代碼@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping("get")
    @Cacheable(cacheNames = "user", key = "#id")
    public User getById(Integer id) {
        System.out.println("get第一次擷取,不走緩存");
        User user = new User();
        user.setId(id);
        user.setAge(18);
        user.setName("benjamin_"+id);
        user.setSex(true);
        return user;
    }

    @PostMapping("update")
    @CachePut(cacheNames = "user", key = "#search.id")
    public User update(@RequestBody User search) {
        System.out.println("update更新緩存");
        User user = new User();
        Integer id = search.getId();
        user.setId(id);
        user.setAge(search.getAge() != null ? search.getAge()+1 : 0);
        user.setName("update_benjamin_"+id);
        user.setSex(true);
        return user;
    }

}
           

3. 測試

1、調用查詢接口:localhost:8080/user/get?id=1

緩存不止redis,學會使用本地緩存ehcache

2、第一次調用,列印"get第一次擷取,不走緩存"。再調用一次發現沒有列印了,但是資料正常查詢,說明走了緩存

緩存不止redis,學會使用本地緩存ehcache

3、調用更新接口

緩存不止redis,學會使用本地緩存ehcache

4、再調用查詢接口,查詢到的就是更新的資料,說明緩存更新成功

緩存不止redis,學會使用本地緩存ehcache

4. 注意事項

謹慎使用maxElementsInMemory

maxElementsInMemory表示的是最大緩存多少個key,這個配置項謹慎使用,一般我們應該根據占用多少記憶體空間來控制,而不是占用多少個key,如果出現某些key的資料量特别大時,就會導緻key數量沒超過,但記憶體占用超過導緻的OOM了

這個我們通過一個生成大資料量的接口來模拟,其中generateMemoryString方法可以在文末的源碼倉庫中

1、書寫接口

java複制代碼@GetMapping("build")
    @Cacheable(cacheNames = "user", key = "#id")
    public User build(Integer id) {
        System.out.println("get第一次擷取,不走緩存");
        User user = new User();
        user.setId(id);
        user.setAge(18);
        // 生成指定大小的字元串
        user.setName(generateMemoryString(id));
        user.setSex(true);
        return user;
    }
           

2、限制項目JVM記憶體為100m,友善更快模拟出報錯

緩存不止redis,學會使用本地緩存ehcache

3、調用接口localhost:8080/user/build?id=100,因為該接口會生成大資料,占用本地緩存,而JVM緩存又給的100M,是以調用會報錯堆記憶體溢出,如圖所示

緩存不止redis,學會使用本地緩存ehcache

4、是以該配置項要謹慎使用,可以通過maxBytesLocalHeap,maxBytesLocalDisk設定占用多少記憶體、磁盤來替代

xml複制代碼<cache
            name="user"
            eternal="false"
            maxBytesLocalHeap="50M"
            maxBytesLocalDisk="200M"
            overflowToDisk="false"
            diskPersistent="false"
            timeToLiveSeconds="3600"
            timeToIdleSeconds="0"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
    />
           

如果maxBytesLocalHeap和maxElementsInMemory都配置了的,誰先達到配置的值,就觸發

如果單個key值太大,仍然會導緻OOM

雖然我們上面配置了maxBytesLocalHeap來限制最大使用的記憶體,比如我們限制了該值為100M,則如果我們有4個30M的資料進來,那麼就會根據配置的淘汰政策去淘汰之前的key,以騰出空間來裝新的資料

但如果新進來的資料很大,比如超過100M了,那麼就會一下子裝滿記憶體,甚至淘汰之前的key也不行,是以這種情況下還是會導緻OOM的

遇到這種情況,兩種處理辦法,一種是保證不會有大于這個門檻值的資料産生,這個可以通過業務代碼控制,二是設定一個全局錯誤捕捉,捕捉産生的OOM報錯,然後傳回一個兜底或者其他的狀态碼,以此辨別

繼續閱讀