Spring Boot中的緩存支持(二)使用Redis做集中式緩存

上一篇介紹了在Spring Boot中如何引入緩存、緩存註解的使用、以及EhCache的整合。

雖然EhCache已經能夠適用很多應用場景,但是由於EhCache是進程內的緩存框架,在集群模式下時,各應用服務器之間的緩存都是獨立的,因此在不同服務器的進程間會存在緩存不一致的情況。即使EhCache提供了集群環境下的緩存同步策略,但是同步依然需要一定的時間,短暫的緩存不一致依然存在。

在一些要求高一致性(任何數據變化都能及時的被查詢到)的系統和應用中,就不能再使用EhCache來解決了,這個時候使用集中式緩存是個不錯的選擇,因此本文將介紹如何在Spring Boot的緩存支持中使用Redis進行數據緩存。

下面以上一篇的例子作為基礎進行改造,將緩存內容遷移到redis中。

準備工作

先來回顧一下在此案例中,我們做了什麼內容:

  • 引入了spring-data-jpa和EhCache
  • 定義了User實體,包含id、name、age字段
  • 使用spring-data-jpa實現了對User對象的數據訪問接口UserRepository
  • 使用Cache相關注解配置了緩存
  • 單元測試,通過連續的查詢和更新數據後的查詢來驗證緩存是否生效

開始改造

  • 刪除EhCache的配置文件src/main/resources/ehcache.xml
  • pom.xml中刪除EhCache的依賴,增加redis的依賴:
Spring Boot中的緩存支持(二)使用Redis做集中式緩存

  • application.properties中增加redis配置,以本地運行為例,比如:
Spring Boot中的緩存支持(二)使用Redis做集中式緩存

我們需要做的配置到這裡就已經完成了,Spring Boot會在偵測到存在Redis的依賴並且Redis的配置是可用的情況下,使用RedisCacheManager初始化CacheManager。

為此,我們可以單步運行我們的單元測試,可以觀察到此時CacheManager的實例是org.springframework.data.redis.cache.RedisCacheManager,並獲得下面的執行結果:

Hibernate: insert into user (age, name) values (?, ?)
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_ from user user0_ where user0_.name=?
第一次查詢:10
第二次查詢:10
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: update user set age=?, name=? where id=?
第三次查詢:10

可以觀察到,在第一次查詢的時候,執行了select語句;第二次查詢沒有執行select語句,說明是從緩存中獲得了結果;而第三次查詢,我們獲得了一個錯誤的結果,根據我們的測試邏輯,在查詢之前我們已經將age更新為20,但是我們從緩存中獲取到的age還是為10。

問題思考

為什麼同樣的邏輯在EhCache中沒有問題,但是到Redis中會出現這個問題呢?

在EhCache緩存時沒有問題,主要是由於EhCache是進程內的緩存框架,第一次通過select查詢出的結果被加入到EhCache緩存中,第二次查詢從EhCache取出的對象與第一次查詢對象實際上是同一個對象(可以在使用Chapter4-4-1工程中,觀察u1==u2來看看是否是同一個對象),因此我們在更新age的時候,實際已經更新了EhCache中的緩存對象。

而Redis的緩存獨立存在於我們的Spring應用之外,我們對數據庫中數據做了更新操作之後,沒有通知Redis去更新相應的內容,因此我們取到了緩存中未修改的數據,導致了數據庫與緩存中數據的不一致。

因此我們在使用緩存的時候,要注意緩存的生命週期,利用好上一篇上提到的幾個註解來做好緩存的更新、刪除

進一步修改

針對上面的問題,我們只需要在更新age的時候,通過@CachePut來讓數據更新操作同步到緩存中,就像下面這樣:

Spring Boot中的緩存支持(二)使用Redis做集中式緩存

在redis-cli中flushdb,清空一下之前的緩存內容,再執行單元測試,可以獲得下面的結果:

Hibernate: insert into user (age, name) values (?, ?)
第一次查詢:10
第二次查詢:10
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: update user set age=?, name=? where id=?
第三次查詢:20

可以看到,我們的第三次查詢獲得了正確的結果!同時,我們的第一次查詢也不是通過select查詢獲得的,因為在初始化數據的時候,調用save方法時,就已經將這條數據加入了redis緩存中,因此後續的查詢就直接從redis中獲取了。

本文內容到此為止,主要介紹了為什麼要使用Redis做緩存,以及如何在Spring Boot中使用Redis做緩存,並且通過一個小問題來幫助大家理解緩存機制,在使用過程中,一定要注意緩存生命週期的控制,防止數據不一致的情況出現。

Spring Boot中的緩存支持(二)使用Redis做集中式緩存


分享到:


相關文章: