手把手教學在Springboot中搭建使用Guava cache,包教包會,不會我輸一包辣條給你


 guava cache使用簡介

  概述

  緩存是日常開發中經常應用到的一種技術手段,合理的利用緩存可以極大的改善應用程序的性能。
  Guava官方對Cache的描述連接
  緩存在各種各樣的用例中非常有用。例如,當計算或檢索值很昂貴時,您應該考慮使用緩存,並且不止一次需要它在某個輸入上的值。
  緩存ConcurrentMap要小,但不完全相同。最根本的區別在於一個ConcurrentMap堅持所有添加到它直到他們明確地刪除元素。
  另一方面,緩存一般配置為自動退出的條目,以限制其內存占用。在某些情況下,一個LoadingCache可以即使不驅逐的條目是有用的,因為它的自動緩存加載。

  適用性
  你願意花一些內存來提高速度。You are willing to spend some memory to improve speed.
  您希望Key有時會不止一次被查詢。You expect that keys will sometimes get queried more than once.
  你的緩存不需要存儲更多的數據比什么都適合在。(Guava緩存是本地應用程序的一次運行)。Your cache will not need to store more data than what would fit inRAM. (Guava caches are local to a single run of your application.
  它們不將數據存儲在文件中,也不存儲在外部服務器上。如果這樣做不適合您的需要,考慮一個工具像memcached。

  

  基於引用的回收(Reference-based Eviction)強(strong)、軟(soft)、弱(weak)、虛(phantom)引用-參考
  通過使用弱引用的鍵、或弱引用的值、或軟引用的值,Guava Cache可以把緩存設置為允許垃圾回收:
  CacheBuilder.weakKeys():使用弱引用存儲鍵。當鍵沒有其它(強或軟)引用時,緩存項可以被垃圾回收。因為垃圾回收僅依賴恆等式(==),使用弱引用鍵的緩存用==而不是equals比較鍵。
  CacheBuilder.weakValues():使用弱引用存儲值。當值沒有其它(強或軟)引用時,緩存項可以被垃圾回收。因為垃圾回收僅依賴恆等式(==),使用弱引用值的緩存用==而不是equals比較值。
  CacheBuilder.softValues():使用軟引用存儲值。軟引用只有在響應內存需要時,才按照全局最近最少使用的順序回收。考慮到使用軟引用的性能影響,我們通常建議使用更有性能預測性的緩存大小限定(見上文,基於容量回收)。使用軟引用值的緩存同樣用==而不是equals比較值。!

  guava cache 是利用CacheBuilder類用builder模式構造出兩種不同的cache加載方式CacheLoader,Callable,共同邏輯都是根據key是加載value。不同的地方在於CacheLoader的定義比較寬泛,是針對整個cache定義的,可以認為是統一的根據key值load value的方法,而Callable的方式較為靈活,允許你在get的時候指定load方法。看以下代碼

 1         Cache<String,Object> cache = CacheBuilder.newBuilder()
 2                 .expireAfterWrite(10, TimeUnit.SECONDS).maximumSize(500).build();
 3  
 4          cache.get("key", new Callable<Object>() { //Callable 加載
 5             @Override
 6             public Object call() throws Exception {
 7                 return "value";
 8             }
 9         });
10  
11         LoadingCache<String, Object> loadingCache = CacheBuilder.newBuilder()
12                 .expireAfterAccess(30, TimeUnit.SECONDS).maximumSize(5)
13                 .build(new CacheLoader<String, Object>() {
14                     @Override
15                     public Object load(String key) throws Exception {
16                         return "value";
17                     }
18                 });    

 

 guava Cache數據移除:

  guava做cache時候數據的移除方式,在guava中數據的移除分為被動移除和主動移除兩種。
  被動移除數據的方式,guava默認提供了三種方式:
  1.基於大小的移除:看字面意思就知道就是按照緩存的大小來移除,如果即將到達指定的大小,那就會把不常用的鍵值對從cache中移除。
  定義的方式一般為 CacheBuilder.maximumSize(long),還有一種一種可以算權重的方法,個人認為實際使用中不太用到。就這個常用的來看有幾個注意點,
    其一,這個size指的是cache中的條目數,不是內存大小或是其他;
    其二,並不是完全到了指定的size系統才開始移除不常用的數據的,而是接近這個size的時候系統就會開始做移除的動作;
    其三,如果一個鍵值對已經從緩存中被移除了,你再次請求訪問的時候,如果cachebuild是使用cacheloader方式的,那依然還是會從cacheloader中再取一次值,如果這樣還沒有,就會拋出異常
  2.基於時間的移除:guava提供了兩個基於時間移除的方法
    expireAfterAccess(long, TimeUnit)  這個方法是根據某個鍵值對最后一次訪問之后多少時間后移除
    expireAfterWrite(long, TimeUnit)  這個方法是根據某個鍵值對被創建或值被替換后多少時間移除
  3.基於引用的移除:
  這種移除方式主要是基於java的垃圾回收機制,根據鍵或者值的引用關系決定移除
  主動移除數據方式,主動移除有三種方法
  1.單獨移除用 Cache.invalidate(key)
  2.批量移除用 Cache.invalidateAll(keys)
  3.移除所有用 Cache.invalidateAll()
  如果需要在移除數據的時候有所動作還可以定義Removal Listener,但是有點需要注意的是默認Removal Listener中的行為是和移除動作同步執行的,如果需要改成異步形式,可以考慮使用RemovalListeners.asynchronous(RemovalListener, Executor)

  現在來實戰演示:

  1.maven依賴 

1 <dependency>
2    <groupId>com.google.guava</groupId>
3    <artifactId>guava</artifactId>
4    <version>23.0</version>
5 </dependency>

  2.GuavaAbstractLoadingCache  緩存加載方式和基本屬性使用基類(我用的是CacheBuilder)

  1 import com.google.common.cache.CacheBuilder;
  2 import com.google.common.cache.CacheLoader;
  3 import com.google.common.cache.LoadingCache;
  4 import org.slf4j.Logger;
  5 import org.slf4j.LoggerFactory;
  6 
  7 import java.util.Date;
  8 import java.util.concurrent.ExecutionException;
  9 import java.util.concurrent.TimeUnit;
 10 
 11 /**
 12  * @ClassName: GuavaAbstractLoadingCache
 13  * @author LiJing
 14  * @date 2018/11/2 11:09 
 15  *
 16  */
 17 
 18 public abstract class GuavaAbstractLoadingCache <K, V> {
 19     protected final Logger logger = LoggerFactory.getLogger(this.getClass());
 20 
 21     //用於初始化cache的參數及其缺省值
 22     private int maximumSize = 1000;                 //最大緩存條數,子類在構造方法中調用setMaximumSize(int size)來更改
 23     private int expireAfterWriteDuration = 60;      //數據存在時長,子類在構造方法中調用setExpireAfterWriteDuration(int duration)來更改
 24     private TimeUnit timeUnit = TimeUnit.MINUTES;   //時間單位(分鍾)
 25 
 26     private Date resetTime;     //Cache初始化或被重置的時間
 27     private long highestSize=0; //歷史最高記錄數
 28     private Date highestTime;   //創造歷史記錄的時間
 29 
 30     private LoadingCache<K, V> cache;
 31 
 32     /**
 33      * 通過調用getCache().get(key)來獲取數據
 34      * @return cache
 35      */
 36     public LoadingCache<K, V> getCache() {
 37         if(cache == null){  //使用雙重校驗鎖保證只有一個cache實例
 38             synchronized (this) {
 39                 if(cache == null){
 40                     cache = CacheBuilder.newBuilder().maximumSize(maximumSize)      //緩存數據的最大條目,也可以使用.maximumWeight(weight)代替
 41                             .expireAfterWrite(expireAfterWriteDuration, timeUnit)   //數據被創建多久后被移除
 42                             .recordStats()                                          //啟用統計
 43                             .build(new CacheLoader<K, V>() {
 44                                 @Override
 45                                 public V load(K key) throws Exception {
 46                                     return fetchData(key);
 47                                 }
 48                             });
 49                     this.resetTime = new Date();
 50                     this.highestTime = new Date();
 51                     logger.debug("本地緩存{}初始化成功", this.getClass().getSimpleName());
 52                 }
 53             }
 54         }
 55 
 56         return cache;
 57     }
 58 
 59     /**
 60      * 根據key從數據庫或其他數據源中獲取一個value,並被自動保存到緩存中。
 61      * @param key
 62      * @return value,連同key一起被加載到緩存中的。
 63      */
 64     protected abstract V fetchData(K key) throws Exception;
 65 
 66     /**
 67      * 從緩存中獲取數據(第一次自動調用fetchData從外部獲取數據),並處理異常
 68      * @param key
 69      * @return Value
 70      * @throws ExecutionException
 71      */
 72     protected V getValue(K key) throws ExecutionException {
 73         V result = getCache().get(key);
 74         if(getCache().size() > highestSize){
 75             highestSize = getCache().size();
 76             highestTime = new Date();
 77         }
 78 
 79         return result;
 80     }
 81 
 82     public long getHighestSize() {
 83         return highestSize;
 84     }
 85 
 86     public Date getHighestTime() {
 87         return highestTime;
 88     }
 89 
 90     public Date getResetTime() {
 91         return resetTime;
 92     }
 93 
 94     public void setResetTime(Date resetTime) {
 95         this.resetTime = resetTime;
 96     }
 97 
 98     public int getMaximumSize() {
 99         return maximumSize;
100     }
101 
102     public int getExpireAfterWriteDuration() {
103         return expireAfterWriteDuration;
104     }
105 
106     public TimeUnit getTimeUnit() {
107         return timeUnit;
108     }
109 
110     /**
111      * 設置最大緩存條數
112      * @param maximumSize
113      */
114     public void setMaximumSize(int maximumSize) {
115         this.maximumSize = maximumSize;
116     }
117 
118     /**
119      * 設置數據存在時長(分鍾)
120      * @param expireAfterWriteDuration
121      */
122     public void setExpireAfterWriteDuration(int expireAfterWriteDuration) {
123         this.expireAfterWriteDuration = expireAfterWriteDuration;
124     }
125 }

 

   3.ILocalCache 緩存獲取調用接口 (用接口方式 類業務操作)

1 public interface ILocalCache <K, V> {
2 
3     /**
4      * 從緩存中獲取數據
5      * @param key
6      * @return value
7      */
8     public V get(K key);
9 }

   4.緩存獲取的實現方法 緩存實例

 1 import com.cn.alasga.merchant.bean.area.Area;
 2 import com.cn.alasga.merchant.mapper.area.AreaMapper;
 3 import com.cn.alasga.merchant.service.area.AreaService;
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import org.springframework.stereotype.Component;
 6 
 7 
 8 /**
 9  * @author LiJing
10  * @ClassName: LCAreaIdToArea
11  * @date 2018/11/2 11:12
12  */
13 
14 @Component
15 public class AreaCache extends GuavaAbstractLoadingCache<Long, Area> implements ILocalCache<Long, Area> {
16 
17     @Autowired
18     private AreaService areaService;
19 
20     //由Spring來維持單例模式
21     private AreaCache() {
22         setMaximumSize(4000); //最大緩存條數
23     }
24 
25     @Override
26     public Area get(Long key) {
27         try {
28             Area value = getValue(key);
29             return value;
30         } catch (Exception e) {
31             logger.error("無法根據baseDataKey={}獲取Area,可能是數據庫中無該記錄。", key, e);
32             return null;
33         }
34     }
35 
36     /**
37      * 從數據庫中獲取數據
38      */
39     @Override
40     protected Area fetchData(Long key) {
41         logger.debug("測試:正在從數據庫中獲取area,area id={}", key);
42         return areaService.getAreaInfo(key);
43     }
44 }

  至此,以上就完成了,簡單緩存搭建,就可以使用了. 其原理就是就是先從緩存中查詢,沒有就去數據庫中查詢放入緩存,再去維護緩存,基於你設置的屬性,只需集成緩存實現接口就可以擴展緩存了............上面就是舉個栗子

   4.再來編寫緩存管理,進行緩存的管理  這里是統一的緩存管理 可以返回到Controller去統一管理

  1 import com.cn.alasga.common.core.page.PageParams;
  2 import com.cn.alasga.common.core.page.PageResult;
  3 import com.cn.alasga.common.core.util.SpringContextUtil;
  4 import com.google.common.cache.CacheStats;
  5 
  6 import java.text.NumberFormat;
  7 import java.text.SimpleDateFormat;
  8 import java.time.LocalDateTime;
  9 import java.time.ZoneId;
 10 import java.util.*;
 11 import java.util.concurrent.ConcurrentMap;
 12 
 13 /**
 14  * @ClassName: GuavaCacheManager
 15  * @author LiJing
 16  * @date 2018/11/2 11:17 
 17  *
 18  */
 19 public class GuavaCacheManager {
 20 
 21     //保存一個Map: cacheName -> cache Object,以便根據cacheName獲取Guava cache對象
 22     private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> cacheNameToObjectMap = null;
 23 
 24     /**
 25      * 獲取所有GuavaAbstractLoadingCache子類的實例,即所有的Guava Cache對象
 26      * @return
 27      */
 28 
 29     @SuppressWarnings("unchecked")
 30     private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> getCacheMap(){
 31         if(cacheNameToObjectMap==null){
 32             cacheNameToObjectMap = (Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>>) SpringContextUtil.getBeanOfType(GuavaAbstractLoadingCache.class);
 33         }
 34         return cacheNameToObjectMap;
 35 
 36     }
 37 
 38     /**
 39      *  根據cacheName獲取cache對象
 40      * @param cacheName
 41      * @return
 42      */
 43     private static GuavaAbstractLoadingCache<Object, Object> getCacheByName(String cacheName){
 44         return (GuavaAbstractLoadingCache<Object, Object>) getCacheMap().get(cacheName);
 45     }
 46 
 47     /**
 48      * 獲取所有緩存的名字(即緩存實現類的名稱)
 49      * @return
 50      */
 51     public static Set<String> getCacheNames() {
 52         return getCacheMap().keySet();
 53     }
 54 
 55     /**
 56      * 返回所有緩存的統計數據
 57      * @return List<Map<統計指標,統計數據>>
 58      */
 59     public static ArrayList<Map<String, Object>> getAllCacheStats() {
 60 
 61         Map<String, ? extends Object> cacheMap = getCacheMap();
 62         List<String> cacheNameList = new ArrayList<>(cacheMap.keySet());
 63         Collections.sort(cacheNameList);//按照字母排序
 64 
 65         //遍歷所有緩存,獲取統計數據
 66         ArrayList<Map<String, Object>> list = new ArrayList<>();
 67         for(String cacheName : cacheNameList){
 68             list.add(getCacheStatsToMap(cacheName));
 69         }
 70 
 71         return list;
 72     }
 73 
 74     /**
 75      * 返回一個緩存的統計數據
 76      * @param cacheName
 77      * @return Map<統計指標,統計數據>
 78      */
 79     private static Map<String, Object> getCacheStatsToMap(String cacheName) {
 80         Map<String, Object> map =  new LinkedHashMap<>();
 81         GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
 82         CacheStats cs = cache.getCache().stats();
 83         NumberFormat percent = NumberFormat.getPercentInstance(); // 建立百分比格式化用
 84         percent.setMaximumFractionDigits(1); // 百分比小數點后的位數
 85         map.put("cacheName", cacheName);//Cache名稱
 86         map.put("size", cache.getCache().size());//當前數據量
 87         map.put("maximumSize", cache.getMaximumSize());//最大緩存條數
 88         map.put("survivalDuration", cache.getExpireAfterWriteDuration());//過期時間
 89         map.put("hitCount", cs.hitCount());//命中次數
 90         map.put("hitRate", percent.format(cs.hitRate()));//命中比例
 91         map.put("missRate", percent.format(cs.missRate()));//讀庫比例
 92         map.put("loadSuccessCount", cs.loadSuccessCount());//成功加載數
 93         map.put("loadExceptionCount", cs.loadExceptionCount());//成功加載數
 94         map.put("totalLoadTime", cs.totalLoadTime()/1000000);       //總加載毫秒ms
 95         SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 96         if(cache.getResetTime()!=null){
 97             map.put("resetTime", df.format(cache.getResetTime()));//重置時間
 98             LocalDateTime localDateTime = LocalDateTime.ofInstant(cache.getResetTime().toInstant(), ZoneId.systemDefault()).plusMinutes(cache.getTimeUnit().toMinutes(cache.getExpireAfterWriteDuration()));
 99             map.put("survivalTime", df.format(Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())));//失效時間
100         }
101         map.put("highestSize", cache.getHighestSize());//歷史最高數據量
102         if(cache.getHighestTime()!=null){
103             map.put("highestTime", df.format(cache.getHighestTime()));//最高數據量時間
104         }
105 
106         return map;
107     }
108 
109     /**
110      * 根據cacheName清空緩存數據
111      * @param cacheName
112      */
113     public static void resetCache(String cacheName){
114         GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
115         cache.getCache().invalidateAll();
116         cache.setResetTime(new Date());
117     }
118 
119     /**
120      * 分頁獲得緩存中的數據
121      * @param pageParams
122      * @return
123      */
124     public static PageResult<Object> queryDataByPage(PageParams<Object> pageParams) {
125         PageResult<Object> data = new PageResult<>(pageParams);
126 
127         GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName((String) pageParams.getParams().get("cacheName"));
128         ConcurrentMap<Object, Object> cacheMap = cache.getCache().asMap();
129         data.setTotalRecord(cacheMap.size());
130         data.setTotalPage((cacheMap.size()-1)/pageParams.getPageSize()+1);
131 
132         //遍歷
133         Iterator<Map.Entry<Object, Object>> entries = cacheMap.entrySet().iterator();
134         int startPos = pageParams.getStartPos()-1;
135         int endPos = pageParams.getEndPos()-1;
136         int i=0;
137         Map<Object, Object> resultMap = new LinkedHashMap<>();
138         while (entries.hasNext()) {
139             Map.Entry<Object, Object> entry = entries.next();
140             if(i>endPos){
141                 break;
142             }
143 
144             if(i>=startPos){
145                 resultMap.put(entry.getKey(), entry.getValue());
146             }
147 
148             i++;
149         }
150         List<Object> resultList = new ArrayList<>();
151         resultList.add(resultMap);
152         data.setResults(resultList);
153         return data;
154     }
155 }

  緩存service

 1 import com.alibaba.dubbo.config.annotation.Service;
 2 import com.cn.alasga.merchant.cache.GuavaCacheManager;
 3 import com.cn.alasga.merchant.service.cache.CacheService;
 4 
 5 import java.util.ArrayList;
 6 import java.util.Map;
 7 
 8 /**
 9  * @ClassName: CacheServiceImpl
10  * @author zhoujl
11  * @date 2018.11.6 下午 5:29
12  *
13  */
14 @Service(version = "1.0.0")
15 public class CacheServiceImpl implements CacheService {
16 
17     @Override
18     public ArrayList<Map<String, Object>> getAllCacheStats() {
19         return GuavaCacheManager.getAllCacheStats();
20     }
21 
22     @Override
23     public void resetCache(String cacheName) {
24         GuavaCacheManager.resetCache(cacheName);
25     }
26 }

  緩存控制器

  1 import com.alibaba.dubbo.config.annotation.Reference;
  2 import com.cn.alasga.common.core.page.JsonResult;
  3 import com.cn.alasga.merchant.service.cache.CacheService;
  4 import com.github.pagehelper.PageInfo;
  5 import org.apache.shiro.authz.annotation.RequiresPermissions;
  6 import org.springframework.stereotype.Controller;
  7 import org.springframework.web.bind.annotation.*;
  8 
  9 /**
 10  * @ClassName: CacheAdminController
 11  * @author LiJing
 12  * @date 2018/11/6 10:10 
 13  *
 14  */
 15 @Controller
 16 @RequestMapping("/cache")
 17 public class CacheAdminController {
 18 
 19     @Reference(version = "1.0.0")
 20     private CacheService cacheService;
 21 
 22     @GetMapping("")
 23     @RequiresPermissions("cache:view")
 24     public String index() {
 25         return "admin/system/cache/cacheList";
 26     }
 27 
 28     @PostMapping("/findPage")
 29     @ResponseBody
 30     @RequiresPermissions("cache:view")
 31     public PageInfo findPage() {
 32         return new PageInfo<>(cacheService.getAllCacheStats());
 33     }
 34 
 35     /**
 36      * 清空緩存數據、並返回清空后的統計信息
 37      * @param cacheName
 38      * @return
 39      */
 40     @RequestMapping(value = "/reset", method = RequestMethod.POST)
 41     @ResponseBody
 42     @RequiresPermissions("cache:reset")
 43     public JsonResult cacheReset(String cacheName) {
 44         JsonResult jsonResult = new JsonResult();
 45 
 46         cacheService.resetCache(cacheName);
 47         jsonResult.setMessage("已經成功重置了" + cacheName + "!");
 48 
 49         return jsonResult;
 50     }
 51 
 52     /**
 53      * 查詢cache統計信息
 54      * @param cacheName
 55      * @return cache統計信息
 56      */
 57     /*@RequestMapping(value = "/stats", method = RequestMethod.POST)
 58     @ResponseBody
 59     public JsonResult cacheStats(String cacheName) {
 60         JsonResult jsonResult = new JsonResult();
 61 
 62         //暫時只支持獲取全部
 63 
 64         switch (cacheName) {
 65             case "*":
 66                 jsonResult.setData(GuavaCacheManager.getAllCacheStats());
 67                 jsonResult.setMessage("成功獲取了所有的cache!");
 68                 break;
 69 
 70             default:
 71                 break;
 72         }
 73 
 74         return jsonResult;
 75     }*/
 76 
 77     /**
 78      * 返回所有的本地緩存統計信息
 79      * @return
 80      */
 81     /*@RequestMapping(value = "/stats/all", method = RequestMethod.POST)
 82     @ResponseBody
 83     public JsonResult cacheStatsAll() {
 84         return cacheStats("*");
 85     }*/
 86 
 87     /**
 88      * 分頁查詢數據詳情
 89      * @param params
 90      * @return
 91      */
 92     /*@RequestMapping(value = "/queryDataByPage", method = RequestMethod.POST)
 93     @ResponseBody
 94     public PageResult<Object> queryDataByPage(@RequestParam Map<String, String> params){
 95         int pageSize = Integer.valueOf(params.get("pageSize"));
 96         int pageNo = Integer.valueOf(params.get("pageNo"));
 97         String cacheName = params.get("cacheName");
 98 
 99         PageParams<Object> page = new PageParams<>();
100         page.setPageSize(pageSize);
101         page.setPageNo(pageNo);
102         Map<String, Object> param = new HashMap<>();
103         param.put("cacheName", cacheName);
104         page.setParams(param);
105 
106         return GuavaCacheManager.queryDataByPage(page);
107     }*/
108 }

  以上就是gauva緩存,可以reset每一個緩存,管理每一個緩存,更新一些不經常改變的數據.   下面是頁面展示~~~~~~~~~~

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM