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每一個緩存,管理每一個緩存,更新一些不經常改變的數據. 下面是頁面展示~~~~~~~~~~