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