Spring Boot中7種最佳化快取方法

banq發表於2024-07-16

在本文中,列舉了 7 種在 Spring Boot 應用程式中最佳化快取的技術。最佳化快取至關重要,因為它透過減少後端系統的負載和加快資料檢索速度直接增強了應用程式的效能和可擴充套件性。高效的快取策略可最大限度地減少延遲,確保更快的響應時間,並改善整體使用者體驗。


1- 確定理想候選
首先,我們需要了解快取的理想候選者。
我們首先想到的是快取昂貴且耗時的操作,例如查詢資料庫、呼叫 Web 服務或執行復雜的計算。
這些都是正確的候選者,但定義一些理想的快取候選者的一般特徵是有幫助的。這些準備將幫助我們在應用程式中識別這些特徵:

  • 頻繁訪問的資料:經常且重複訪問的資料適合快取。
  • 昂貴的獲取或計算:需要大量時間或計算資源來檢索或處理的資料。
  • 靜態或很少變化的資料:不經常變化的資料,確保快取的資料在較長時間內保持有效。
  • 高讀寫比:訪問頻率高於修改或更新頻率的資料可以得到有效快取。這保證了從快取快速讀取的好處超過更新帶來的成本。
  • 可預測模式:遵循可預測訪問模式的資料,可以實現更高效的快取管理。

這些特性可以幫助我們有效地識別和快取能夠為我們的應用程式帶來最顯著的效能提升的資料。


2-快取過期
設定適當的過期策略可確保我們的快取資料保持有效、 最新且記憶體高效。它將最佳化您的 Spring Boot 應用程式的效能和一致性。

建議使用這些方法來管理 Spring Boot 應用程式中的快取過期:

驅逐策略:
有以下著名的驅逐策略:

  • 最近最少使用(LRU):首先驅逐最近最少訪問的專案。
  • 最不頻繁使用(LFU):首先驅逐最不頻繁訪問的專案。
  • 先進先出(FIFO):首先淘汰訪問頻率最低的專案。

Spring Cache 抽象不支援這些驅逐策略,但您可以根據所選的提供程式使用快取提供程式的特定配置。透過仔細選擇和配置驅逐策略,您可以確保您的快取機制保持高效、有效,並與應用程式的效能和資源利用率目標保持一致。

基於時間的到期:
對於每個快取提供程式,定義在一定時間段後清除快取條目的生存時間 (TTL) 間隔是不同的。例如,在Redis我們的 Spring Boot 應用程式中使用快取的情況下,我們可以使用此配置指定生存時間:

spring.cache.redis.time-to-live=10m

如果你的快取提供程式不支援生存時間,你可以使用註釋@CacheEvict和排程程式來實現它,如下所示:

@CacheEvict (value = <font>"cache1" , allEntries = true
    @Scheduled (fixedRateString =
"${your.config.key.for.ttl.in.milli}"
    public void emptyCache1 () { 
       
// 重新整理快取,除了描述性日誌外,我們不需要在這裡編寫任何程式碼!<i>
     }

自定義驅逐策略:透過為單個快取條目或所有條目定義基於事件或情況的自定義過期策略,我們可以防止快取汙染並保持其一致性。 Spring Boot 有不同的註解來支援自定義過期策略:

  • @CacheEvict: 從快取中刪除一個或所有條目。
  • @CachePut: 使用新值更新條目。
  • CacheManager:快取管理器: 我們可以使用 Spring 的 CacheManager 和 Cache 介面實現自定義驅逐策略。 為此,我們可以使用 evict()、put() 或 clear() 等方法。 我們還可以使用 getNativeCache() 方法訪問底層快取提供程式,以獲得更多功能。

定製驅逐政策最重要的是找到合適的驅逐地點和條件。

3-條件快取
條件快取與驅逐策略一起在最佳化快取策略中發揮著重要作用。在某些情況下,我們不需要將特定實體的所有資料儲存在快取中,

條件快取確保只有滿足特定條件的資料才會儲存在快取中。

這樣可以防止快取空間中不必要的資料,從而最佳化資源利用率。

@Cacheable 和 @CachePut 註解都有條件和除非屬性,允許我們定義快取項的條件:

  • 條件: 指定 SpEL(Spring Expression Language,Spring 表示式語言)表示式,該表示式必須求值為真才能快取(或更新)資料。
  • 除非: 指定一個 SpEL 表示式,該表示式必須求值為 false 才能快取(或更新)資料。

為了澄清起見,看一下這段程式碼:

@Cacheable(value = <font>"employeeByName", condition = "result.size() > 10", unless = "result.size() < 1000")
public List<Employee> employeesByName(String name) {
   
// Method logic to retrieve data<i>
    return someEmployeeList;
}

在這段程式碼中,只有當結果列表的大小大於 10 且小於 1000 時,僱員列表才會被快取。

最後一點是,與上一節類似,我們也可以使用 CacheManager 和 Cache 介面以程式設計方式實現條件快取。 這為快取行為提供了更多的靈活性和控制。

4-分散式快取與本地快取
當我們談論快取時,我們通常會想到分散式快取,例如 Redis,Memcached 或 Hazelcast。在微服務架構流行的時代,本地快取在提高應用程式效能方面也發揮著很大的作用。

瞭解本地快取和分散式快取之間的區別可以幫助您選擇正確的策略來最佳化我們的 Spring Boot 應用程式中的快取。

每種型別都有其優點和缺點,必須根據您的應用程式需求來考慮。

什麼是本地快取?
本地快取是一種快取機制,其中資料儲存在應用程式執行的同一臺機器或例項的記憶體中。一些著名的本地快取庫是 Ehcache、Caffeine 和 Guava Cache。

本地快取可以非常快速地訪問快取資料,因為它可以避免與遠端資料檢索相關的網路延遲和開銷(分散式快取)。本地快取通常比分散式快取更易於設定和管理,並且不需要額外的基礎設施。

何時應該使用本地快取,何時應該使用分散式快取?
本地快取適用於小型應用程式或微服務,其中資料集較小,可以輕鬆容納在單臺計算機的記憶體中。它也適用於低延遲至關重要且跨例項資料一致性不是主要問題的場景。

另一方面,分散式快取系統適用於具有大量資料快取需求的大型應用程式,對於這種應用程式來說,可擴充套件性、容錯性和跨多個例項的資料一致性至關重要。

在 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>


@SpringBootApplication
@EnableCaching
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

spring:
  cache:
    caffeine:
      spec: maximumSize=500,expireAfterAccess=10m


5- 自定義鍵生成策略
Spring 快取註解中的預設鍵生成演算法通常是這樣的:

  • 如果沒有給出引數,則返回 0。
  • 如果僅給出一個引數,則返回該例項。
  • 如果給出了多個引數,則返回根據所有引數的雜湊值計算出的鍵。

只要 hashCode() 能反映出自然鍵,這種方法就能很好地適用於具有自然鍵的物件。

但在某些場景下,預設的鍵生成策略並不能很好地發揮作用:

  • 我們需要有意義的鍵
  • 具有同一型別的多個引數的方法
  • 具有可選引數或空引數的方法
  • 我們需要在鍵中包含上下文資料,例如語言環境、宗旨 ID 或使用者角色,以使其具有唯一性

Spring Cache 提供了兩種方法來定義自定義鍵生成策略:
  • 為鍵屬性指定 SpEL(Spring Expression Language,Spring 表示式語言)表示式,生成新鍵時必須對該表示式進行評估:

@CachePut(value = <font>"phonebook", key = "phoneNumber.name")
    PhoneNumber create(PhoneNumber phoneNumber) {
        return phonebookRepository.insert(phoneNumber);
    }

  • 定義一個實現了 KeyGenerator 介面的 Bean,然後將其指定為 keyGenerator 屬性:

@Component(<font>"customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return
"UNIQUE_KEY";
    }
}

///////<i>

@CachePut(value =
"phonebook", keyGenerator = "customKeyGenerator")
PhoneNumber create(PhoneNumber phoneNumber) {
    return phonebookRepository.insert(phoneNumber);
}

在我們的應用中,使用自定義鍵生成策略可以大大提高快取記憶體的效率和有效性。 精心設計的鍵生成策略可確保正確、唯一地識別快取記憶體條目,從而最大限度地減少快取記憶體丟失,最大限度地提高快取記憶體命中率。

6- 非同步快取
您可能已經注意到,Spring 快取抽象 API 是阻塞和同步的,如果您使用 WebFlux 堆疊,使用 Spring 快取註解(如 @Cacheable 或 @CachePut)將快取反應堆包裝器物件(Mono 或 Flux)。 在這種情況下,你有三種方法:

  • 呼叫反應器型別上的 cache() 方法,並使用 Spring Cache 註釋對該方法進行註解。
  • 使用底層快取提供商提供的非同步 API(如果支援),並以程式設計方式處理快取。
  • 圍繞快取 API 實施非同步封裝,並使其非同步化(如果快取提供商不支援)。

不過,在 Spring Framework 6.2 釋出後,如果快取提供程式支援 WebFlux 專案的非同步快取(如 Caffeine Cache):Spring 的宣告式快取基礎架構會檢測反應式方法簽名(如返回 Reactor Mono 或 Flux),並專門處理此類方法,以便對其生成的值進行非同步快取,而不是嘗試快取返回的反應式流釋出器例項本身。 這需要目標快取提供商的支援,例如將 CaffeineCacheManager 設定為 setAsyncCacheMode(true)。

@Configuration
@EnableCaching
public class CacheConfig {
  @Bean
  public CacheManager cacheManager() {
    final CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(buildCaffeineCache());
    cacheManager.setAsyncCacheMode(true); <font>// <--<i>
    return cacheManager;
  }
}


7- 監控快取以查詢瓶頸
監控快取指標對於識別應用程式中的瓶頸和最佳化快取策略至關重要。
要監控的最重要的指標是:

  • 快取命中率:快取命中率與總快取請求數之比表明快取有效,而低命中率則表明快取未得到有效利用。
  • 快取未命中率:快取未命中數與總快取請求數之比,表明快取經常無法提供所請求的資料,可能是由於快取大小不足或金鑰管理不善造成的。
  • 快取驅逐率:快取中專案被驅逐的頻率。如果驅逐率很高,則表明快取大小太小或驅逐策略不適合訪問模式。
  • 記憶體使用情況:快取使用的記憶體量。
  • 延遲:從快取中檢索資料所需的時間。
  • 錯誤率:與快取伺服器負載相關的指標,例如每秒請求數。


 

相關文章