Like Share Discussion Bookmark Smile

J.J. Huang   2019-04-12   Spring Boot   瀏覽次數:

SpringBoot - 第二十五章 | 缓存支持與Redis集中式缓存(二)

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

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

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

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

準備工作

首先,下載範例 chapte14

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

  • 引入了spring-data-jpaEhCache
  • 定義了Customer實體
  • 使用spring-data-jpa實現了對Customer對象的資料訪問接口CustomerRepository
  • 使用Cache相關註解配置了緩存
  • 單元測試,通過連續的查詢和更新資料後的查詢來驗證緩存是否生效

開始修改

  • 刪除EhCache的配置文件ehcache.xml

  • pom.xml中刪除EhCache的依賴,增加redis的依賴:

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
    </dependency>
  • application.properties中增加redis配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # REDIS (RedisProperties)
    # Redis資料庫索引(默認為0)
    spring.redis.database=0
    # Redis伺服器地址
    spring.redis.host=localhost
    # Redis伺服器連接端口
    spring.redis.port=6379
    # Redis伺服器連接密碼(默認為空)
    spring.redis.password=
    # 連接池最大連接數(使用負值表示沒有限制)
    spring.redis.pool.max-active=8
    # 連接池最大阻塞等待時間(使用負值表示沒有限制)
    spring.redis.pool.max-wait=-1
    # 連接池中的最大空閒連接
    spring.redis.pool.max-idle=8
    # 連接池中的最小空閒連接
    spring.redis.pool.min-idle=0
    # 連接超時時間(毫秒)
    spring.redis.timeout=1000
  • 修改 Customer 序列化(Serializable)

  • 修改 測試範例

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

1
2
3
4
5
6
Hibernate: select customer0_.id as id1_0_, customer0_.age as age2_0_, customer0_.create_by as create_b3_0_, customer0_.create_dt as create_d4_0_, customer0_.modify_by as modify_b5_0_, customer0_.modify_dt as modify_d6_0_, customer0_.name as name7_0_ from customer customer0_ where customer0_.name=?
第一次查詢:10
第二次查詢:10
Hibernate: select customer0_.id as id1_0_0_, customer0_.age as age2_0_0_, customer0_.create_by as create_b3_0_0_, customer0_.create_dt as create_d4_0_0_, customer0_.modify_by as modify_b5_0_0_, customer0_.modify_dt as modify_d6_0_0_, customer0_.name as name7_0_0_ from customer customer0_ where customer0_.id=?
Hibernate: update customer set age=?, create_by=?, create_dt=?, modify_by=?, modify_dt=?, name=? where id=?
第三次查詢:10

可以發現在第一次查詢的時候,執行了Select語句,第二次則沒有,直接取得緩存內的值;在第三次查詢前,我們將age更新成20,但是在第三次查詢的時候,卻得到了10的結果。


問題分析

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

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

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

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


再次修改

針對上面的問題,我們只需要在更新age的時候,通過@CachePut來讓資料更新操作同步到緩存中。

  • 修改 CustomerRepository

  • 清空Redis緩存內容

    1
    2
    $ redis-cli
    127.0.0.1:6379> flushdb

  • 再次運行我們的單元測試
1
2
3
4
5
6
7
8
Hibernate: insert into customer (age, create_by, create_dt, modify_by, modify_dt, name) values (?, ?, ?, ?, ?, ?)
2019-04-11 14:08:28.228 INFO 6376 --- [ main] io.lettuce.core.EpollProvider : Starting without optional epoll library
2019-04-11 14:08:28.230 INFO 6376 --- [ main] io.lettuce.core.KqueueProvider : Starting without optional kqueue library
第一次查詢:10
第二次查詢:10
Hibernate: select customer0_.id as id1_0_0_, customer0_.age as age2_0_0_, customer0_.create_by as create_b3_0_0_, customer0_.create_dt as create_d4_0_0_, customer0_.modify_by as modify_b5_0_0_, customer0_.modify_dt as modify_d6_0_0_, customer0_.name as name7_0_0_ from customer customer0_ where customer0_.id=?
Hibernate: update customer set age=?, create_by=?, create_dt=?, modify_by=?, modify_dt=?, name=? where id=?
第三次查詢:20

以上範例建議用Debug的方式一步一步去看,可以看到第一次查詢是直接從緩存中取得,因為在初始化資料的時候,執行save的方法就已經將這個資料寫入到Redis裡面了,所以後面的第一次和第二次的查詢都直接取緩存資料。
然後在第三次查詢前,更新的資料也一併剛新Redis緩存,所以查詢出來的值就是正確的了。

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

註:以上參考了
SpringBoot2.0+整合redis,使用 RedisTemplate操作redis
oKongSpringBoot | 第十一章:Redis的集成和简单使用文章。
程序猿DD-翟永超Spring Boot中的缓存支持(二)使用Redis做集中式缓存 文章。