Spring Cache抽象詳解

對緩存的理解--以空間換時間

緩存--(犧牲了空間或說提供更多的空間)讓數據更接近於使用者

工做機制是--先從緩存中讀取數據,若是沒有再從慢速設備上讀取實際數據(數據也會存入緩存)

緩存什麼--那些常常讀取且不常常修改的數據/那些昂貴(CPU/IO)的且對於相同的請求有相同的計算結果的數據

舉例1--CPU--L1/L2--內存--磁盤

CPU須要數據時先從L1/L2中讀取,若是沒有到內存中找,若是尚未會到磁盤上找。java

舉例2--Maven

還有如用過Maven的朋友都應該知道,咱們找依賴的時候,先從本機倉庫找,再從本地服務器倉庫找,最後到遠程倉庫服務器找。程序員

舉例3--京東倉儲

還有如京東的物流爲何那麼快?他們在各個地都有分倉庫,若是該倉庫有貨物那麼送貨的速度是很是快的。算法

幾個重要的指標

緩存命中率--代表緩存是否運行良好的

即【從緩存中讀取數據的次數】與【總讀取次數】的比率,命中率越高越好:spring

命中率 = 從緩存中讀取次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])數據庫

Miss率 = 沒有從緩存中讀取的次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])編程

這是一個很是重要的監控指標,若是作緩存必定要健康這個指標來看緩存是否工做良好。緩存

移除策略--不一樣的移除策略實際上看的是不一樣的指標

即若是緩存滿了,從緩存中移除數據的策略;常見的有LFU、LRU、FIFO:服務器

FIFO(First In First Out):先進先出算法,即先放入緩存的先被移除;app

LRU(Least Recently Used):最久未使用算法,使用時間距離如今最久的那個被移除;less

LFU(Least Frequently Used):最近最少使用算法,必定時間段內使用次數(頻率)最少的那個被移除。

TTL(Time To Live )

存活期,即從緩存中建立時間點開始直到它到期的一個時間段(無論在這個時間段內有沒有訪問都將過時)

TTI(Time To Idle)

空閒期,即一個數據多久沒被訪問將從緩存中移除的時間。

★實際設計緩存時以上重要指標都應該考慮進去,固然根據實際需求可能有的指標並不會採用進設計去

★MySQL數據庫中的索引也是以空間換取時間,緩存也是以空間換取時間。因此有時爲了提升效率(減小響應時間),就要適當的犧牲空間

Java中緩存的應用

在Java中,咱們通常對調用方法進行緩存控制,好比我調用"findUserById(Long id)",那麼我應該在調用這個方法以前先從緩存中查找有沒有,若是沒有再掉該方法如從數據庫加載用戶,而後添加到緩存中,下次調用時將會從緩存中獲取到數據。

基於註解的對Cache的支持

Spring 3.1起,提供了基於註解的對Cache的支持。使用Spring Cache的好處:

基於註解,代碼清爽簡潔;

基於註解也能夠實現複雜的邏輯;

能夠對緩存進行回滾;

Spring Cache不是具體的緩存技術,而是基於具體的緩存產品(如Guava、EhCache、Redis等)的共性進行了一層封裝,可是能夠經過簡單的配置切換底層使用的緩存。

★Spring Cache並不是緩存產品,而是基於各類緩存產品(如Guava、EhCache、Redis等)的共性進行了一層封裝,結合SpringBoot的開箱即用的特性用起來會很是方便,由於Spring Cache經過註解隔離了具體的緩存產品,讓用戶更加專一於應用層面。具體的底層緩存技術究竟採用了Guava、EhCache仍是Redis,只須要簡單的配置就能夠實現方便的切換。

Spring Cache總

Cache API及默認提供的實現

Cache註解

實現複雜的Cache邏輯

Cache API及默認提供的實現--Cache接口和CacheManager接口

Cache接口

源碼

package org.springframework.cache;

 

public interface Cache {

String getName();  //緩存的名字

Object getNativeCache(); //獲得底層使用的緩存,如Ehcache

ValueWrapper get(Object key); //根據key獲得一個ValueWrapper,而後調用其get方法獲取值

<T> T get(Object key, Class<T> type);//根據key,和value的類型直接獲取value

void put(Object key, Object value);//往緩存放數據

void evict(Object key);//從緩存中移除key對應的緩存

void clear(); //清空緩存

 

interface ValueWrapper { //緩存值的Wrapper

Object get(); //獲得真實的value

        }

}

分析--提供了緩存的基本操做:讀取、寫入、移除(清空)操做

提供的默認實現

ConcurrentMapCache:使用java.util.concurrent.ConcurrentHashMap實現的Cache。

GuavaCache:對Guava com.google.common.cache.Cache進行的Wrapper,須要Google Guava 12.0或更高版本,@since spring 4。

EhCacheCache:使用Ehcache實現。

JCacheCache:對javax.cache.Cache進行的wrapper,@since spring 3.2;spring4將此類更新到JCache 0.11版本。

 

CacheManager接口

源碼

package org.springframework.cache;  

import java.util.Collection;  

public interface CacheManager {  

    Cache getCache(String name); //根據Cache名字獲取Cache   

    Collection<String> getCacheNames(); //獲得全部Cache的名字  

}  

分析--在應用中並非使用一個Cache,而是多個,所以Spring還提供了CacheManager抽象,用於緩存的管理。接口方法主要包括根據緩存名獲取緩存和獲取全部緩存的名字集合

提供的默認實現

ConcurrentMapCacheManager/ConcurrentMapCacheFactoryBean:管理ConcurrentMapCache;

GuavaCacheManager;

EhCacheCacheManager/EhCacheManagerFactoryBean;

JCacheCacheManager/JCacheManagerFactoryBean;

 

CompositeCacheManager

CompositeCacheManager用於組合CacheManager,便可以從多個CacheManager中輪詢獲得相應的Cache。

 

調用cacheManager.getCache(cacheName) 時,會先從第一個cacheManager中查找有沒有cacheName的cache,若是沒有接着查找第二個,若是最後找不到,由於fallbackToNoOpCache=true,那麼將返回一個NOP的Cache不然返回null。

NoOpCacheNoOpCacheManager的一個內部類

 

Spring不進行Cache的緩存策略的維護,這些都是由底層Cache本身實現,Spring只是提供了一個Wrapper,提供一套對外一致的API。

★使用實例

代碼方式--直接使用Spring提供的API

@Test  

public void test() throws IOException {  

    //建立底層Cache  

    net.sf.ehcache.CacheManager ehcacheManager  

            = new net.sf.ehcache.CacheManager(new ClassPathResource("ehcache.xml").getInputStream());  

  

    //建立Spring的CacheManager  

    EhCacheCacheManager cacheCacheManager = new EhCacheCacheManager();  

    //設置底層的CacheManager  

    cacheCacheManager.setCacheManager(ehcacheManager);  

  

    Long id = 1L;  

    User user = new User(id, "zhang", "zhang@gmail.com");  

  

    //根據緩存名字獲取Cache  

    Cache cache = cacheCacheManager.getCache("user");  

    //往緩存寫數據  

    cache.put(id, user);  

    //從緩存讀數據  

    Assert.assertNotNull(cache.get(id, User.class));  

}  

代碼方式說明

EhCacheCacheManager用來根據緩存名稱(cacheName)建立緩存Cache,而後經過緩存Cache來進行緩存的操做。

XML配置--Spring容器獲取對象

 

spring提供EhCacheManagerFactoryBean來簡化ehcache cacheManager的建立,這樣注入configLocation,會自動根據路徑從classpath下找,比編碼方式簡單多了,而後就能夠從spring容器獲取cacheManager進行操做了。此處的transactionAware表示是否事務環繞的,若是true,則若是事務回滾,緩存也回滾,默認false。

註解方式代碼

@Bean  

public CacheManager cacheManager() {  

  

    try {  

        net.sf.ehcache.CacheManager ehcacheCacheManager  

                = new net.sf.ehcache.CacheManager(new ClassPathResource("ehcache.xml").getInputStream());  

  

        EhCacheCacheManager cacheCacheManager = new EhCacheCacheManager(ehcacheCacheManager);  

        return cacheCacheManager;  

    } catch (IOException e) {  

        throw new RuntimeException(e);  

    }  

}  

註解方式說明--主要就是註冊下CacheManager

★三種使用方式比較:代碼方式、XML配置方式、註解方式

Spring是一種產品,提供這麼多種方式是爲了知足不一樣的編程習慣的程序員的編程習慣。

代碼方式最爲靈活,也須要對源碼有必定的瞭解,作深刻的、個性化需求的開發最好仍是採用對源碼封裝的方式,也就是直接操做代碼的方式。

XML配置和註解方式較爲簡潔。可是作深刻的、個性化的開發,靈活性有些受制約。

★先生成CacheManager,生成後CacheManager根據CacheName建立緩存Cache,而後各類操做都是經過Cache進行的

能夠寫本身的Cache實現;即便不用Spring Cache註解,也儘可能使用Spring Cache API進行Cache的操做,由於替換底層Cache是很是方便的

Cache註解

啓用Cache註解

XML風格

 

還能夠指定一個 key-generator,即默認的key生成策略。

註解風格代碼

@Configuration

@ComponentScan(basePackages = "com.sishuok.spring.service")

@EnableCaching(proxyTargetClass = true)

public class AppConfig implements CachingConfigurer {

    @Bean

    @Override

    public CacheManager cacheManager() {

 

        try {

            net.sf.ehcache.CacheManager ehcacheCacheManager

                    = new net.sf.ehcache.CacheManager(new ClassPathResource("ehcache.xml").getInputStream());

 

            EhCacheCacheManager cacheCacheManager = new EhCacheCacheManager(ehcacheCacheManager);

            return cacheCacheManager;

        } catch (IOException e) {

            throw new RuntimeException(e);

        }

    }

 

    @Bean

    @Override

    public KeyGenerator keyGenerator() {

        return new SimpleKeyGenerator();

    }

}

註解風格說明

使用@EnableCaching啓用Cache註解支持。注入須要的cacheManager。也能夠對keyGenerator進行配置,不配置則採用默認的。從spring4開始默認的keyGenerator是SimpleKeyGenerator。

@CachePut

@CachePut註解說明

public @interface CachePut {  

    String[] value();              //緩存的名字,能夠把數據寫到多個緩存  

    String key() default "";       //緩存key,若是不指定將使用默認的KeyGenerator生成,後邊介紹  

    String condition() default ""; //知足緩存條件的數據纔會放入緩存,condition在調用方法以前和以後都會判斷  

    String unless() default "";    //用於否決緩存更新的,不像condition,該表達只在方法執行以後判斷,此時能夠拿到返回值result進行判斷了  

}  

@CachePut註解使用

應用到寫數據的方法上,如新增/修改方法,調用方法時會自動把相應的數據放入緩存。好比下面調用該方法時,會把user.id做爲key,返回值做爲value放入緩存

 

@CacheEvict

@CacheEvict註解說明

public @interface CacheEvict {  

    String[] value();                        //請參考@CachePut  

    String key() default "";                 //請參考@CachePut  

    String condition() default "";           //請參考@CachePut  

    boolean allEntries() default false;      //是否移除全部數據  

    boolean beforeInvocation() default false;//是調用方法以前移除/仍是調用以後移除  

}

@CacheEvict註解使用

應用到移除數據的方法上,如刪除方法,調用方法時會從緩存中移除相應的數據

 

@Cacheable

@Cacheable註解說明

public @interface Cacheable {  

    String[] value();             //請參考@CachePut  

    String key() default "";      //請參考@CachePut  

    String condition() default "";//請參考@CachePut  

    String unless() default "";   //請參考@CachePut    

}

@Cacheable註解使用

應用到讀取數據的方法上,便可緩存的方法,如查找方法:先從緩存中讀取,若是沒有再調用方法獲取數據,而後把數據添加到緩存中

 

@Cacheable、@CachePut、@CacheEvict註解的攔截解析邏輯

實際上就是AOP,攔截處理,在 CacheAspectSupport 的 execute 方法中進行的處理。

 

處理邏輯以下(沒細看,不知是否如此,並且不一樣版本的Spring可能有所不一樣,真想明白這個邏輯,請先本身仔細想一遍,手動實現一下,再比對Spring源碼分析本身的處理邏輯跟Spring中的是否一致,誰的處理更好,爲何?本身先想一想怎麼處理這個邏輯才合理!!!

 

若是有@CachePut操做,即便有@Cacheable也不會從緩存中讀取;問題很明顯,若是要混合多個註解使用,不能組合使用@CachePut和@Cacheable;官方說應該避免這樣使用(解釋是若是帶條件的註解相互排除的場景);不過我的感受仍是不要考慮這個好,讓用戶來決定如何使用,不然一會介紹的場景不能知足。

註解實現複雜的緩存邏輯,須要經過Spring Expression Language (SpEL)

Spring Cache提供了一些供使用的SpEL上下文數據,下表直接摘自Spring官方文檔

名字

位置

描述

示例

methodName

root對象

當前被調用的方法名

#root.methodName

method

root對象

當前被調用的方法

#root.method.name

target

root對象

當前被調用的目標對象

#root.target

targetClass

root對象

當前被調用的目標對象類

#root.targetClass

args

root對象

當前被調用的方法的參數列表

#root.args[0]

caches

root對象

當前方法調用使用的緩存列表(如@Cacheable(value={"cache1", "cache2"})),則有兩個cache

#root.caches[0].name

argument name

執行上下文

當前被調用的方法的參數,如findById(Long id),咱們能夠經過#id拿到參數

#user.id

result

執行上下文

方法執行後的返回值(僅當方法執行以後的判斷有效,如‘unless’,'cache evict'的beforeInvocation=false)

#result

經過這些數據能夠實現比較複雜的緩存邏輯。

Key生成器

若是在Cache註解上沒有指定key的話@CachePut(value = "user"),會使用KeyGenerator進行生成一個key。默認提供了SimpleKeyGenerator生成器。

KeyGenerator接口

public interface KeyGenerator {

 

Object generate(Object target, Method method, Object... params);

 

}

SimpleKeyGenerator生成key的源碼--若是隻有一個參數,就使用參數做爲key,不然使用SimpleKey做爲key

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生成器,而後經過xml風格的<cache:annotation-driven key-generator=""/>或註解方式指定keyGenerator

條件緩存

根據運行流程,以下@Cacheable將在執行方法以前( #result還拿不到返回值)判斷condition,若是返回true,則查緩存

 

根據運行流程,以下@CachePut將在執行完方法後(#result就能拿到返回值了)判斷condition,若是返回true,則放入緩存

 

根據運行流程,以下@CachePut將在執行完方法後(#result就能拿到返回值了)判斷unless,若是返回false,則放入緩存;(即跟condition相反)

 

根據運行流程,以下@CacheEvict, beforeInvocation=false表示在方法執行以後調用(#result能拿到返回值了);且判斷condition,若是返回true,則移除緩存

 

@Caching--組合多個Cache註解使用

@Caching的用法

有時候可能組合多個Cache註解使用;好比用戶新增成功後,要添加id-->user;username--->user;email--->user的緩存;此時就須要@Caching組合多個註解標籤了。

如用戶新增成功後,添加id-->user;username--->user;email--->user到緩存

@Caching(  

        put = {  

                @CachePut(value = "user", key = "#user.id"),  

                @CachePut(value = "user", key = "#user.username"),  

                @CachePut(value = "user", key = "#user.email")  

        }  

)  

public User save(User user) {  }

 

這樣能夠在根據id、根據username、根據email查找user的方法上使用@Cacheable,就可使用緩存了。

@Caching的定義

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@Documented

public @interface Caching {

Cacheable[] cacheable() default {};

CachePut[] put() default {};

CacheEvict[] evict() default {};

}

★自定義緩存註解--美團應用中就有這個的實際應用,定義了針對美團項目的註解

上面的@Caching組合,會讓方法上的註解顯得整個代碼比較亂,此時可使用自定義註解把這些註解組合到一個註解中。

這樣在方法上使用以下代碼便可,整個代碼顯得比較乾淨

@UserSaveCache  

public User save(User user)

【自定義緩存註解】定義代碼

@Caching(  

        put = {  

                @CachePut(value = "user", key = "#user.id"),  

                @CachePut(value = "user", key = "#user.username"),  

                @CachePut(value = "user", key = "#user.email")  

        }  

)  

@Target({ElementType.METHOD, ElementType.TYPE})  

@Retention(RetentionPolicy.RUNTIME)  

@Inherited  

public @interface UserSaveCache {  }  

自定義緩存註解示例1--新增和修改

定義

@Caching(  

        put = {  

                @CachePut(value = "user", key = "#user.id"),  

                @CachePut(value = "user", key = "#user.username"),  

                @CachePut(value = "user", key = "#user.email")  

        }  

)  

@Target({ElementType.METHOD, ElementType.TYPE})  

@Retention(RetentionPolicy.RUNTIME)  

@Inherited  

public @interface UserSaveCache {  }

新增時使用

@UserSaveCache

public User saveUser(User user){} 

修改時使用

@UserSaveCache

public User updateUser(User user){} 

自定義緩存註解示例2--刪除

定義

@Caching(  

        evict = {  

                @CacheEvict(value = "user", key = "#user.id"),  

                @CacheEvict(value = "user", key = "#user.username"),  

                @CacheEvict(value = "user", key = "#user.email")  

        }  

)  

@Target({ElementType.METHOD, ElementType.TYPE})  

@Retention(RetentionPolicy.RUNTIME)  

@Inherited  

public @interface UserDelCache {  }

刪除時使用

@UserDelCache 

public User deleteUser(User user){}

@Caching查詢示例

示例1

@Caching(  

        cacheable = {  

                @Cacheable(value = "user", key = "#id")  

        }  

)  

public User findById(final Long id) 

示例2

@Caching(  

        cacheable = {  

                @Cacheable(value = "user", key = "#username")  

        }  

)  

public User findByUsername(final String username) 

示例3

@Caching(  

        cacheable = {  

                @Cacheable(value = "user", key = "#email")  

        }  

)  

public User findByEmail(final String email)

相關文章
相關標籤/搜索