「性能提升」擴展Spring Cache 支持多級緩存

2020-10-03 Java大數據高級架構師

為什麼多級緩存

緩存的引入是現在大部分系統所必須考慮的

  • redis 作為常用中間件,雖然我們一般業務系統(畢竟業務量有限)不會遇到如下圖 在隨著 data-size 的增大和數據結構的複雜的造成性能下降,但網絡 IO 消耗會成為整個調用鏈路中不可忽視的部分。尤其在 微服務架構中,一次調用往往會涉及多次調用 例如pig oauth2.0 的 client 認證[1]

  • Caffeine 來自未來的本地內存緩存,性能比如常見的內存緩存實現性能高出不少詳細對比[2]

綜合所述:我們需要構建 L1 Caffeine JVM 級別緩存 , L2 Redis 緩存。

設計難點

目前大部分應用緩存都是基於 Spring Cache 實現,基於註解(annotation)的緩存(cache)技術,存在的問題如下:

  • Spring Cache 僅支持 單一的緩存來源,即:只能選擇 Redis 實現或者 Caffeine 實現,並不能同時使用。
  • 數據一致性:各層緩存之間的數據一致性問題,如應用層緩存和分布式緩存之前的數據一致性問題。
  • 緩存過期:Spring Cache 不支持主動的過期策略

業務流程

如何使用

  • 引入依賴

<dependency> <groupId>com.pig4cloud.plugin</groupId> <artifactId>multilevel-cache-spring-boot-starter</artifactId> <version>0.0.1</version></dependency>

  • 開啟緩存支持

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

  • 目標接口聲明 Spring Cache 註解

@Cacheable(value = "get",key = "#key")@GetMapping("/get")public String get(String key){ return "success";}

性能比較

為保證性能 redis 在 127.0.0.1 環路安裝

  • OS: macOS Mojave
  • CPU: 2.3 GHz Intel Core i5
  • RAM: 8 GB 2133 MHz LPDDR3
  • JVM: corretto_11.jdk

代碼原理

  • 自定義 CacheManager 多級緩存實現

public class RedisCaffeineCacheManager implements CacheManager { @Override public Cache getCache(String name) { Cache cache = cacheMap.get(name); if (cache != null) { return cache; } cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(), cacheConfigProperties); Cache oldCache = cacheMap.putIfAbsent(name, cache); log.debug("create cache instance, the cache name is : {}", name); return oldCache == null ? cache : oldCache; }}

  • 多級讀取、過期策略實現

public class RedisCaffeineCache extends AbstractValueAdaptingCache { protected Object lookup(Object key) { Object cacheKey = getKey(key); // 1. 先調用 caffeine 查詢是否存在指定的值 Object value = caffeineCache.getIfPresent(key); if (value != null) { log.debug("get cache from caffeine, the key is : {}", cacheKey); return value; } // 2. 調用 redis 查詢在指定的值 value = stringKeyRedisTemplate.opsForValue().get(cacheKey); if (value != null) { log.debug("get cache from redis and put in caffeine, the key is : {}", cacheKey); caffeineCache.put(key, value); } return value; }}

  • 過期策略,所有更新操作都基於 redis pub/sub 消息機制更新

public class RedisCaffeineCache extends AbstractValueAdaptingCache { @Override public void put(Object key, Object value) { push(new CacheMessage(this.name, key)); } @Override public ValueWrapper putIfAbsent(Object key, Object value) { push(new CacheMessage(this.name, key)); } @Override public void evict(Object key) { push(new CacheMessage(this.name, key)); } @Override public void clear() { push(new CacheMessage(this.name, null)); } private void push(CacheMessage message) { stringKeyRedisTemplate.convertAndSend(topic, message); }}

  • MessageListener 刪除指定 Caffeine 的指定值

public class CacheMessageListener implements MessageListener { private final RedisTemplate<Object, Object> redisTemplate; private final RedisCaffeineCacheManager redisCaffeineCacheManager; @Override public void onMessage(Message message, byte[] pattern) { CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody()); cacheMessage.getCacheName(), cacheMessage.getKey()); redisCaffeineCacheManager.clearLocal(cacheMessage.getCacheName(), cacheMessage.getKey()); }}

相關焦點

  • 已關注「性能提升」擴展Spring Cache 支持多級緩存
    為什麼多級緩存緩存的引入是現在大部分系統所必須考慮的redis 作為常用中間件,雖然我們一般業務系統(畢竟業務量有限)不會遇到如下圖 在隨著 data-size 的增大和數據結構的複雜的造成性能下降,但網絡 IO 消耗會成為整個調用鏈路中不可忽視的部分。
  • 使用spring cache讓我的接口性能瞬間提升了
    當時考慮分類是放在商城首頁,以後流量大,而且不經常變動,為了提升首頁訪問速度,我考慮使用緩存。對於java開發而言,首先的緩存當然是redis。本以為萬事大吉,後來,在系統上線之前,測試對商城首頁做了一次性能壓測,發現qps是100多,一直上不去。我們仔細分析了一下原因,發現了兩個主要的優化點:去掉多餘的接口日誌列印 和 分類接口引入redis cache做一次二級緩存。日誌列印我在這裡就不多說了,不是本文的重點,我們重點說一下redis cache。
  • Spring@Cacheable緩存機制
    @Cacheable緩存在spring中使用很方便,主要是對相同的請求,不會去重複去資料庫取數據,而是使用上一次的緩存的結果,大大提高了效率。spring配置文件專門為緩存提供了一個cache:命名空間,為了啟動spring緩存,需要在配置文件中導入cache:命名空間。在spring配置文件中添加<cache:annotation-driven cache-manager="緩存管理器ID">,該元素指定spring根據註解來啟用Bean級別或方法級別的緩存。
  • 還在用Guava Cache?它才是Java本地緩存之王
    「LFU的局限性」:在 LFU 中只要數據訪問模式的概率分布隨時間保持不變時,其命中率就能變得非常高。比如有部新劇出來了,我們使用 LFU 給他緩存下來,這部新劇在這幾天大概訪問了幾億次,這個訪問頻率也在我們的 LFU 中記錄了幾億次。
  • Fallback多級降級,Request Cache減壓
    Fallback多級降級測試3.多級降級處理多級降級是成功的。接口,然後接口裡面連續調用了 兩次requestCacheService.requestCache(name);如果列印兩遍相同的log說明沒有被緩存到本地,解壓失敗。
  • SpringBootCache源碼解析:Cache自動配置
    此時,緩存便派上了用場。而在 Spring 3.1 中引入了基於註解的 Cache 的支持在spring-context 包 中 定 義 了 org.springframework.cache. CacheManager 和org.springframework.cache.Cache 接口,用來統一-不同的緩存的技術。
  • Spring Boot 2.x基礎教程:進程內緩存的使用與Cache註解詳解
    隨著時間的積累,應用的使用用戶不斷增加,數據規模也越來越大,往往資料庫查詢操作會成為影響用戶使用體驗的瓶頸,此時使用緩存往往是解決這一問題非常好的手段之一。Spring 3開始提供了強大的基於註解的緩存支持,可以通過註解配置方式低侵入的給原有Spring應用增加緩存功能,提高數據訪問性能。
  • SpringBoot2.0實戰(23)整合Redis之集成緩存SpringDataCache
    @Cacheable 作用在方法上,表明該方法的結果可以緩存,如果緩存存在,則目標方法不會被調用,直接從緩存中獲取,如果緩存不存在,則執行方法體,並將結果存入緩存。@CacheEvice 作用在方法上,刪除緩存項或者清空緩存。@CachePut 作用在方法上,不管緩存是否存在,都會執行方法體,並將結果存入緩存。
  • 使用Caffeine和springboot的多級緩存配置
    #使用Caffeine和springboot的多級緩存配置因此,在許多應用程式中,包括普通的Spring和Spring Boot,您都可以@Cacheable在任何方法上使用它,並且其結果將被緩存,以便下次調用該方法時,將返回緩存的結果。但是,提供的緩存管理器僅允許您配置一個緩存規範。緩存規範包括到期時間,初始容量,最大大小等。
  • Caffeine Cache~高性能 Java 本地緩存之王
    注意:AsyncLoadingCache不支持弱引用和軟引用。配置文件的方式注入相關參數properties文件spring.cache.cache-names=cache1spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s或Yaml文件
  • Redis實現Spring緩存
    -- spring自己的緩存管理器,這裡定義了緩存位置名稱 ,即註解中的value --> <bean id="myCacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property
  • 在了解Cache緩存原理之前,你只是個普通的程式設計師
    如果我們可以提升主存的速度,那麼系統將會獲得很大的性能提升。如今的DDR存儲設備,動不動就是幾個GB,容量很大。如果我們採用更快材料製作更快速度的主存,並且擁有幾乎差不多的容量。其成本將會大幅度上升。我們試圖提升主存的速度和容量,又期望其成本很低,這就有點難為人了。因此,我們有一種折中的方法,那就是製作一塊速度極快但是容量極小的存儲設備。那麼其成本也不會太高。
  • SpringBoot集成redis與spring-cache
    未月廿三 | 作者urlify.cn/nUn2Aj | 來源spring基於註解的緩存對於緩存聲明,spring的緩存提供了一組java註解:@Cacheable:觸發緩存寫入。@CacheEvict:觸發緩存清除。@CachePut:更新緩存(不會影響到方法的運行)。@Caching:重新組合要應用於方法的多個緩存操作。@CacheConfig:設置類級別上共享的一些常見緩存設置。
  • 從零搭建Spring Boot腳手架:整合Redis作為緩存
    緩存及Redis配置緩存以及Redis相關的配置項分別為spring.cache和spring.redis開頭的配置,這裡比較簡單的配置為:spring: redis: host: localhost port: 6379 cache: 全局過期時間 time-to-live: 120複製代碼
  • 賊厲害,手擼的 SpringBoot緩存系統,性能槓槓的
    緩存是最直接有效提升系統性能的手段之一。個人認為用好用對緩存是優秀程式設計師的必備基本素質。;com.power.demo.cache.config;  import org.springframework.cache.CacheManager;  import org.springframework.cache.annotation.CachingConfigurerSupport;  import 
  • 開源吧,整合Redis作為緩存搭建Spring Boot框架
    緩存及Redis配置緩存以及Redis相關的配置項分別為spring.cache和spring.redis開頭的配置,這裡比較簡單的配置為:spring: redis: host: localhost port: 6379 cache:
  • Spring Boot 2.x基礎教程:使用集中式緩存Redis
    之前我們介紹了兩種進程內緩存的用法,包括Spring Boot默認使用的ConcurrentMap緩存以及緩存框架EhCache。雖然EhCache已經能夠適用很多應用場景,但是由於EhCache是進程內的緩存框架,在集群模式下時,各應用伺服器之間的緩存都是獨立的,因此在不同伺服器的進程間會存在緩存不一致的情況。
  • 「最強」Lettuce 已支持 Redis6 客戶端緩存
    Redis 客戶端緩存緩存的解決方案一般有兩種:【L1】 內存緩存(如 Caffeine、Ehcache) —— 速度快,進程內可用,但重啟緩存丟失,出現緩存雪崩的問題。【L2】集中式緩存(如 Redis)—— 可同時為多節點提供服務,但高並發下,帶寬成為瓶頸。
  • 緩存提升性能的關鍵性手段
    「 計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決」!而「緩存層」可以說是添加得最多的層!主要目的就是為了提高性能!此類緩存稱為「主動緩存」,因為其緩存數據是由用戶的主動修改來觸發更新的。
  • 用好SpringCache公司再也不用擔心我搞不定AOP了
    其中第一個就是使用緩存。使用緩存是一個很「高性價比」的性能優化方式,尤其是對於有大量重複查詢的程序來說。>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId></dependency>