CPU須要數據時先從L1/L2中讀取,若是沒有到內存中找,若是尚未會到磁盤上找。java
還有如用過Maven的朋友都應該知道,咱們找依賴的時候,先從本機倉庫找,再從本地服務器倉庫找,最後到遠程倉庫服務器找。程序員
還有如京東的物流爲何那麼快?他們在各個地都有分倉庫,若是該倉庫有貨物那麼送貨的速度是很是快的。算法
即【從緩存中讀取數據的次數】與【總讀取次數】的比率,命中率越高越好:spring
命中率 = 從緩存中讀取次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])數據庫
Miss率 = 沒有從緩存中讀取的次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])編程
這是一個很是重要的監控指標,若是作緩存必定要健康這個指標來看緩存是否工做良好。緩存
即若是緩存滿了,從緩存中移除數據的策略;常見的有LFU、LRU、FIFO:服務器
FIFO(First In First Out):先進先出算法,即先放入緩存的先被移除;app
LRU(Least Recently Used):最久未使用算法,使用時間距離如今最久的那個被移除;less
LFU(Least Frequently Used):最近最少使用算法,必定時間段內使用次數(頻率)最少的那個被移除。
存活期,即從緩存中建立時間點開始直到它到期的一個時間段(無論在這個時間段內有沒有訪問都將過時)
空閒期,即一個數據多久沒被訪問將從緩存中移除的時間。
在Java中,咱們通常對調用方法進行緩存控制,好比我調用"findUserById(Long id)",那麼我應該在調用這個方法以前先從緩存中查找有沒有,若是沒有再掉該方法如從數據庫加載用戶,而後添加到緩存中,下次調用時將會從緩存中獲取到數據。
Spring 3.1起,提供了基於註解的對Cache的支持。使用Spring Cache的好處:
基於註解,代碼清爽簡潔;
基於註解也能夠實現複雜的邏輯;
能夠對緩存進行回滾;
Spring Cache不是具體的緩存技術,而是基於具體的緩存產品(如Guava、EhCache、Redis等)的共性進行了一層封裝,可是能夠經過簡單的配置切換底層使用的緩存。
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版本。
package org.springframework.cache;
import java.util.Collection;
public interface CacheManager {
Cache getCache(String name); //根據Cache名字獲取Cache
Collection<String> getCacheNames(); //獲得全部Cache的名字
}
ConcurrentMapCacheManager/ConcurrentMapCacheFactoryBean:管理ConcurrentMapCache;
GuavaCacheManager;
EhCacheCacheManager/EhCacheManagerFactoryBean;
JCacheCacheManager/JCacheManagerFactoryBean;
CompositeCacheManager用於組合CacheManager,便可以從多個CacheManager中輪詢獲得相應的Cache。
調用cacheManager.getCache(cacheName) 時,會先從第一個cacheManager中查找有沒有cacheName的cache,若是沒有接着查找第二個,若是最後找不到,由於fallbackToNoOpCache=true,那麼將返回一個NOP的Cache不然返回null。
@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來進行緩存的操做。
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);
}
}
Spring是一種產品,提供這麼多種方式是爲了知足不一樣的編程習慣的程序員的編程習慣。
代碼方式最爲靈活,也須要對源碼有必定的瞭解,作深刻的、個性化需求的開發最好仍是採用對源碼封裝的方式,也就是直接操做代碼的方式。
XML配置和註解方式較爲簡潔。可是作深刻的、個性化的開發,靈活性有些受制約。
還能夠指定一個 key-generator,即默認的key生成策略。
@Configuration
@ComponentScan(basePackages = "com.sishuok.spring.service")
@EnableCaching(proxyTargetClass = true)
public class AppConfig implements CachingConfigurer {
@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);
}
}
@Bean
public KeyGenerator keyGenerator() {
return new SimpleKeyGenerator();
}
}
使用@EnableCaching啓用Cache註解支持。注入須要的cacheManager。也能夠對keyGenerator進行配置,不配置則採用默認的。從spring4開始默認的keyGenerator是SimpleKeyGenerator。
public @interface CachePut {
String[] value(); //緩存的名字,能夠把數據寫到多個緩存
String key() default ""; //緩存key,若是不指定將使用默認的KeyGenerator生成,後邊介紹
String condition() default ""; //知足緩存條件的數據纔會放入緩存,condition在調用方法以前和以後都會判斷
String unless() default ""; //用於否決緩存更新的,不像condition,該表達只在方法執行以後判斷,此時能夠拿到返回值result進行判斷了
}
應用到寫數據的方法上,如新增/修改方法,調用方法時會自動把相應的數據放入緩存。好比下面調用該方法時,會把user.id做爲key,返回值做爲value放入緩存
public @interface CacheEvict {
String[] value(); //請參考@CachePut
String key() default ""; //請參考@CachePut
String condition() default ""; //請參考@CachePut
boolean allEntries() default false; //是否移除全部數據
boolean beforeInvocation() default false;//是調用方法以前移除/仍是調用以後移除
}
應用到移除數據的方法上,如刪除方法,調用方法時會從緩存中移除相應的數據
public @interface Cacheable {
String[] value(); //請參考@CachePut
String key() default ""; //請參考@CachePut
String condition() default "";//請參考@CachePut
String unless() default ""; //請參考@CachePut
}
應用到讀取數據的方法上,便可緩存的方法,如查找方法:先從緩存中讀取,若是沒有再調用方法獲取數據,而後把數據添加到緩存中
實際上就是AOP,攔截處理,在 CacheAspectSupport 的 execute 方法中進行的處理。
處理邏輯以下(沒細看,不知是否如此,並且不一樣版本的Spring可能有所不一樣,真想明白這個邏輯,請先本身仔細想一遍,手動實現一下,再比對Spring源碼分析本身的處理邏輯跟Spring中的是否一致,誰的處理更好,爲何?本身先想一想怎麼處理這個邏輯才合理!!!)
若是有@CachePut操做,即便有@Cacheable也不會從緩存中讀取;問題很明顯,若是要混合多個註解使用,不能組合使用@CachePut和@Cacheable;官方說應該避免這樣使用(解釋是若是帶條件的註解相互排除的場景);不過我的感受仍是不要考慮這個好,讓用戶來決定如何使用,不然一會介紹的場景不能知足。
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 |
經過這些數據能夠實現比較複雜的緩存邏輯。
若是在Cache註解上沒有指定key的話@CachePut(value = "user"),會使用KeyGenerator進行生成一個key。默認提供了SimpleKeyGenerator生成器。
public interface KeyGenerator {
Object generate(Object target, Method method, Object... params);
}
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);
}
根據運行流程,以下@Cacheable將在執行方法以前( #result還拿不到返回值)判斷condition,若是返回true,則查緩存
根據運行流程,以下@CachePut將在執行完方法後(#result就能拿到返回值了)判斷condition,若是返回true,則放入緩存
根據運行流程,以下@CachePut將在執行完方法後(#result就能拿到返回值了)判斷unless,若是返回false,則放入緩存;(即跟condition相反)
根據運行流程,以下@CacheEvict, beforeInvocation=false表示在方法執行以後調用(#result能拿到返回值了);且判斷condition,若是返回true,則移除緩存
有時候可能組合多個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,就可使用緩存了。
@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 { }
@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){}
@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(
cacheable = {
@Cacheable(value = "user", key = "#id")
}
)
public User findById(final Long id)
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#username")
}
)
public User findByUsername(final String username)
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#email")
}
)
public User findByEmail(final String email)