Spring Cache使用

jopen 9年前發布 | 142K 次閱讀 緩存組件 Spring Cache

記錄下自己項目在用的Spring Cache的使用方式。
Spring的抽象已經做得夠好了,適合于大多數場景,非常復雜的就需要自己AOP實現了。
Spring官網的文檔挺不錯的,但是對Cache這塊的介紹不是很詳細,結合網上大牛的博文,匯總下文。

緩存概念

緩存簡介

緩存,我的理解是:讓數據更接近于使用者;工作機制是:先從緩存中讀取數據,如果沒有再從慢速設備上讀取實際數據(數據也會存入緩存);緩存什么:那些經常讀取且不經常修改的數據/那些昂貴(CPU/IO)的且對于相同的請求有相同的計算結果的數據。如CPU—L1/L2—內存—磁盤就是一個典型的例子,CPU需要數據時先從 L1/L2中讀取,如果沒有到內存中找,如果還沒有會到磁盤上找。還有如用過Maven的朋友都應該知道,我們找依賴的時候,先從本機倉庫找,再從本地服務器倉庫找,最后到遠程倉庫服務器找;還有如京東的物流為什么那么快?他們在各個地都有分倉庫,如果該倉庫有貨物那么送貨的速度是非常快的。

緩存命中率

即從緩存中讀取數據的次數 與 總讀取次數的比率,命中率越高越好:
命中率 = 從緩存中讀取次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])
Miss率 = 沒有從緩存中讀取的次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])

這是一個非常重要的監控指標,如果做緩存一定要健康這個指標來看緩存是否工作良好;

緩存策略

Eviction policy

移除策略,即如果緩存滿了,從緩存中移除數據的策略;常見的有LFU、LRU、FIFO:

  • FIFO(First In First Out):先進先出算法,即先放入緩存的先被移除;
  • LRU(Least Recently Used):最久未使用算法,使用時間距離現在最久的那個被移除;
  • LFU(Least Frequently Used):最近最少使用算法,一定時間段內使用次數(頻率)最少的那個被移除;
  • </ul>

    TTL(Time To Live )

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

    TTI(Time To Idle)

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

    到此,基本了解了緩存的知識,在Java中,我們一般對調用方法進行緩存控制,比如我調用”findUserById(Long id)”,那么我應該在調用這個方法之前先從緩存中查找有沒有,如果沒有再掉該方法如從數據庫加載用戶,然后添加到緩存中,下次調用時將會從緩存中獲取到數據。

    自Spring 3.1起,提供了類似于@Transactional注解事務的注解Cache支持,且提供了Cache抽象;在此之前一般通過AOP實現;使用Spring Cache的好處:

    • 提供基本的Cache抽象,方便切換各種底層Cache;
    • 通過注解Cache可以實現類似于事務一樣,緩存邏輯透明的應用到我們的業務代碼上,且只需要更少的代碼就可以完成;
    • 提供事務回滾時也自動回滾緩存;
    • 支持比較復雜的緩存邏輯;
    • </ul>

      對于Spring Cache抽象,主要從以下幾個方面學習:

      • Cache API及默認提供的實現
      • Cache注解
      • 實現復雜的Cache邏輯
      • </ul> 緩存簡介開濤的博客 </blockquote>

        Spring Cache簡介

        Spring3.1開始引入了激動人心的基于注釋(annotation)的緩存(cache)技術,它本質上不是一個具體的緩存實現方案(例如EHCache 或者 OSCache),而是一個對緩存使用的抽象,通過在既有代碼中添加少量它定義的各種 annotation,即能夠達到緩存方法的返回對象的效果。

        Spring的緩存技術還具備相當的靈活性,不僅能夠使用 SpEL(Spring Expression Language)來定義緩存的key和各種condition,還提供開箱即用的緩存臨時存儲方案,也支持和主流的專業緩存例如EHCache、 memcached集成。

        其特點總結如下:

        • 通過少量的配置 annotation 注釋即可使得既有代碼支持緩存
        • 支持開箱即用 Out-Of-The-Box,即不用安裝和部署額外第三方組件即可使用緩存
        • 支持 Spring Express Language,能使用對象的任何屬性或者方法來定義緩存的 key 和 condition
        • 支持 AspectJ,并通過其實現任何方法的緩存支持
        • 支持自定義 key 和自定義緩存管理者,具有相當的靈活性和擴展性
        • </ul> Spring Cache 介紹Spring Cache 介紹 - Rollen Holt - 博客園 </blockquote>

          API介紹

          Cache接口

          理解這個接口有助于我們實現自己的緩存管理器

          package org.springframework.cache;

          public interface Cache {

          /**
           * 緩存的名字
           */
          String getName();
          
          /**
           * 得到底層使用的緩存
           */
          Object getNativeCache();
          
          /**
           * 根據key得到一個ValueWrapper,然后調用其get方法獲取值 
           */
          ValueWrapper get(Object key);
          
          /**
           * 根據key,和value的類型直接獲取value  
           */
          <T> T get(Object key, Class<T> type);
          
          /**
           * 存數據
           */
          void put(Object key, Object value);
          
          /**
           * 如果值不存在,則添加,用來替代如下代碼
           * Object existingValue = cache.get(key);
           * if (existingValue == null) {
           *     cache.put(key, value);
           *     return null;
           * } else {
           *     return existingValue;
           * }
           */
          ValueWrapper putIfAbsent(Object key, Object value);
          
          /**
           * 根據key刪數據
           */
          void evict(Object key);
          
          /**
           * 清空數據
           */
          void clear();
          
          /**
           * 緩存值的Wrapper  
           */
          interface ValueWrapper {
              /**
               * 得到value
               */
              Object get();
          }
          

          }</pre>

          默認實現

          默認已經實現了幾個常用的cache
          位于spring-context-x.RELEASE.jar和spring-context-support-x.RELEASE.jar的cache目錄下

          • ConcurrentMapCache:基于java.util.concurrent.ConcurrentHashMap
          • GuavaCache:基于Google的Guava工具
          • EhCacheCache:基于Ehcache
          • JCacheCache:基于javax.cache.Cache(不常用)
          • </ul>

            CacheManager

            用來管理多個cache

            package org.springframework.cache;

            import java.util.Collection;

            public interface CacheManager {

            /**
             * 根據cache名獲取cache
             */
            Cache getCache(String name);
            
            /**
             * 得到所有cache的名字
             */
            Collection<String> getCacheNames();
            
            

            }</pre>

            默認實現

            對應Cache接口的默認實現

            • ConcurrentMapCacheManager / ConcurrentMapCacheFactoryBean
            • GuavaCacheManager
            • EhCacheCacheManager / EhCacheManagerFactoryBean
            • JCacheCacheManager / JCacheManagerFactoryBean
            • </ul>

              CompositeCacheManager

              用于組合CacheManager,可以從多個CacheManager中輪詢得到相應的Cache

              <bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
                  <property name="cacheManagers">
                      <list>
                          <ref bean="concurrentMapCacheManager"/>
                          <ref bean="guavaCacheManager"/>
                      </list>
                  </property>
                  <!-- 都找不到時,不返回null,而是返回NOP的Cache -->
                  <property name="fallbackToNoOpCache" value="true"/>
              </bean>

              事務

              除GuavaCacheManager外,其他Cache都支持Spring事務,如果注解方法出現事務回滾,對應緩存操作也會回滾

              緩存策略

              都是Cache自行維護,Spring只提供對外抽象API

              Cache注解

              每個注解都有多個參數,這里不一一列出,建議進入源碼查看注釋

              啟用注解

              </tr> </tbody> </table> <h3>@CachePut </h3>

              寫數據

              @CachePut(value = "addPotentialNoticeCache", key = "targetClass + '.' + #userCode")
              public List<PublicAutoAddPotentialJob.AutoAddPotentialNotice> put(int userCode, List<PublicAutoAddPotentialJob.AutoAddPotentialNotice> noticeList) {
                  LOGGER.info("緩存({})的公客自動添加潛在客的通知", userCode);
                  return noticeList;
              }

              <h3>@CacheEvict </h3>

              失效數據

              @CacheEvict(value = "addPotentialNoticeCache", key = "targetClass + '.' + #userCode")
              public void remove(int userCode) {
                  LOGGER.info("清除({})的公客自動添加潛在客的通知", userCode);
              }

              <h3>@Cacheable </h3>

              這個用的比較多
              用在查詢方法上,先從緩存中讀取,如果沒有再調用方法獲取數據,然后把數據添加到緩存中

              @Cacheable(value = "kyAreaCache", key="targetClass + '.' + methodName + '.' + #areaId")
              public KyArea findById(String areaId) {
                  // 業務代碼省略
              }

              運行流程

              1. 首先執行@CacheEvict(如果beforeInvocation=true且condition 通過),如果allEntries=true,則清空所有
              2. 接著收集@Cacheable(如果condition 通過,且key對應的數據不在緩存),放入cachePutRequests(也就是說如果cachePutRequests為空,則數據在緩存中)
              3. 如果cachePutRequests為空且沒有@CachePut操作,那么將查找@Cacheable的緩存,否則result=緩存數據(也就是說只要當沒有cache put請求時才會查找緩存)
              4. 如果沒有找到緩存,那么調用實際的API,把結果放入result
              5. 如果有@CachePut操作(如果condition 通過),那么放入cachePutRequests
              6. 執行cachePutRequests,將數據寫入緩存(unless為空或者unless解析結果為false);
              7. 執行@CacheEvict(如果beforeInvocation=false 且 condition 通過),如果allEntries=true,則清空所有
              8. </ol>

                SpEL上下文數據

                在使用時,#root.methodName 等同于 methodName

              <cache:annotation-driven cache-manager="cacheManager"/> 

              </tr> </tbody>

              </tr>

              </tr>

              </tr>

              </tr>

              </tr>

              </tr>

              </tr>

              </tr> </tbody> </table>

              條件緩存

              主要是在注解內用condition和unless的表達式分別對參數和返回結果進行篩選后緩存

              <h3>@Caching </h3>

              多個緩存注解組合使用

              @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) {

              }</pre>

              自定義緩存注解

              把一些特殊場景的注解包裝到一個獨立的注解中,比如@Caching組合使用的注解

              @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 save(User user) {

              }</pre>

              示例

              基于ConcurrentMapCache

              自定義CacheManager

              我需要使用有容量限制和緩存失效時間策略的Cache,默認的ConcurrentMapCacheManager沒法滿足
              通過實現CacheManager接口定制出自己的CacheManager。
              還是拷貝ConcurrentMapCacheManager,使用Guava的Cache做底層容器,因為Guava的Cache容器可以設置緩存策略

              新增了exp、maximumSize兩個策略變量
              修改底層Cache容器的創建

              下面只列出自定義的代碼,其他的都是Spring的ConcurrentMapCacheManager的代碼

              import com.google.common.cache.CacheBuilder;
              import org.springframework.cache.Cache;
              import org.springframework.cache.CacheManager;
              import org.springframework.cache.concurrent.ConcurrentMapCache;

              import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit;

              /**

              • 功能說明:自定義的ConcurrentMapCacheManager,新增超時時間和最大存儲限制
              • 作者:liuxing(2015-04-13 18:44) */ public class ConcurrentMapCacheManager implements CacheManager {

                /**

                • 過期時間,秒(自定義) */ private long exp = 1800; /**
                • 最大存儲數量 (自定義) */ private long maximumSize = 1000;

                  public void setExp(long exp) { this.exp = exp; }

                  public void setMaximumSize(long maximumSize) { this.maximumSize = maximumSize; }

                  /**

                • 創建一個緩存容器,這個方法改寫為使用Guava的Cache
                • @param name
                • @return */ protected Cache createConcurrentMapCache(String name) { return new ConcurrentMapCache(name, CacheBuilder.newBuilder().expireAfterWrite(this.exp, TimeUnit.SECONDS)
                                                                            .maximumSize(this.maximumSize)
                                                                            .build()
                                                                            .asMap(), isAllowNullValues());
                  
                  } }</pre>

                  初始化

                  xml風格

                  <!-- 啟用緩存注解功能,這個是必須的,否則注解不會生效,指定一個默認的Manager,否則需要在注解使用時指定Manager -->
                  <cache:annotation-driven cache-manager="memoryCacheManager"/>

              <!-- 本地內存緩存 --> <bean id="memoryCacheManager" class="com.dooioo.ky.cache.ConcurrentMapCacheManager" p:maximumSize="2000" p:exp="1800"> <property name="cacheNames"> <list> <value>kyMemoryCache</value> </list> </property> </bean></pre>

              使用

              @Cacheable(value = "kyMemoryCache", key="targetClass + '.' + methodName")
              public Map<String, String> queryMobiles(){
                  // 業務代碼省略
              }


              使用Memcached

              一般常用的緩存當屬memcached了,這個就需要自己實現CacheManager和Cache
              注意我實現的Cache里面有做一些定制化操作,比如對key的處理

              創建MemcachedCache

              import com.dooioo.common.jstl.DyFunctions;
              import com.dooioo.commons.Strings;
              import com.google.common.base.Joiner;
              import net.rubyeye.xmemcached.MemcachedClient;
              import net.rubyeye.xmemcached.exception.MemcachedException;
              import org.slf4j.Logger;
              import org.slf4j.LoggerFactory;
              import org.springframework.cache.Cache;
              import org.springframework.cache.support.SimpleValueWrapper;

              import java.util.concurrent.TimeoutException;

              /**

              • 功能說明:自定義spring的cache的實現,參考cache包實現
              • 作者:liuxing(2015-04-12 13:57) */ public class MemcachedCache implements Cache {

                private static final Logger LOGGER = LoggerFactory.getLogger(MemcachedCache.class);

                /**

                • 緩存的別名 */ private String name; /**
                • memcached客戶端 */ private MemcachedClient client; /**
                • 緩存過期時間,默認是1小時
                • 自定義的屬性 */ private int exp = 3600; /**
                • 是否對key進行base64加密 */ private boolean base64Key = false; /**
                • 前綴名 */ private String prefix;

                  @Override public String getName() { return name; }

                  @Override public Object getNativeCache() { return this.client; }

                  @Override public ValueWrapper get(Object key) { Object object = null; try {

                   object = this.client.get(handleKey(objectToString(key)));
                  

                  } catch (TimeoutException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (InterruptedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (MemcachedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  }

                  return (object != null ? new SimpleValueWrapper(object) : null); }

                  @Override public <T> T get(Object key, Class<T> type) { try {

                   Object object = this.client.get(handleKey(objectToString(key)));
                   return (T) object;
                  

                  } catch (TimeoutException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (InterruptedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (MemcachedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  }

                  return null; }

                  @Override public void put(Object key, Object value) { if (value == null) { // this.evict(key);

                   return;
                  

                  }

                  try {

                   this.client.set(handleKey(objectToString(key)), exp, value);
                  

                  } catch (TimeoutException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (InterruptedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (MemcachedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } }

                  @Override public ValueWrapper putIfAbsent(Object key, Object value) { this.put(key, value); return this.get(key); }

                  @Override public void evict(Object key) { try {

                   this.client.delete(handleKey(objectToString(key)));
                  

                  } catch (TimeoutException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (InterruptedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (MemcachedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } }

                  @Override public void clear() { try {

                   this.client.flushAll();
                  

                  } catch (TimeoutException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (InterruptedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } catch (MemcachedException e) {

                   LOGGER.error(e.getMessage(), e);
                  

                  } }

                  public void setName(String name) { this.name = name; }

                  public MemcachedClient getClient() { return client; }

                  public void setClient(MemcachedClient client) { this.client = client; }

                  public void setExp(int exp) { this.exp = exp; }

                  public void setBase64Key(boolean base64Key) { this.base64Key = base64Key; }

                  public void setPrefix(String prefix) { this.prefix = prefix; }

                  /**

                • 處理key
                • @param key
                • @return */ private String handleKey(final String key) { if (base64Key) {

                   return Joiner.on(EMPTY_SEPARATOR).skipNulls().join(this.prefix, DyFunctions.base64Encode(key));
                  

                  }

                  return Joiner.on(EMPTY_SEPARATOR).skipNulls().join(this.prefix, key); }

                  /**

                • 轉換key,去掉空格
                • @param object
                • @return */ private String objectToString(Object object) { if (object == null) {

                   return null;
                  

                  } else if (object instanceof String) {

                   return Strings.replace((String) object, " ", "_");
                  

                  } else {

                   return object.toString();
                  

                  } }

                  private static final String EMPTY_SEPARATOR = "";

              }</pre>

              創建MemcachedCacheManager

              繼承AbstractCacheManager

              import org.springframework.cache.Cache;
              import org.springframework.cache.support.AbstractCacheManager;

              import java.util.Collection;

              /**

              • 功能說明:memcachedCacheManager
              • 作者:liuxing(2015-04-12 15:13) */ public class MemcachedCacheManager extends AbstractCacheManager {

                private Collection<Cache> caches;

                @Override protected Collection<? extends Cache> loadCaches() {

                 return this.caches;
                

                }

                public void setCaches(Collection<Cache> caches) {

                 this.caches = caches;
                

                }

                public Cache getCache(String name) {

                 return super.getCache(name);
                

                }

              }</pre>

              初始化

              <!-- 啟用緩存注解功能,這個是必須的,否則注解不會生效,指定一個默認的Manager,否則需要在注解使用時指定Manager -->
              <cache:annotation-driven cache-manager="cacheManager"/>

              <!-- memcached緩存管理器 --> <bean id="cacheManager" class="com.dooioo.ky.cache.MemcachedCacheManager"> <property name="caches"> <set> <bean class="com.dooioo.ky.cache.MemcachedCache" p:client-ref="ky.memcachedClient" p:name="kyAreaCache" p:exp="86400"/> <bean class="com.dooioo.ky.cache.MemcachedCache" p:client-ref="ky.memcachedClient" p:name="kyOrganizationCache" p:exp="3600"/> </set> </property> </bean></pre>

              使用

              @Cacheable(value = "kyAreaCache", key="targetClass + '.' + methodName + '.' + #areaId")
              public KyArea findById(String areaId) {
                  // 業務代碼省略
              }

              更多

              更多復雜的使用場景和注解語法請自行谷歌!

              參考
              http://docs.spring.io/spring/docs/4.1.x/spring-framework-reference/html/cache.html

              http://www.cnblogs.com/rollenholt/p/4202631.html

              http://jinnianshilongnian.iteye.com/blog/2001040

              來自:http://liuxing.info/2015/06/18/Spring%20Cache%E4%BD%BF%E7%94%A8/

               本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
               轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
               本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
              名稱 位置 描述 示例
              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
sesese色