首頁 > 軟體

SpringBoot使用Caffeine實現快取的範例程式碼

2022-07-04 18:04:57

在本部落格中,我們將探討如何使用Spring的快取框架向任何Spring Boot應用程式新增基本快取支援,如果沒有正確實現,還將探討快取的一些問題。最後但並非最不重要的一點是,我們將看幾個在真實場景中有用的快取範例。

為什麼要在應用程式中新增快取

在深入探討如何嚮應用程式新增快取之前,首先想到的問題是為什麼我們需要在應用程式中使用快取。

假設有一個包含客戶資料的應用程式,使用者發出兩個請求來獲取客戶的資料(id=100)。

這就是沒有快取時的情況。

如您所見,對於每個請求,應用程式都會轉到資料庫獲取資料。從資料庫獲取資料是一項成本高昂的操作,因為它涉及IO。

但是,如果中間有一個快取儲存,可以在其中臨時儲存短時間的資料,則可以將這些往返儲存到資料庫並在IO時間儲存。

這就是使用快取時上述互動的樣子。

在Spring Boot應用程式中實現快取

SpringBoot提供了什麼快取支援?

  • SpringBoot只提供了一個快取抽象,您可以使用它將快取透明、輕鬆地新增到Spring應用程式中。
  • 它不提供實際的快取儲存。
  • 但是,它可以與不同型別的快取提供程式一起工作,如Ehcache、Hazelcast、Redis、Caffee等。
  • SpringBoot的快取抽象可以新增到方法中(使用註釋)
  • 基本上,在執行方法之前,Spring框架將檢查方法資料是否已經快取
  • 如果是,則它將從快取中獲取資料。
  • 否則它將執行該方法並快取資料
  • 它還提供了從快取中更新或刪除資料的抽象。
  • 在我們當前的部落格中,我們將瞭解如何使用Caffeine新增快取,Caffeine是一種基於Java8的高效能、接近最優的快取庫。

您可以在 application.yaml 檔案中指定使用哪個快取提供程式來設定 spring.cache.type 屬性。

但是,如果沒有提供屬性,Spring將根據新增的庫自動檢測快取提供程式。

新增生成依賴項

現在假設您已經啟動並執行了基本的Spring boot應用程式,讓我們新增快取依賴項。

開啟 build.gradle 檔案,並新增以下依賴項以啟用Spring Boot的快取

compile('org.springframework.boot:spring-boot-starter-cache')

接下來我們將新增對Caffeine的依賴

compile group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '2.8.5'

快取設定

現在我們需要在Spring Boot應用程式中啟用快取。

為此,我們需要建立一個設定類並提供註釋 @EnableCaching 。

@Configuration
@EnableCaching
public class CacheConfig {
     
}

現在這個類是一個空類,但是我們可以向它新增更多設定(如果需要)。

現在我們已經啟用了快取,讓我們提供快取名稱和快取屬性的設定,如快取大小、快取過期時間等

最簡單的方法是在 application.yaml 中新增設定

spring:
  cache:
    cache-names: customers, users, roles
    caffeine:
      spec: maximumSize=500, expireAfterAccess=60s

上述設定執行以下操作

  • 將可用快取名稱限制為客戶、使用者和角色。將最大快取大小設定為500。
  • 當快取中的物件數達到此限制時,將根據快取逐出策略從快取中刪除物件。將快取過期時間設定為1分鐘。
  • 這意味著專案將在新增到快取1分鐘後從快取中刪除。

還有另一種設定快取的方法,而不是在 application.yaml 檔案中設定快取。

您可以在快取設定類中新增並提供一個 CacheManager Bean,該Bean可以完成與上面在 application.yaml 中的設定完全相同的工作

@Bean
public CacheManager cacheManager() {
    Caffeine<Object, Object> caffeineCacheBuilder =
        Caffeine.newBuilder()
            .maximumSize(500)
            .expireAfterAccess(
                    1, TimeUnit.MINUTES);
     
    CaffeineCacheManager cacheManager = 
            new CaffeineCacheManager(
            "customers", "roles", "users");
    cacheManager.setCaffeine(caffeineCacheBuilder);
    return cacheManager;
}

在我們的程式碼範例中,我們將使用Java設定。

我們可以在Java中做更多的事情,比如設定 RemovalListener ,當一個項從快取中刪除時執行 RemovalListener ,或者啟用快取統計記錄,等等。

快取方法結果

在我們使用的範例Spring boot應用程式中,我們已經有了以下API GET /API/v1/customer/{id} 來檢索客戶記錄。

我們將向CustomerService類的 getCustomerByd(longCustomerId) 方法新增快取。

要做到這一點,我們只需要做兩件事

1. 將註釋 @CacheConfig(cacheNames=“customers”) 新增到 CustomerService 類

提供此選項將確保 CustomerService 的所有可快取方法都將使用快取名稱“customers”

2. 向方法 Optional getCustomerById(Long customerId) 新增註釋 @Cacheable

@Service
@Log4j2
@CacheConfig(cacheNames = "customers")
public class CustomerService {
 
    @Autowired
    private CustomerRepository customerRepository;
 
    @Cacheable
    public Optional<Customer> getCustomerById(Long customerId) {
        log.info("Fetching customer by id: {}", customerId);
        return customerRepository.findById(customerId);
    }
}

另外,在方法 getCustomerById() 中新增一個 LOGGER 語句,以便我們知道服務方法是否得到執行,或者值是否從快取返回。

複製程式碼 程式碼如下:
log.info("Fetching customer by id: {}", customerId);

測試快取是否正常工作

這就是快取工作所需的全部內容。現在是測試快取的時候了。

啟動您的應用程式,並點選客戶獲取url

http://localhost:8080/api/v1/customer/

在第一次API呼叫之後,您將在紀錄檔中看到以下行—“ Fetching customer by id ”。

但是,如果再次點選API,您將不會在紀錄檔中看到任何內容。這意味著該方法沒有得到執行,並且從快取返回客戶記錄。

現在等待一分鐘(因為快取過期時間設定為1分鐘)。

一分鐘後再次點選GETAPI,您將看到下面的語句再次被記錄——“通過id獲取客戶”。

這意味著客戶記錄在1分鐘後從快取中刪除,必須再次從資料庫中獲取。

為什麼快取有時會很危險

快取更新/失效

通常我們快取 GET 呼叫,以提高效能。

但我們需要非常小心的是快取物件的更新/刪除。

@CachePut
@cacheexecute

如果未將 @CachePut/@cacheexecute 放入更新/刪除方法中,GET呼叫中快取返回的物件將與資料庫中儲存的物件不同。考慮下面的範例場景。

如您所見,第二個請求已將人名更新為“ John Smith ”。但由於它沒有更新快取,因此從此處開始的所有請求都將從快取中獲取過時的個人記錄(“ John Doe ”),直到該項在快取中被刪除/更新。

快取複製

大多數現代web應用程式通常有多個應用程式節點,並且在大多數情況下都有一個負載平衡器,可以將使用者請求重定向到一個可用的應用程式節點。

這種型別的部署為應用程式提供了可伸縮性,任何使用者請求都可以由任何一個可用的應用程式節點提供服務。

在這些分散式環境(具有多個應用伺服器節點)中,快取可以通過兩種方式實現

  • 應用伺服器中的嵌入式快取(正如我們現在看到的)
  • 遠端快取伺服器

嵌入式快取

嵌入式快取駐留在應用程式伺服器中,它隨應用程式伺服器啟動/停止。由於每臺伺服器都有自己的快取副本,因此對其快取的任何更改/更新都不會自動反映在其他應用程式伺服器的快取中。

考慮具有嵌入式快取的多節點應用伺服器的下面場景,其中使用者可以根據應用伺服器為其請求服務而得到不同的結果。

正如您在上面的範例中所看到的,更新請求更新了 Application Node2 的資料庫和嵌入式快取。

但是, Application Node1 的嵌入式快取未更新,並且包含過時資料。因此, Application Node1 的任何請求都將繼續服務於舊資料。

要解決這個問題,您需要實現 CACHE REPLICATION —其中任何一個快取中的任何更新都會自動複製到其他快取(下圖中顯示為藍色虛線)

遠端快取伺服器

解決上述問題的另一種方法是使用遠端快取伺服器(如下所示)。

 

然而,這種方法的最大缺點是增加了響應時間——這是由於從遠端快取伺服器獲取資料時的網路延遲(與記憶體快取相比)

快取自定義

到目前為止,我們看到的快取範例是嚮應用程式新增基本快取所需的唯一程式碼。

然而,現實世界的場景可能不是那麼簡單,可能需要進行一些客製化。在本節中,我們將看到幾個這樣的例子

快取金鑰

我們知道快取是金鑰、值對的儲存。

範例1:預設快取鍵–具有單引數的方法

最簡單的快取鍵是當方法只有一個引數,並且該引數成為快取鍵時。在下面的範例中, Long customerId 是快取鍵

範例2:預設快取鍵–具有多個引數的方法

在下面的範例中,快取鍵是所有三個引數的SimpleKey– countryId 、 regionId 、 personId 。

範例3:自定義快取金鑰

在下面的範例中,我們將此人的 emailAddress 指定為快取的金鑰

範例4:使用 KeyGenerator 的自定義快取金鑰

讓我們看看下面的範例–如果要快取當前登入使用者的所有角色,該怎麼辦。

該方法中沒有提供任何引數,該方法在內部獲取當前登入使用者並返回其角色。

為了實現這個需求,我們需要建立一個如下所示的自定義金鑰生成器

然後我們可以在我們的方法中使用這個鍵生成器,如下所示。

條件快取

在某些用例中,我們只希望在滿足某些條件的情況下快取結果

範例1(支援 java.util.Optional –僅當存在時才快取)

僅當結果中存在 person 物件時,才快取 person 物件。

@Cacheable( value = "persons", unless = "#result?.id")
public Optional<Person> getPerson(Long personId)

範例2(如果需要,by-pass快取)

@Cacheable(value = "persons", condition="#fetchFromCache")
public Optional<Person> getPerson(long personId, boolean fetchFromCache)

僅當方法引數“ fetchFromCache ”為true時,才從快取中獲取人員。通過這種方式,方法的呼叫方有時可以決定繞過快取並直接從資料庫獲取值。

範例3(基於物件屬性的條件計算)

僅當價格低於500且產品有庫存時,才快取產品。

@Cacheable( 
   value="products", 
   condition="#product.price<500",
   unless="#result.outOfStock")
public Product findProduct(Product product)

@CachePut

我們已經看到 @Cacheable 用於將專案放入快取。

但是,如果該物件被更新,並且我們想要更新快取,該怎麼辦?

我們已經在前面的一節中看到,不更新快取post任何更新操作都可能導致從快取返回錯誤的結果。

@CachePut(key = "#person.id")
public Person update(Person person)

但是如果 @Cacheable 和 @CachePut 都將一個專案放入快取,它們之間有什麼區別?

主要區別在於實際的方法執行

@Cacheable
@CachePut

快取失效

快取失效與將物件放入快取一樣重要。

當我們想要從快取中刪除一個或多個物件時,有很多場景。讓我們看一些例子。

例1

假設我們有一個用於批次匯入個人記錄的API。

我們希望在呼叫此方法之前,應該清除整個 person 快取(因為大多數 person 記錄可能會在匯入時更新,而快取可能會過時)。我們可以這樣做如下

@CacheEvict(
   value = "persons", 
   allEntries = true, 
   beforeInvocation = true)
public void importPersons()

例2

我們有一個Delete Person API,我們希望它在刪除時也能從快取中刪除 Person 記錄。

@CacheEvict(
   value = "persons", 
   key = "#person.emailAddress")
public void deletePerson(Person person)

預設情況下 @CacheEvict 在方法呼叫後執行。

到此這篇關於SpringBoot使用Caffeine實現快取的範例程式碼的文章就介紹到這了,更多相關SpringBoot Caffeine快取內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com