最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Spring?Cache實現Caffeine+Redis二級緩存

 更新時間:2022年03月23日 09:03:18   作者:雨點的名字  
本文主要介紹了基于Spring?Cache實現Caffeine+Redis二級緩存,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

本文主要介紹了基于Spring Cache實現二級緩存(Caffeine+Redis),具體如下:

一、聊聊什么是硬編碼使用緩存?

在學習Spring Cache之前,筆者經常會硬編碼的方式使用緩存。

我們來舉個實際中的例子,為了提升用戶信息的查詢效率,我們對用戶信息使用了緩存,示例代碼如下:

    @Autowire
    private UserMapper userMapper;

    @Autowire
    private RedisCache redisCache;

    //查詢用戶
    public User getUserById(Long userId) {
        //定義緩存key
        String cacheKey = "userId_" + userId;
        //先查詢redis緩存
        User user = redisCache.get(cacheKey);
        //如果緩存中有就直接返回,不再查詢數據庫
        if (user != null) {
            return user;
        }
        //沒有再查詢數據庫
        user = userMapper.getUserById(userId);

        //數據存入緩存,這樣下次查詢就能到緩存中獲取
        if (user != null) {
            stringCommand.set(cacheKey, user);
        }

        return user;
    }

相信很多同學都寫過類似風格的代碼,這種風格符合面向過程的編程思維,非常容易理解。但它也有一些缺點:

代碼不夠優(yōu)雅。業(yè)務邏輯有四個典型動作:存儲,讀取,修改,刪除。每次操作都需要定義緩存Key ,調用緩存命令的API,產生較多的重復代碼;

緩存操作和業(yè)務邏輯之間的代碼耦合度高,對業(yè)務邏輯有較強的侵入性。侵入性主要體現如下兩點:

  • 開發(fā)聯調階段,需要去掉緩存,只能注釋或者臨時刪除緩存操作代碼,也容易出錯;

  • 某些場景下,需要更換緩存組件,每個緩存組件有自己的API,更換成本頗高。

如果說是下面這樣的,是不是就優(yōu)雅多了。

@Mapper
public interface UserMapper  {
    
    /**
     * 根據用戶id獲取用戶信息
     *
     * 如果緩存中有直接返回緩存數據,如果沒有那么就去數據庫查詢,查詢完再插入緩存中,這里緩存的key前綴為cache_user_id_,+傳入的用戶ID
     */
    @Cacheable(key = "'cache_user_id_' + #userId")
    User getUserById(Long userId);
}

再看實現類

    @Autowire
    private UserMapper userMapper;

    //查詢用戶
    public User getUserById(Long userId) {
        return userMapper.getUserById(userId);
    }

這么一看是不是完全和緩存分離開來,如果開發(fā)聯調階段,需要去掉緩存那么直接注釋掉注解就好了,是不是非常完美。

而且這一整套實現都不要自己手動寫,Spring Cache就已經幫我定義好相關注解和接口,我們可以輕易實現上面的功能。

二、Spring Cache簡介

Spring Cache是Spring-context包中提供的基于注解方式使用的緩存組件,定義了一些標準接口,通過實現這些接口,就可以通過在

方法上增加注解來實現緩存。這樣就能夠避免緩存代碼與業(yè)務處理耦合在一起的問題。

Spring Cache核心的接口就兩個:CacheCacheManager

1、Cache接口

該接口定義提供緩存的具體操作,比如緩存的放入、讀取、清理:

package org.Springframework.cache;
import java.util.concurrent.Callable;

public interface Cache {

	// cacheName,緩存的名字,默認實現中一般是CacheManager創(chuàng)建Cache的bean時傳入cacheName
	String getName();

	//得到底層使用的緩存,如Ehcache
	Object getNativeCache();

	// 通過key獲取緩存值,注意返回的是ValueWrapper,為了兼容存儲空值的情況,將返回值包裝了一層,通過get方法獲取實際值
	ValueWrapper get(Object key);

	// 通過key獲取緩存值,返回的是實際值,即方法的返回值類型
	<T> T get(Object key, Class<T> type);

	// 通過key獲取緩存值,可以使用valueLoader.call()來調使用@Cacheable注解的方法。當@Cacheable注解的sync屬性配置為true時使用此方法。
	// 因此方法內需要保證回源到數據庫的同步性。避免在緩存失效時大量請求回源到數據庫
	<T> T get(Object key, Callable<T> valueLoader);

	// 將@Cacheable注解方法返回的數據放入緩存中
	void put(Object key, Object value);

	// 當緩存中不存在key時才放入緩存。返回值是當key存在時原有的數據
	ValueWrapper putIfAbsent(Object key, Object value);

	// 刪除緩存
	void evict(Object key);

	// 清空緩存
	void clear();

	// 緩存返回值的包裝
	interface ValueWrapper {

	// 返回實際緩存的對象
		Object get();
	}
}

2、CacheManager接口

主要提供Cache實現bean的創(chuàng)建,每個應用里可以通過cacheName來對Cache進行隔離,每個cacheName對應一個Cache實現。

package org.Springframework.cache;
import java.util.Collection;

public interface CacheManager {

	// 通過cacheName創(chuàng)建Cache的實現bean,具體實現中需要存儲已創(chuàng)建的Cache實現bean,避免重復創(chuàng)建,也避免內存緩存
        //對象(如Caffeine)重新創(chuàng)建后原來緩存內容丟失的情況
	Cache getCache(String name);

	// 返回所有的cacheName
	Collection<String> getCacheNames();
}

3、常用注解說明

@Cacheable:主要應用到查詢數據的方法上。

public @interface Cacheable {

    // cacheNames,CacheManager就是通過這個名稱創(chuàng)建對應的Cache實現bean
	@AliasFor("cacheNames")
	String[] value() default {};

	@AliasFor("value")
	String[] cacheNames() default {};

    // 緩存的key,支持SpEL表達式。默認是使用所有參數及其計算的hashCode包裝后的對象(SimpleKey)
	String key() default "";

	// 緩存key生成器,默認實現是SimpleKeyGenerator
	String keyGenerator() default "";

	// 指定使用哪個CacheManager,如果只有一個可以不用指定
	String cacheManager() default "";

	// 緩存解析器
	String cacheResolver() default "";

	// 緩存的條件,支持SpEL表達式,當達到滿足的條件時才緩存數據。在調用方法前后都會判斷
	String condition() default "";
        
    // 滿足條件時不更新緩存,支持SpEL表達式,只在調用方法后判斷
	String unless() default "";

	// 回源到實際方法獲取數據時,是否要保持同步,如果為false,調用的是Cache.get(key)方法;如果為true,調用的是Cache.get(key, Callable)方法
	boolean sync() default false;

}

@CacheEvict:清除緩存,主要應用到刪除數據的方法上。相比Cacheable多了兩個屬性

public @interface CacheEvict {

  // ...相同屬性說明請參考@Cacheable中的說明
	// 是否要清除所有緩存的數據,為false時調用的是Cache.evict(key)方法;為true時調用的是Cache.clear()方法
	boolean allEntries() default false;

	// 調用方法之前或之后清除緩存
	boolean beforeInvocation() default false;
}

@CachePut:放入緩存,主要用到對數據有更新的方法上。屬性說明參考@Cacheable

@Caching:用于在一個方法上配置多種注解

@EnableCaching:啟用Spring cache緩存,作為總的開關,在SpringBoot的啟動類或配置類上需要加上此注解才會生效

三、使用二級緩存需要思考的一些問題?

我們知道關系數據庫(Mysql)數據最終存儲在磁盤上,如果每次都從數據庫里去讀取,會因為磁盤本身的IO影響讀取速度,所以就有了

像redis這種的內存緩存。

通過內存緩存確實能夠很大程度的提高查詢速度,但如果同一查詢并發(fā)量非常的大,頻繁的查詢redis,也會有明顯的網絡IO上的消耗,

那我們針對這種查詢非常頻繁的數據(熱點key),我們是不是可以考慮存到應用內緩存,如:caffeine。

當應用內緩存有符合條件的數據時,就可以直接使用,而不用通過網絡到redis中去獲取,這樣就形成了兩級緩存。

應用內緩存叫做一級緩存,遠程緩存(如redis)叫做二級緩存。

整個流程如下

流程看著是很清新,但其實二級緩存需要考慮的點還很多。

1.如何保證分布式節(jié)點一級緩存的一致性?

我們說一級緩存是應用內緩存,那么當你的項目部署在多個節(jié)點的時候,如何保證當你對某個key進行修改刪除操作時,使其它節(jié)點

的一級緩存一致呢?

2.是否允許存儲空值?

這個確實是需要考慮的點。因為如果某個查詢緩存和數據庫中都沒有,那么就會導致頻繁查詢數據庫,導致數據庫Down,這也是我們

常說的緩存穿透。

但如果存儲空值呢,因為可能會存儲大量的空值,導致緩存變大,所以這個最好是可配置,按照業(yè)務來決定是否開啟。

3.是否需要緩存預熱?

也就是說,我們會覺得某些key一開始就會非常的熱,也就是熱點數據,那么我們是否可以一開始就先存儲到緩存中,避免緩存擊穿。

4.一級緩存存儲數量上限的考慮?

既然一級緩存是應用內緩存,那你是否考慮一級緩存存儲的數據給個限定最大值,避免存儲太多的一級緩存導致OOM。

5.一級緩存過期策略的考慮?

我們說redis作為二級緩存,redis是淘汰策略來管理的。具體可參考redis的8種淘汰策略。那你的一級緩存策略呢?就好比你設置一級緩存

數量最大為5000個,那當第5001個進來的時候,你是怎么處理呢?是直接不保存,還是說自定義LRU或者LFU算法去淘汰之前的數據?

6.一級緩存過期了如何清除?

我們說redis作為二級緩存,我們有它的緩存過期策略(定時、定期、惰性),那你的一級緩存呢,過期如何清除呢?

這里4、5、6小點如果說用我們傳統的Map顯然實現是很費勁的,但現在有更好用的一級緩存庫那就是Caffeine。

四、Caffeine 簡介

Caffeine,一個用于Java的高性能緩存庫。

緩存和Map之間的一個根本區(qū)別是緩存會清理存儲的項目。

1、寫入緩存策略

Caffeine有三種緩存寫入策略:手動同步加載異步加載。

2、緩存值的清理策略

Caffeine有三種緩存值的清理策略:基于大小、基于時間基于引用。

基于容量:當緩存大小超過配置的大小限制時會發(fā)生回收。

基于時間

  • 寫入后到期策略。
  • 訪問后過期策略。
  • 到期時間由 Expiry 實現獨自計算。

基于引用:啟用基于緩存鍵值的垃圾回收。

  • Java種有四種引用:強引用,軟引用,弱引用和虛引用,caffeine可以將值封裝成弱引用或軟引用。
  • 軟引用:如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。
  • 弱引用:在垃圾回收器線程掃描它所管轄的內存區(qū)域的過程中,一旦發(fā)現了只具有弱引用的對象,不管當前內存空間足夠與否,都會
    回收它的內存。

3、統計

Caffeine提供了一種記錄緩存使用統計信息的方法,可以實時監(jiān)控緩存當前的狀態(tài),以評估緩存的健康程度以及緩存命中率等,方便后

續(xù)調整參數。

4、高效的緩存淘汰算法

緩存淘汰算法的作用是在有限的資源內,盡可能識別出哪些數據在短時間會被重復利用,從而提高緩存的命中率。常用的緩存淘汰算法有

LRU、LFU、FIFO等。

FIFO:先進先出。選擇最先進入的數據優(yōu)先淘汰。
LRU:最近最少使用。選擇最近最少使用的數據優(yōu)先淘汰。
LFU:最不經常使用。選擇在一段時間內被使用次數最少的數據優(yōu)先淘汰。

LRU(Least Recently Used)算法認為最近訪問過的數據將來被訪問的幾率也更高。

LRU通常使用鏈表來實現,如果數據添加或者被訪問到則把數據移動到鏈表的頭部,鏈表的頭部為熱數據,鏈表的尾部如冷數據,當

數據滿時,淘汰尾部的數據。

LFU(Least Frequently Used)算法根據數據的歷史訪問頻率來淘汰數據,其核心思想是“如果數據過去被訪問多次,那么將來被訪問

的頻率也更高”。根據LFU的思想,如果想要實現這個算法,需要額外的一套存儲用來存每個元素的訪問次數,會造成內存資源的浪費。

Caffeine采用了一種結合LRU、LFU優(yōu)點的算法:W-TinyLFU,其特點:高命中率、低內存占用。

5、其他說明

Caffeine的底層數據存儲采用ConcurrentHashMap。因為Caffeine面向JDK8,在jdk8中ConcurrentHashMap增加了紅黑樹,在hash沖突

嚴重時也能有良好的讀性能。

五、基于Spring Cache實現二級緩存(Caffeine+Redis)

前面說了,使用了redis緩存,也會存在一定程度的網絡傳輸上的消耗,所以會考慮應用內緩存,但有點很重要的要記住:

應用內緩存可以理解成比redis緩存更珍惜的資源,所以,caffeine 不適用于數據量大,并且緩存命中率極低的業(yè)務場景,如用戶維度的緩存。

當前項目針對應用都部署了多個節(jié)點,一級緩存是在應用內的緩存,所以當對數據更新和清除時,需要通知所有節(jié)點進行清理緩存的操作。

可以有多種方式來實現這種效果,比如:zookeeper、MQ等,但是既然用了redis緩存,redis本身是有支持訂閱/發(fā)布功能的,所以就

不依賴其他組件了,直接使用redis的通道來通知其他節(jié)點進行清理緩存的操作。

當某個key進行更新刪除操作時,通過發(fā)布訂閱的方式通知其它節(jié)點進行刪除該key本地的一級緩存就可以了。

具體具體項目代碼這里就不再粘貼出來了,這樣只粘貼如何引用這個starter包。

1、maven引入使用

   <dependency>
            <groupId>com.jincou</groupId>
            <artifactId>redis-caffeine-cache-starter</artifactId>
            <version>1.0.0</version>
   </dependency>

2、application.yml

添加二級緩存相關配置

# 二級緩存配置
# 注:caffeine 不適用于數據量大,并且緩存命中率極低的業(yè)務場景,如用戶維度的緩存。請慎重選擇。
l2cache:
  config:
    # 是否存儲空值,默認true,防止緩存穿透
    allowNullValues: true
    # 組合緩存配置
    composite:
      # 是否全部啟用一級緩存,默認false
      l1AllOpen: false
      # 是否手動啟用一級緩存,默認false
      l1Manual: true
      # 手動配置走一級緩存的緩存key集合,針對單個key維度
      l1ManualKeySet:
      - userCache:user01
      - userCache:user02
      - userCache:user03
      # 手動配置走一級緩存的緩存名字集合,針對cacheName維度
      l1ManualCacheNameSet:
      - userCache
      - goodsCache
    # 一級緩存
    caffeine:
      # 是否自動刷新過期緩存 true 是 false 否
      autoRefreshExpireCache: false
      # 緩存刷新調度線程池的大小
      refreshPoolSize: 2
      # 緩存刷新的頻率(秒)
      refreshPeriod: 10
      # 寫入后過期時間(秒)
      expireAfterWrite: 180
      # 訪問后過期時間(秒)
      expireAfterAccess: 180
      # 初始化大小
      initialCapacity: 1000
      # 最大緩存對象個數,超過此數量時之前放入的緩存將失效
      maximumSize: 3000

    # 二級緩存
    redis:
      # 全局過期時間,單位毫秒,默認不過期
      defaultExpiration: 300000
      # 每個cacheName的過期時間,單位毫秒,優(yōu)先級比defaultExpiration高
      expires: {userCache: 300000,goodsCache: 50000}
      # 緩存更新時通知其他節(jié)點的topic名稱 默認 cache:redis:caffeine:topic
      topic: cache:redis:caffeine:topic

3、啟動類上增加@EnableCaching

/**
 *  啟動類
 */
@EnableCaching
@SpringBootApplication
public class CacheApplication {

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

}

4、在需要緩存的方法上增加@Cacheable注解

/**
 *  測試
 */
@Service
public class CaffeineCacheService {

    private final Logger logger = LoggerFactory.getLogger(CaffeineCacheService.class);

    /**
     * 用于模擬db
     */
    private static Map<String, UserDTO> userMap = new HashMap<>();

    {
        userMap.put("user01", new UserDTO("1", "張三"));
        userMap.put("user02", new UserDTO("2", "李四"));
        userMap.put("user03", new UserDTO("3", "王五"));
        userMap.put("user04", new UserDTO("4", "趙六"));
    }

    /**
     * 獲取或加載緩存項
     */
    @Cacheable(key = "'cache_user_id_' + #userId", value = "userCache")
    public UserDTO queryUser(String userId) {
        UserDTO userDTO = userMap.get(userId);
        try {
            Thread.sleep(1000);// 模擬加載數據的耗時
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("加載數據:{}", userDTO);
        return userDTO;
    }


    /**
     * 獲取或加載緩存項
     * <p>
     * 注:因底層是基于caffeine來實現一級緩存,所以利用的caffeine本身的同步機制來實現
     * sync=true 則表示并發(fā)場景下同步加載緩存項,
     * sync=true,是通過get(Object key, Callable<T> valueLoader)來獲取或加載緩存項,此時valueLoader(加載緩存項的具體邏輯)會被緩存起來,所以CaffeineCache在定時刷新過期緩存時,緩存項過期則會重新加載。
     * sync=false,是通過get(Object key)來獲取緩存項,由于沒有valueLoader(加載緩存項的具體邏輯),所以CaffeineCache在定時刷新過期緩存時,緩存項過期則會被淘汰。
     * <p>
     */
    @Cacheable(value = "userCache", key = "#userId", sync = true)
    public List<UserDTO> queryUserSyncList(String userId) {
        UserDTO userDTO = userMap.get(userId);
        List<UserDTO> list = new ArrayList();
        list.add(userDTO);
        logger.info("加載數據:{}", list);
        return list;
    }

    /**
     * 更新緩存
     */
    @CachePut(value = "userCache", key = "#userId")
    public UserDTO putUser(String userId, UserDTO userDTO) {
        return userDTO;
    }

    /**
     * 淘汰緩存
     */
    @CacheEvict(value = "userCache", key = "#userId")
    public String evictUserSync(String userId) {
        return userId;
    }
}

項目源碼https://github.com/yudiandemingzi/springboot-redis-caffeine-cache

推薦相關二級緩存相關項目

1.阿里巴巴jetcache: https://github.com/alibaba/jetcache

2.J2Cache: https://gitee.com/ld/J2Cache

3.l2cache: https://github.com/ck-jesse/l2cache(感謝)

這幾個現在業(yè)界比較常用的二級緩存項目,功能更加強大,而且性能更高效,使用也非常方便只要引入jar包,添加配置注解就可以。

到此這篇關于基于Spring Cache實現二級緩存(Caffeine+Redis)的文章就介紹到這了,更多相關Spring Cache二級緩存內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 一文詳解Java如何系統地避免空指針問題

    一文詳解Java如何系統地避免空指針問題

    新手Java開發(fā)總是經??罩羔槞z查,甚至某些老手也會犯這樣的問題,所以這篇文章小編就帶大家一起來看看如何系統地避免空指針問題,希望對大家有所幫助
    2024-01-01
  • Javaweb resin4如何配置端口虛擬目錄

    Javaweb resin4如何配置端口虛擬目錄

    這篇文章主要介紹了Javaweb resin4如何配置端口虛擬目錄,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-07-07
  • 分享Java8中通過Stream對列表進行去重的實現

    分享Java8中通過Stream對列表進行去重的實現

    本文主要介紹了分享Java8中通過Stream對列表進行去重的實現,包括兩種方法,具有一定的參考價值,感興趣的可以了解一下
    2023-11-11
  • SpringBoot validator參數驗證restful自定義錯誤碼響應方式

    SpringBoot validator參數驗證restful自定義錯誤碼響應方式

    這篇文章主要介紹了SpringBoot validator參數驗證restful自定義錯誤碼響應方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • java實現支付寶支付接口的調用

    java實現支付寶支付接口的調用

    本文主要介紹了java實現支付寶支付接口的調用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07
  • java compare compareTo方法區(qū)別詳解

    java compare compareTo方法區(qū)別詳解

    本文主要介紹了java compare compareTo方法區(qū)別,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Java內省實例解析

    Java內省實例解析

    這篇文章主要介紹了Java內省實例解析,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • 淺析如何高效格式化輸出JSON字符串

    淺析如何高效格式化輸出JSON字符串

    JSON(JavaScript?Object?Notation)作為一種輕量級的數據交換格式,已經成為了各種編程語言間傳遞數據的標準,下面我們就來聊聊如何高效格式化輸出JSON字符串吧
    2025-01-01
  • Java?Map雙列集合使代碼更高效

    Java?Map雙列集合使代碼更高效

    這篇文章主要介紹了Java?Map雙列集合使用,使你的代碼更高效,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • spring cloud gateway跨域全局CORS配置方式

    spring cloud gateway跨域全局CORS配置方式

    這篇文章主要介紹了spring cloud gateway跨域全局CORS配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07

最新評論

图木舒克市| 威海市| 民丰县| 林芝县| 和平县| 吴忠市| 开原市| 碌曲县| 承德县| 陵水| 应用必备| 清河县| 通渭县| 洛南县| 鹤山市| 凤台县| 万全县| 全南县| 辽源市| 鲁甸县| 红安县| 临朐县| 盐亭县| 新巴尔虎左旗| 江阴市| 贵阳市| 广平县| 九江市| 前郭尔| 祁东县| 玛纳斯县| 清丰县| 鹤庆县| 泰安市| 堆龙德庆县| 濮阳市| 江阴市| 西城区| 永平县| 衡南县| 翼城县|