日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Cache

發布時間:2025/3/15 javascript 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Cache 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在WEB后端應用程序來說,耗時比較大的往往有兩個地方:一個是查數據庫,一個是調用其它服務的API(因為其它服務最終也要去做查數據庫等耗時操作)。重復查詢也有兩種。一種是我們在應用程序中代碼寫得不好,寫的for循環,可能每次循環都用重復的參數去查詢了。這種情況,比較聰明一點的程序員都會對這段代碼進行重構,用Map來把查出來的東西暫時放在內存里,后續去查詢之前先看看Map里面有沒有,沒有再去查數據庫,其實這就是一種緩存的思想。另一種重復查詢是大量的相同或相似請求造成的。比如資訊網站首頁的文章列表、電商網站首頁的商品列表、微博等社交媒體熱搜的文章等等,當大量的用戶都去請求同樣的接口,同樣的數據,如果每次都去查數據庫,那對數據庫來說是一個不可承受的壓力。所以我們通常會把高頻的查詢進行緩存,我們稱它為“熱點”。

一、為什么使用Spring Cache

前面提到了緩存有諸多的好處,于是大家就摩拳擦掌準備給自己的應用加上緩存的功能。但是網上一搜卻發現緩存的框架太多了,各有各的優勢,比如Redis、Memcached、Guava、Caffeine等等。如果我們的程序想要使用緩存,就要與這些框架耦合。聰明的架構師已經在利用接口來降低耦合了,利用面向對象的抽象和多態的特性,做到業務代碼與具體的框架分離。但我們仍然需要顯式地在代碼中去調用與緩存有關的接口和方法,在合適的時候插入數據到緩存里,在合適的時候從緩存中讀取數據。
想一想AOP的適用場景,這不就是天生就應該AOP去做的嗎?
是的,Spring Cache就是一個這個框架。它利用了AOP,實現了基于注解的緩存功能,并且進行了合理的抽象,業務代碼不用關心底層是使用了什么緩存框架,只需要簡單地加一個注解,就能實現緩存功能了。而且Spring Cache也提供了很多默認的配置,用戶可以3秒鐘就使用上一個很不錯的緩存功能。

既然有這么好的輪子,干嘛不用呢?

二、如何使用Spring Cache

上面的3秒鐘,絕對不夸張。使用SpringCache分為很簡單的三步:加依賴,開啟緩存,加緩存注解。

本文示例代碼使用的是官方的示例代碼,git地址:github.com/spring-guid…

  • 加依賴
  • gradle:

    implementation 'org.springframework.boot:spring-boot-starter-cache'

    maven:

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId> </dependency>
  • 開啟緩存
  • 在啟動類加上@EnableCaching注解即可開啟使用緩存。

    @SpringBootApplication @EnableCaching public class CachingApplication {public static void main(String[] args) {SpringApplication.run(CachingApplication.class, args);} }
  • 加緩存注解
  • 在要緩存的方法上面添加@Cacheable注解,即可緩存這個方法的返回值。

    @Override @Cacheable("books") public Book getByIsbn(String isbn) {simulateSlowService();return new Book(isbn, "Some book"); } // Don't do this at home private void simulateSlowService() {try {long time = 3000L;Thread.sleep(time);} catch (InterruptedException e) {throw new IllegalStateException(e);} }
  • 測試
  • @Override public void run(String... args) {logger.info(".... Fetching books");logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); }

    測試一下,可以發現。第一次和第二次(第二次參數和第一次不同)調用getByIsbn方法,會等待3秒,而后面四個調用,都會立即返回。

    三、常用注解

    Spring Cache有幾個常用注解,分別為@Cacheable、@CachePut、@CacheEvict、@Caching、 @CacheConfig。除了最后一個CacheConfig外,其余四個都可以用在類上或者方法級別上,如果用在類上,就是對該類的所有public方法生效,下面分別介紹一下這幾個注解。

  • @Cacheable
  • @Cacheble注解表示這個方法有了緩存的功能,方法的返回值會被緩存下來,下一次調用該方法前,會去檢查是否緩存中已經有值,如果有就直接返回,不調用方法。如果沒有,就調用方法,然后把結果緩存起來。這個注解一般用在查詢方法上。

  • @CachePut:用于解決緩存數據一致性中的雙寫模式,需要有返回值
  • 加了@CachePut注解的方法,會把方法的返回值put到緩存里面緩存起來,供其它地方使用。它通常用在新增方法上。

  • @CacheEvict:用于解決緩存數據一致性中的失效模式
  • 使用了CacheEvict注解的方法,會清空指定緩存。一般用在更新或者刪除的方法上。

  • @Caching
  • Java注解的機制決定了,一個方法上只能有一個相同的注解生效。那有時候可能一個方法會操作多個緩存(這個在刪除緩存操作中比較常見,在添加操作中不太常見)。Spring Cache當然也考慮到了這種情況,@Caching注解就是用來解決這類情況的,大家一看它的源碼就明白了。

    public @interface Caching {Cacheable[] cacheable() default {};CachePut[] put() default {};CacheEvict[] evict() default {}; }
  • @CacheConfig
  • 前面提到的四個注解,都是Spring Cache常用的注解。每個注解都有很多可以配置的屬性,這個我們在下一節再詳細解釋。但這幾個注解通常都是作用在方法上的,而有些配置可能又是一個類通用的,這種情況就可以使用@CacheConfig了,它是一個類級別的注解,可以在類級別上配置cacheNames、keyGenerator、cacheManager、cacheResolver等。

  • 注解的屬性
    • key: key的來源可分為三類,分別是:默認的、keyGenerator生成的、主動指定的。
    • condition:在激活注解功能前,進行condition驗證,如果condition結果為true,則表明驗證通過,緩存注解生效;否則緩存注解不生效。condition作用時機在:緩存注解檢查緩存中是否有對應的key-value 之前。注:緩存注解檢查緩存中是否有對應的key-value 在運行目標方法之前,所以 condition作用時機也在運行目標方法之前。
    • cacheNames:通過cacheNames對數據進行隔離,不同cacheName下可以有相同的key。也可稱呼cacheName為命名空間。實際上(以spring-cache為例),可以通過設置RedisCacheConfiguration#usePrefix的true或false來控制是否使用前綴。如果否,那么最終的redis鍵就是key值;如果是,那么就會根據cacheName生成一個前綴,然后再追加上key作為最終的redis鍵.cacheName還有其它重要的功能:cacheName(就像其名稱【命名空間】所說)實現了數據分區的功能,一些操作可以直接按照命名空間批量進行。如:spring框架中的Cache實際對應的就是一個【命名空間】,spring會先去找到數據所在的命名空間(即:先找到對應的Cache),再由Cache結合key,最終定位到數據。

    注意:若屬性cacheNames(或屬性value)指定了多個命名空間;當進行緩存存儲時,會在這些命名空間下都存一份key-value。當進行緩存讀取時,會按照cacheNames值里命名空間的順序,挨個挨個從命名空間中查找對應的key,如果在某個命名空間中查找打了對應的緩存,就不會再查找排在后面的命名空間,也不會再執行對應方法,直接返回緩存中的value值。

    • unless:功能是:是否令注解(在方法執行后的功能)不生效;若unless的結果為true,則(方法執行后的功能)不生效;若unless的結果為false,則(方法執行后的)功能生效。注:unless默認為"",即相當于默認為false。unless的作用時機:目標方法運行后。注:如果(因為直接從緩存中獲取到了數據,而導致)目標方法沒有被執行,那么unless字段不生效。
    • allEntries:此屬性主要出現在@CacheEvict注解中,表示是否清除指定命名空間中的所有數據,默認為false。
    • beforeInvocation:此屬性主要出現在@CacheEvict注解中,表示 是否在目標方法執行前使 此注解生效。 默認為false,即:目標方法執行完畢后此注解生效。

    三、常用注解的配置原理

    注解使用詳細例子
    這部分我們最好是結合源碼來看,才能更好地理解這些配置的運作機制。
    源碼:解析注解的時機。
    這一節主要是源碼解析,有點晦澀,對源碼不感興趣的同學可以跳過。但如果想要弄清楚Spring Cache運作的原理,還是推薦一看的。前面提到的幾個注解@Cacheable、@CachePut、@CacheEvict、@CacheConfig,都有一些可配置的屬性。這些配置的屬性都可以在抽象類CacheOperation及其子類中可以找到。它們大概是這樣的關系:

    看到這里不得不佩服,這繼承用得,妙啊。

    解析每個注解的代碼在SpringCacheAnnotationParser類中可以找到,比如parseEvictAnnotation方法,里面就有這么一句:

    builder.setCacheWide(cacheEvict.allEntries());

    明明注解里叫allEntries,但是CacheEvictOperation里卻叫cacheWide?看了下作者,都是多個作者,但第一作者都是一個叫Costin Leau的哥們,我對這個命名還是有一點小小的困惑。。。看來大佬們寫代碼也會有命名不一致的問題

    那這個SpringCacheAnnotationParser是在什么時候被調用的呢?很簡單,我們在這個類的某個方法上打個斷點,然后debug就行了,比如parseCacheableAnnotation方法。

    在debug界面,可以看到調用鏈非常長,前面是我們熟悉的IOC注冊Bean的一個流程,直到我們看到了一個叫做AbstractAutowireCapableBeanFactory的BeanFactory,然后這個類在創建Bean的時候會去找是否有Advisor。正好Spring Cache源碼里就定義了這么一個Advisor:BeanFactoryCacheOperationSourceAdvisor。這個Advisor返回的PointCut是一個CacheOperationSourcePointcut,這個PointCut復寫了matches方法,在里面去獲取了一個CacheOperationSource,調用它的getCacheOperations方法。這個CacheOperationSource是個接口,主要的實現類是AnnotationCacheOperationSource。在findCacheOperations方法里,就會調用到我們最開始說的SpringCacheAnnotationParser了。

    這樣就完成了基于注解的解析。

    四、入口:基于AOP的攔截器

    那我們實際調用方法的時候,是怎么處理的呢?我們知道,使用了AOP的Bean,會生成一個代理對象,實際調用的時候,會執行這個代理對象的一系列的Interceptor。Spring Cache使用的是一個叫做CacheInterceptor的攔截器。我們如果加了緩存相應的注解,就會走到這個攔截器上。這個攔截器繼承了CacheAspectSupport類,會執行這個類的execute方法,這個方法就是我們要分析的核心方法了。

    @Cacheable的sync

    我們繼續看之前提到的execute方法,該方法首先會判斷是否是同步。這里的同步配置是用的@Cacheable的sync屬性,默認是false。如果配置了同步的話,多個線程嘗試用相同的key去緩存拿數據的時候,會是一個同步的操作。(加鎖)

    我們來看看同步操作的源碼。如果判斷當前需要同步操作

    (1)、首先會去判斷當前的condition是不是符合條件
    (2)、這里的condition也是@Cacheable中定義的一個配置,它是一個EL表達式,比如我們可以這樣用來緩存id大于1的Book:

    @Override @Cacheable(cacheNames = "books", condition = "#id > 1", sync = true) public Book getById(Long id) {return new Book(String.valueOf(id), "some book"); }

    如果不符合條件,就不使用緩存,也不把結果放入緩存,直接跳到5。否則,嘗試獲取key

    • (3)、在獲取key的時候,會先判斷用戶有沒有定義key,它也是一個EL表達式。如果沒有的話,就用keyGenerator生成一個key:
    @Nullable protected Object generateKey(@Nullable Object result) {if (StringUtils.hasText(this.metadata.operation.getKey())) {EvaluationContext evaluationContext = createEvaluationContext(result);return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);}return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args); }

    我們可以用這種方式手動指定根據id生成book-1,book-2這樣的key:

    @Override @Cacheable(cacheNames = "books", sync = true, key = "'book-' + #id") public Book getById(Long id) {return new Book(String.valueOf(id), "some book"); }

    這里的key是一個Object對象,如果我們不在注解上面指定key,會使用keyGenerator生成的key。默認的keyGenerator是SimpleKeyGenerator,它生成的是一個SimpleKey對象,方法也很簡單,如果沒有入參,就返回一個EMPTY的對象,如果有入參,且只有一個入參,并且不是空或者數組,就用這個參數(注意這里用的是參數本身,而不是SimpleKey對象。否則,用所有入參包一個SimpleKey。

    源碼:

    @Override public Object generate(Object target, Method method, Object... params) {return generateKey(params); } /*** Generate a key based on the specified parameters.*/ public static Object generateKey(Object... params) {if (params.length == 0) {return SimpleKey.EMPTY;}if (params.length == 1) {Object param = params[0];if (param != null && !param.getClass().isArray()) {return param;}}return new SimpleKey(params); }

    看到這里你一定有一個疑問吧,這里只用入參,沒有類名和方法名的區別,那如果兩個方法入參一樣,豈不是key沖突了?

    你的感覺沒錯,大家可以試一下這兩個方法:

    // 定義兩個參數都是String的方法 @Override @Cacheable(cacheNames = "books", sync = true) public Book getByIsbn(String isbn) {simulateSlowService();return new Book(isbn, "Some book"); }@Override @Cacheable(cacheNames = "books", sync = true) public String test(String test) {return test; }

    // 調用這兩個方法,用相同的參數"test"

    logger.info("test getByIsbn -->" + bookRepository.getByIsbn("test")); logger.info("test test -->" + bookRepository.test("test"));

    你會發現兩次生成的key相同,然后在調用test方法的時候,控制臺會報錯:

    Caused by: java.lang.ClassCastException: class com.example.caching.Book cannot be cast to class java.lang.String (com.example.caching.Book is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')at com.sun.proxy.$Proxy33.test(Unknown Source) ~[na:na]at com.example.caching.AppRunner.run(AppRunner.java:23) ~[main/:na]at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:795) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]... 5 common frames omitted

    Book不能強轉成String,因為我們第一次調用getByIsbn方法的時候,生成的key是test,然后換成了返回值Book對象到緩存里面。而調用test方法的時候,生成的key還是test,就會取出Book,但是test方法的返回值是String,所以會嘗試強轉到String,結果發現強轉失敗。

    我們可以自定義一個keyGenerator來解決這個問題:

    @Component public class MyKeyGenerator implements KeyGenerator {@Overridepublic Object generate(Object target, Method method, Object... params) {return target.getClass().getName() + method.getName() + Stream.of(params).map(Object::toString).collect(Collectors.joining(","));} }

    然后就可以在配置里面使用這個自定義的MyKeyGenerator了,再次運行程序,就不會出現上述問題。

    @Override @Cacheable(cacheNames = "books", sync = true, keyGenerator = "myKeyGenerator") public Book getByIsbn(String isbn) {simulateSlowService();return new Book(isbn, "Some book"); }@Override @Cacheable(cacheNames = "books", sync = true, keyGenerator = "myKeyGenerator") public String test(String test) {return test; }

    接著往下看,可以看到我們得到了一個Cache。這個Cache是在我們調用CacheAspectSupport的execute方法的時候,會new一個CacheOperationContext。在這個Context的構造方法里,會用cacheResolver去解析注解中的Cache,生成Cache對象。默認的cacheResolver是SimpleCacheResolver,它從CacheOperation中取得配置的cacheNames,然后用cacheManager去get一個Cache。這里的cacheManager是用于管理Cache的一個容器,默認的cacheManager是ConcurrentMapCacheManager。聽名字就知道是基于ConcurrentMap來做的了,底層是ConcurrentHashMap。那這里的Cache是什么東西呢?Cache就對“緩存容器”的一個抽象,包含了緩存會用到的get、put、evict、putIfAbsent等方法。不同的cacheNames會對應不同的Cache對象,比如我們可以在一個方法上定義兩個cacheNames,雖然也可以用value,它是cacheNames的別名,但如果有多個配置的時候,更推薦用cacheNames,因為這樣具有更好的可讀性。

    @Override @Cacheable(cacheNames = {"book", "test"}) public Book getByIsbn(String isbn) {simulateSlowService();return new Book(isbn, "Some book"); }

    默認的Cache是ConcurrentMapCache,它也是基于ConcurrentHashMap的。但這里有個問題,我們回到上面的execute方法的代碼,發現如果設置了sync為true,它取的是第一個Cache,而沒有管剩下的Cache。所以如果你配置了sync為true,只支持配置一個cacheNames,如果配了多個,就會報錯:

    @Cacheable(sync=true) only allows a single cache on...

    繼續往下看,發現調用的是Cache的get(Object, Callcable)方法。這個方法會先嘗試去緩存中用key取值,如果取不到在調用callable函數,然后加到緩存里。Spring Cache也是期望Cache的實現類在這個方法內部實現“同步”的功能。所以我們再回過頭去看Cacheable中sync屬性上方的注釋,它寫到:`使用sync為true,會有這些限制:

    • 不支持unless,這個從代碼可以看到,只支持了condition,沒有支持unless;
    • 只能有一個cache,因為代碼就寫死了一個。我猜這是為了更好地支持同步,它把同步放到了Cache里面去實現。
    • 沒有不支持其它的Cache操作,代碼里面寫死了,只支持Cachable,我猜這也是為了支持同步。

    如果sync為false呢?

    繼續往下看execute的代碼,大概經歷了下面這些步驟:

  • 嘗試在方法調用前刪除緩存,這個在@CacheEvict配置的beforeInvocation,默認為false(如果為true才會在這一步刪除緩存);
  • 嘗試獲取緩存;
  • 如果第2步獲取不到,嘗試獲取Cachable的注解,生成相應的CachePutRequest;
  • 如果第2步獲取到了,并且沒有CachPut注解,就直接從緩存中獲取值。否則,調用目標方法;
  • 解析CachePut注解,同樣生成相應的CachePutRequest;
  • 執行所有的CachePutRequest;
  • 嘗試在方法調用后刪除緩存,如果@CacheEvict配置的beforeInvocation為false會刪除緩存
  • 至此,我們就結合源碼解釋完了所有的配置發生作用的時機。

    五、使用其它緩存框架

    如果要使用其它的緩存框架,應該怎么做呢?通過上面的源碼分析我們知道,如果要使用其它的緩存框架,我們只需要重新定義好CacheManager和CacheResolver這兩個Bean就行了。事實上,Spring會自動檢測我們是否引入了相應的緩存框架,如果我們引入了spring-data-redis,Spring就會自動使用spring-data-redis提供的RedisCacheManager,RedisCache。如果我們要使用Caffeine框架。只需要引入Caffeine,Spring Cache就會默認使用CaffeineCacheManager和CaffeineCache。

    implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'com.github.ben-manes.caffeine:caffeine'

    Caffeine是一個性能非常高的緩存框架,它使用了Window TinyLfu回收策略,提供了一個近乎最佳的命中率。Spring Cache還支持各種配置,在CacheProperties類里面,里面還提供了各種主流的緩存框架的特殊配置。比如Redis的過期時間等(默認永不過期)。

    private final Caffeine caffeine = new Caffeine();private final Couchbase couchbase = new Couchbase();private final EhCache ehcache = new EhCache();private final Infinispan infinispan = new Infinispan();private final JCache jcache = new JCache();private final Redis redis = new Redis();

    六、使用緩存帶來的問題

  • 雙寫不一致
  • 使用緩存會帶來許多問題,尤其是高并發下,包括緩存穿透、緩存擊穿、緩存雪崩、雙寫不一致等問題。具體的問題介紹和常用的解決方案可以參考我的個人網站上的文章《緩存常見問題及解決方案》。其中主要聊一下雙寫不一致的問題,這是一個比較常見的問題,其中一個常用的解決方案是,更新的時候,先刪除緩存,再更新數據庫。所以Spring Cache的@CacheEvict會有一個beforeInvocation的配置。但使用緩存通常會存在緩存中的數據和數據庫中不一致的問題,尤其是調用第三方接口,你不會知道它什么時候更新了數據。但使用緩存的業務場景很多時候并不需求數據的強一致,比如首頁的熱點文章,我們可以讓緩存一分鐘失效,這樣就算一分鐘內,不是最新的熱點排行也沒關系。

  • 占用額外的內存
  • 這個是無可避免的。因為總要有一個地方去放緩存。不管是ConcurrentHashMap也好,Redis也好,Caffeine也好,總歸是會占用額外的內存資源去放緩存的。但緩存的思想正是用空間去換時間,有時候占用這點額外的空間對于時間上的優化來說,是非常值得的。這里需要注意的是,SpringCache默認使用的是ConcurrentHashMap,它不會自動回收key,所以如果使用默認的這個緩存,程序就會越來越大,并且得不到回收。最終可能導致OOM。

    我們來模擬實驗一下:

    @Component public class MyKeyGenerator implements KeyGenerator {@Overridepublic Object generate(Object target, Method method, Object... params) {// 每次都生成不同的keyreturn UUID.randomUUID().toString();} } //調它個100w次 for (int i = 0; i < 1000000; i++) {bookRepository.test("test"); }

    然后把最大內存設置成20M: -Xmx20M。

    我們先來測試默認的基于ConcurrentHashMap的緩存,發現它很快就會報OOM。

    Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "RMI TCP Connection(idle)"

    我們使用Caffeine,并且配置一下它的最大容量:

    spring:cache:caffeine:spec: maximumSize=100

    再次運行程序,發現正常運行,不會報錯。所以如果是用基于同一個JVM內存的緩存的話,個人比較推薦使用Caffeine,強烈不推薦用默認的基于ConcurrentHashMap的實現。那什么情況適合用Redis這種需要調用第三方進程的緩存呢?如果你的應用程序是分布式的,一個服務器查詢出來后,希望其它服務器也能用這個緩存,那就推薦使用基于Redis的緩存。使用Spring Cache也有不好之處,就是屏蔽了底層緩存的特性。比如,很難做到不同的場景有不同的過期時間(但并不是做不到,也可以通過配置不同的cacheManager來實現)。但整體上來看,還是利大于弊的,大家自己衡量,適合自己就好。

    七、總結

    * 每一個需要緩存的數據我們都來指定要放到那個名字的緩存。【緩存的分區(按照業務類型分)* 代表當前方法的結果需要緩存,如果緩存中有,方法都不用調用,如果緩存中沒有,會調用方法。最后將方法的結果放入緩存* 默認行為* 如果緩存中有,方法不再調用* key是默認生成的:緩存的名字::SimpleKey::[](自動生成key值)* 緩存的value值,默認使用jdk序列化機制,將序列化的數據存到redis中* 默認時間是 -1** 自定義操作:key的生成* 指定生成緩存的key:key屬性指定,接收一個Spel* 指定緩存的數據的存活時間:配置文檔中修改存活時間* 將數據保存為json格式*** Spring-Cache的不足之處:* 1)、讀模式* 緩存穿透:查詢一個null數據。解決方案:緩存空數據* 緩存擊穿:大量并發進來同時查詢一個正好過期的數據。解決方案:加鎖 ? 默認是無加鎖的;使用sync = true來解決擊穿問題* 緩存雪崩:大量的key同時過期。解決:加隨機時間。加上過期時間* 2)、寫模式:(緩存與數據庫一致)* 1)、讀寫加鎖。* 2)、引入Canal,感知到MySQL的更新去更新Redis* 3)、讀多寫多,直接去數據庫查詢就行** 總結:* 常規數據(讀多寫少,即時性,一致性要求不高的數據,完全可以使用Spring-Cache):寫模式(只要緩存的數據有過期時間就足夠了)* 特殊數據:特殊設計** 原理:* CacheManager(RedisCacheManager)->Cache(RedisCache)->Cache負責緩存的讀寫

    文章轉自

    總結

    以上是生活随笔為你收集整理的Spring Cache的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。