譯者 | 劉汪洋
審校 | 重樓
緩存是提高應用性能的有效方法。我們之前發布過一篇文章,介紹了緩存的概念和好處,主要針對 Spring Boot 進行了討論。 在本文中,我們將探討優化 Spring Boot 應用程序緩存的 7 種技術。
目錄
1- 確定待緩存的對象
2- 緩存過期
淘汰策略
基于時間的過期策略
自定義淘汰策略
3- 條件緩存
4- 分布式緩存 vs 本地緩存
什么是本地緩存?
何時使用本地緩存 Vs. 分布式緩存?
在Spring Boot中實現本地緩存
5- 自定義鍵生成策略
6- 異步緩存
7- 監控緩存指標以發現瓶頸
如何在Spring Boot中監控緩存指標
總結
1.確定待緩存的對象
首先,我們需要明確哪些對象最適合緩存。一般而言,那些代價高昂且耗時的操作的結果需要優先考慮,例如數據庫查詢、網絡服務調用或復雜計算的結果。然而,定義一些理想緩存候選對象的通用特征將更重要。這些特征有助于我們在應用程序中識別適合緩存的對象:
- 頻繁訪問的數據:經常被訪問和重復訪問的數據是良好的緩存候選對象。
- 代價高昂的獲取或計算:需要大量時間或計算資源來檢索或處理的數據。
- 靜態或變化較少的數據:變化不頻繁的數據,確保緩存的數據在較長時間內保持有效。
- 高讀寫比率:當數據被訪問的頻率遠高于修改或更新的頻率時,可以有效地進行緩存。這保證了緩存快速讀取的優勢超過其更新成本。
- 可預測的訪問模式:遵循可預測訪問模式的數據,允許更高效的緩存管理。
這些特征可以幫助我們有效地識別和緩存能夠顯著提升應用程序性能的數據。 既然我們知道如何找到理想的緩存候選對象,就可以開始在 Spring Boot 應用程序中啟用緩存??梢允褂米⒔饣蚓幊谭绞竭M行緩存配置。我在這篇文章中詳細討論了如何在 Spring Boot 中使用緩存,以及_ Digma_ 如何幫助我們發現緩存未命中或識別緩存候選對象。
2.緩存過期
緩存過期策略設置得當可以確保緩存數據的有效性和及時更新,提高內存利用率,從而優化 Spring Boot 應用程序的性能和一致性。以下是一些推薦的管理 Spring Boot 應用程序中緩存過期的方法:
淘汰策略
常見的淘汰策略包括:
- 最近最少使用(LRU):優先淘汰最近最少訪問的對象。
- 最不經常使用(LFU):優先淘汰訪問頻率最低的對象。
- 先進先出(FIFO):優先淘汰最早放入緩存的對象。
雖然 Spring Cache 抽象本身不直接支持這些淘汰策略,但你可以根據所選的緩存提供者使用其特定配置。通過仔細選擇和配置合適的淘汰策略,可以確保緩存機制高效運行,并與應用程序的性能和資源利用目標相一致。
基于時間的過期策略
定義緩存條目的生存時間(TTL)在不同緩存提供者中有所不同。例如,在 Spring Boot 應用程序中使用 Redis 進行緩存時,可以通過以下配置指定生存時間:
spring.cache.redis.time-to-live=10m
如果緩存提供者不支持 TTL,可以使用@CacheEvict注解和調度器來實現,例如:
@CacheEvict(value = "cache1", allEntries = true)
@Scheduled(fixedRateString = "${your.config.key.for.ttl.in.milli}")
public void emptyCache1() {
// 刷新緩存,這里無需編寫任何代碼,除了描述性日志!
}
自定義淘汰策略
通過根據事件或情況為單個緩存條目或所有條目定義自定義過期策略,可以防止緩存污染并保持其一致性。Spring Boot 具有多種注解來支持自定義過期策略:
- @CacheEvict:從緩存中刪除一個或所有條目。
- @CachePut:用新值更新條目。
- CacheManager:可以使用Spring的CacheManager和Cache接口實現自定義淘汰策略??梢允褂胑vict()、put()或clear()等方法進行操作,還可以通過getNativeCache()方法訪問底層緩存提供者,以獲得更多功能。
實施自定義淘汰策略的關鍵在于找到合適的時機和條件來淘汰緩存對象。
3.條件緩存
條件緩存與淘汰策略共同在優化緩存策略中發揮重要作用。在某些情況下,我們不需要將所有特定實體的數據存儲在緩存中。
條件緩存確保只有符合特定條件的數據才會存儲在緩存中。
這可以防止緩存中存儲不必要的數據,從而優化資源利用。 @Cacheable和@CachePut注解都具有condition和unless屬性,允許我們為緩存項定義條件:
- condition:指定一個 SpEL(Spring表達式語言)表達式,該表達式必須評估為true,數據才會被緩存(或更新)。
- unless:指定一個 SpEL 表達式,該表達式必須評估為false,數據才會被緩存(或更新)。
為了更清楚,請看以下代碼示例:
@Cacheable(value = "employeeByName", condition = "#result.size() > 10", unless = "#result.size() < 1000")
public List<Employee> employeesByName(String name) {
// 檢索數據的方法邏輯
return someEmployeeList;
}
在這段代碼中,只有當結果列表的大小大于 10 且小于 1000 時,員工列表才會被緩存。 最后一點,與前一部分類似,我們也可以使用CacheManager和Cache接口以編程方式實現條件緩存。這提供了更多的靈活性和對緩存行為的控制。
4.分布式緩存 vs. 本地緩存
談到緩存,我們通常會想到分布式緩存,如 Redis、Memcached 或 Hazelcast。在微服務架構盛行的時代,本地緩存也在提升應用性能方面發揮了重要作用。 理解本地緩存和分布式緩存之間的差異,有助于選擇合適的策略來優化 Spring Boot 應用中的緩存。每種類型都有其優缺點,根據應用需求進行權衡至關重要。
什么是本地緩存?
本地緩存是一種緩存機制,其中數據存儲在與應用運行的同一臺機器或實例的內存中。一些知名的本地緩存庫包括 Ehcache、Caffeine 和 Guava Cache。 本地緩存允許快速訪問緩存數據,因為它避免了與遠程數據檢索(分布式緩存)相關的網絡延遲和開銷。本地緩存通常比分布式緩存更易于設置和管理,并且不需要額外的基礎設施。
何時使用本地緩存 vs. 分布式緩存?
本地緩存適用于小型應用程序或數據集較小且可以舒適地放入單臺機器內存中的微服務。它也適用于低延遲至關重要且實例間數據一致性不是主要問題的場景。本地緩存的優勢在于其速度快、設置和管理簡單。 另一方面,分布式緩存系統適用于具有大量數據緩存需求的大規模應用,在這些應用中,可伸縮性、容錯性和多個實例間的數據一致性至關重要。分布式緩存能夠分擔數據存儲負擔,并在節點故障時提供數據冗余。
在 Spring Boot 中實現本地緩存
Spring Boot 支持通過各種內存緩存提供程序(如 Ehcache、Caffeine 或 ConcurrentHashMap)實現本地緩存。我們只需添加所需的依賴項,并在 Spring Boot 應用程序中啟用緩存即可。例如,要使用 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>
然后使用 @EnableCaching 注解啟用緩存:
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
除了通用的 Spring 緩存配置外,我們還可以使用特定的配置來調整 Caffeine 緩存,如下所示:
spring:
cache:
caffeine:
spec: maximumSize=500,expireAfterAccess=10m
5. 自定義鍵生成策略
Spring 緩存注解中的默認鍵生成算法通常如下:
如果沒有參數,則返回 0。 如果只有一個參數,則返回該實例。 如果有多個參數,則返回由所有參數的哈希值計算出的鍵。 只要 hashCode() 能準確反映對象的自然鍵,這種方法對具有自然鍵的對象效果良好。
但在某些情況下,默認的鍵生成策略效果并不好:
- 我們需要有意義的鍵。
- 方法有多個相同類型的參數。
- 方法具有可選參數或空參數。
- 我們需要在鍵中包含上下文數據,如區域、租戶 ID 或用戶角色,以使其唯一。
Spring Cache 提供了兩種定義自定義鍵生成策略的方法:
- 通過 key 屬性指定一個 SpEL(Spring 表達式語言)表達式,該表達式應計算出一個新的鍵:
@CachePut(value = "phonebook", key = "#phoneNumber.name")
PhoneNumber create(PhoneNumber phoneNumber) {
return phonebookRepository.insert(phoneNumber);
}
- 定義一個實現 KeyGenerator 接口的 bean,并將其指定給 keyGenerator 屬性:
@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return "UNIQUE_KEY";
}
}
@CachePut(value = "phonebook", keyGenerator = "customKeyGenerator")
PhoneNumber create(PhoneNumber phoneNumber) {
return phonebookRepository.insert(phoneNumber);
}
使用自定義鍵生成策略可以顯著提升應用程序緩存的性能。設計良好的鍵生成策略能夠確保緩存條目的唯一性,最大限度地減少緩存丟失,并提高緩存命中率。
6. 異步緩存
如你所見,Spring 緩存抽象 API 是阻塞且同步的。如果你使用 WebFlux 棧,通過 Spring 緩存注解(如 @Cacheable 或 @CachePut)將緩存應用于 Reactor 包裝對象(Mono 或 Flux)。在這種情況下,你有三種方法:
- 在 Reactor 類型上調用 cache() 方法,并在該方法上添加 Spring 緩存注解。
- 使用底層緩存提供程序的異步 API(如果支持),并以編程方式處理緩存。
- 實現一個圍繞緩存 API 的異步包裝器,使其支持異步操作(如果緩存提供程序不支持)。
然而,自Spring Framework 6.2 發布以來,如果緩存提供程序支持 WebFlux 項目的異步緩存(如 Caffeine Cache):
Spring 的聲明式緩存基礎設施會檢測到返回 Mono 或 Flux 的反應式方法,并異步緩存其產生的值,而不是緩存返回的 Reactive Streams Publisher 實例。這需要目標緩存提供程序的支持,例如將 CaffeineCacheManager 設置為 setAsyncCacheMode(true)。
配置示例如下:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(buildCaffeineCache());
cacheManager.setAsyncCacheMode(true); // <--
return cacheManager;
}
}
7. 監控緩存指標以發現瓶頸
監控緩存指標對于識別瓶頸和優化應用程序中的緩存策略至關重要。 需要監控的關鍵指標包括:
- 緩存命中率:緩存命中次數與總請求次數的比率,表明緩存的有效性。低命中率表明緩存未被有效利用。
- 緩存未命中率:緩存未命中次數與總請求次數的比率,表明緩存經常無法提供請求的數據,可能是由于緩存大小不足或鍵管理不當。
- 緩存淘汰率:緩存中項目被淘汰的頻率。高淘汰率表明緩存大小過小或淘汰策略不適合當前的訪問模式。
- 內存使用量:緩存使用的內存量。
- 延遲:從緩存中檢索數據所需的時間。
- 錯誤率:通常指的是系統在處理請求時遇到的錯誤數量或比例。錯誤率可以包括緩存無法響應請求、超時錯誤等。
如何在 Spring Boot 中監控緩存指標
Spring Boot Actuator 啟動時會自動為所有可用的 Cache 實例配置 Micrometer。對于啟動后,動態創建或以編程方式創建的緩存,需要進行注冊。請查看相關文檔以確保您的緩存提供程序得到支持。 首先,添加 Actuator 和 Micrometer 依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
然后,啟用 Actuator 端點:
management.endpoints.web.exposure.include=*
現在,可以使用 /actuator/caches 端點查看配置的緩存列表。對于緩存指標,可以使用以下端點:
- /actuator/metrics/cache.gets
- /actuator/metrics/cache.puts
- /actuator/metrics/cache.evictions
- /actuator/metrics/cache.removals
總結
在本文中,我們詳細學習了 7 種優化 Spring Boot 應用緩存的技術。優化緩存至關重要,因為它通過減少后端系統的負載和加快數據檢索速度,直接提升了應用的性能和可擴展性。高效的緩存策略能夠最小化延遲,確保更快的響應時間,從而顯著改善整體用戶體驗。
譯者介紹
劉汪洋,51CTO社區編輯,昵稱:明明如月,一個擁有 5 年開發經驗的某大廠高級 Java 工程師,擁有多個主流技術博客平臺博客專家稱號,博客閱讀量 400W+,粉絲 3W+。2022 年騰訊云優秀創作者,2022 年阿里云技術社區最受歡迎技術電子書 TOP 10 《性能優化方法論》作者,慕課網:剖析《阿里巴巴 Java 開發手冊》、深度解讀《Effective Java》 技術專欄作者。
原文標題:Top 7 Techniques to Optimize Caching in Spring Boot,作者:Saeed Zarinfam