(Reference-based Eviction)强(strong)、软(soft)、弱(weak)、虚(phantom)引用-参考:通过使用弱引用的键、或弱引用的值、或软引用的值GuavaCache可以把缓存设置为允许垃圾回收:
Guava cache 是利用CacheBuilder类用builder模式构造出两种不同的cache加载方式CacheLoader,Callable,共同逻辑都是根据key是加载value。不同的地方在于CacheLoader的定义比较宽泛,是针对整个cache定义的,可以认为是统一的根据key值load value的方法,而Callable的方式较为灵活,允许你在get的时候指定load方法。看以下代码:
Cache<String,Object> cache = CacheBuilder.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS).maximumSize(500).build(); cache.get("key", new Callable<Object>() { //Callable 加载 @Override public Object call() throws Exception { return "value"; } }); LoadingCache<String, Object> loadingCache = CacheBuilder.newBuilder() .expireAfterAccess(30, TimeUnit.SECONDS).maximumSize(5) .build(new CacheLoader<String, Object>() { @Override public Object load(String key) throws Exception { return "value"; } }); 复制代码
定义的方式一般为 CacheBuilder.maximumSize(long),还有一种一种可以算权重的方法,个人认为实际使用中不太用到。就这个常用的来看有几个注意点,
如果需要在移除数据的时候有所动作还可以定义Removal Listener,但是有点需要注意的是默认Removal Listener中的行为是和移除动作同步执行的,如果需要改成异步形式,可以考虑使用RemovalListeners.asynchronous(RemovalListener, Executor)
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency> 复制代码
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * @ClassName: GuavaAbstractLoadingCache * @author LiJing * @date 2019/07/02 11:09 * */ public abstract class GuavaAbstractLoadingCache <K, V> { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); //用于初始化cache的参数及其缺省值 private int maximumSize = 1000; //最大缓存条数,子类在构造方法中调用setMaximumSize(int size)来更改 private int expireAfterWriteDuration = 60; //数据存在时长,子类在构造方法中调用setExpireAfterWriteDuration(int duration)来更改 private TimeUnit timeUnit = TimeUnit.MINUTES; //时间单位(分钟) private Date resetTime; //Cache初始化或被重置的时间 private long highestSize=0; //历史最高记录数 private Date highestTime; //创造历史记录的时间 private LoadingCache<K, V> cache; /** * 通过调用getCache().get(key)来获取数据 * @return cache */ public LoadingCache<K, V> getCache() { if(cache == null){ //使用双重校验锁保证只有一个cache实例 synchronized (this) { if(cache == null){ cache = CacheBuilder.newBuilder().maximumSize(maximumSize) //缓存数据的最大条目,也可以使用.maximumWeight(weight)代替 .expireAfterWrite(expireAfterWriteDuration, timeUnit) //数据被创建多久后被移除 .recordStats() //启用统计 .build(new CacheLoader<K, V>() { @Override public V load(K key) throws Exception { return fetchData(key); } }); this.resetTime = new Date(); this.highestTime = new Date(); logger.debug("本地缓存{}初始化成功", this.getClass().getSimpleName()); } } } return cache; } /** * 根据key从数据库或其他数据源中获取一个value,并被自动保存到缓存中。 * @param key * @return value,连同key一起被加载到缓存中的。 */ protected abstract V fetchData(K key) throws Exception; /** * 从缓存中获取数据(第一次自动调用fetchData从外部获取数据),并处理异常 * @param key * @return Value * @throws ExecutionException */ protected V getValue(K key) throws ExecutionException { V result = getCache().get(key); if(getCache().size() > highestSize){ highestSize = getCache().size(); highestTime = new Date(); } return result; } public long getHighestSize() { return highestSize; } public Date getHighestTime() { return highestTime; } public Date getResetTime() { return resetTime; } public void setResetTime(Date resetTime) { this.resetTime = resetTime; } public int getMaximumSize() { return maximumSize; } public int getExpireAfterWriteDuration() { return expireAfterWriteDuration; } public TimeUnit getTimeUnit() { return timeUnit; } /** * 设置最大缓存条数 * @param maximumSize */ public void setMaximumSize(int maximumSize) { this.maximumSize = maximumSize; } /** * 设置数据存在时长(分钟) * @param expireAfterWriteDuration */ public void setExpireAfterWriteDuration(int expireAfterWriteDuration) { this.expireAfterWriteDuration = expireAfterWriteDuration; } } 复制代码
public interface ILocalCache <K, V> { /** * 从缓存中获取数据 * @param key * @return value */ public V get(K key); } 复制代码
import com.cn.xxx.xxx.bean.area.Area; import com.cn.xxx.xxx.mapper.area.AreaMapper; import com.cn.xxx.xxx.service.area.AreaService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author LiJing * @ClassName: LCAreaIdToArea * @date 2019/07/02 11:12 */ @Component public class AreaCache extends GuavaAbstractLoadingCache<Long, Area> implements ILocalCache<Long, Area> { @Autowired private AreaService areaService; //由Spring来维持单例模式 private AreaCache() { setMaximumSize(4000); //最大缓存条数 } @Override public Area get(Long key) { try { Area value = getValue(key); return value; } catch (Exception e) { logger.error("无法根据baseDataKey={}获取Area,可能是数据库中无该记录。", key, e); return null; } } /** * 从数据库中获取数据 */ @Override protected Area fetchData(Long key) { logger.debug("测试:正在从数据库中获取area,area id={}", key); return areaService.getAreaInfo(key); } } 复制代码
至此,以上就完成了,简单缓存搭建,就可以使用了. 其原理就是就是先从缓存中查询,没有就去数据库中查询放入缓存,再去维护缓存,基于你设置的属性,只需集成缓存实现接口就可以扩展缓存了............上面就是举个栗子
import com.cn.xxx.common.core.page.PageParams; import com.cn.xxx.common.core.page.PageResult; import com.cn.xxx.common.core.util.SpringContextUtil; import com.google.common.cache.CacheStats; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; import java.util.concurrent.ConcurrentMap; /** * @ClassName: GuavaCacheManager * @author LiJing * @date 2019/07/02 11:17 * */ public class GuavaCacheManager { //保存一个Map: cacheName -> cache Object,以便根据cacheName获取Guava cache对象 private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> cacheNameToObjectMap = null; /** * 获取所有GuavaAbstractLoadingCache子类的实例,即所有的Guava Cache对象 * @return */ @SuppressWarnings("unchecked") private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> getCacheMap(){ if(cacheNameToObjectMap==null){ cacheNameToObjectMap = (Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>>) SpringContextUtil.getBeanOfType(GuavaAbstractLoadingCache.class); } return cacheNameToObjectMap; } /** * 根据cacheName获取cache对象 * @param cacheName * @return */ private static GuavaAbstractLoadingCache<Object, Object> getCacheByName(String cacheName){ return (GuavaAbstractLoadingCache<Object, Object>) getCacheMap().get(cacheName); } /** * 获取所有缓存的名字(即缓存实现类的名称) * @return */ public static Set<String> getCacheNames() { return getCacheMap().keySet(); } /** * 返回所有缓存的统计数据 * @return List<Map<统计指标,统计数据>> */ public static ArrayList<Map<String, Object>> getAllCacheStats() { Map<String, ? extends Object> cacheMap = getCacheMap(); List<String> cacheNameList = new ArrayList<>(cacheMap.keySet()); Collections.sort(cacheNameList);//按照字母排序 //遍历所有缓存,获取统计数据 ArrayList<Map<String, Object>> list = new ArrayList<>(); for(String cacheName : cacheNameList){ list.add(getCacheStatsToMap(cacheName)); } return list; } /** * 返回一个缓存的统计数据 * @param cacheName * @return Map<统计指标,统计数据> */ private static Map<String, Object> getCacheStatsToMap(String cacheName) { Map<String, Object> map = new LinkedHashMap<>(); GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName); CacheStats cs = cache.getCache().stats(); NumberFormat percent = NumberFormat.getPercentInstance(); // 建立百分比格式化用 percent.setMaximumFractionDigits(1); // 百分比小数点后的位数 map.put("cacheName", cacheName);//Cache名称 map.put("size", cache.getCache().size());//当前数据量 map.put("maximumSize", cache.getMaximumSize());//最大缓存条数 map.put("survivalDuration", cache.getExpireAfterWriteDuration());//过期时间 map.put("hitCount", cs.hitCount());//命中次数 map.put("hitRate", percent.format(cs.hitRate()));//命中比例 map.put("missRate", percent.format(cs.missRate()));//读库比例 map.put("loadSuccessCount", cs.loadSuccessCount());//成功加载数 map.put("loadExceptionCount", cs.loadExceptionCount());//成功加载数 map.put("totalLoadTime", cs.totalLoadTime()/1000000); //总加载毫秒ms SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); if(cache.getResetTime()!=null){ map.put("resetTime", df.format(cache.getResetTime()));//重置时间 LocalDateTime localDateTime = LocalDateTime.ofInstant(cache.getResetTime().toInstant(), ZoneId.systemDefault()).plusMinutes(cache.getTimeUnit().toMinutes(cache.getExpireAfterWriteDuration())); map.put("survivalTime", df.format(Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())));//失效时间 } map.put("highestSize", cache.getHighestSize());//历史最高数据量 if(cache.getHighestTime()!=null){ map.put("highestTime", df.format(cache.getHighestTime()));//最高数据量时间 } return map; } /** * 根据cacheName清空缓存数据 * @param cacheName */ public static void resetCache(String cacheName){ GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName); cache.getCache().invalidateAll(); cache.setResetTime(new Date()); } /** * 分页获得缓存中的数据 * @param pageParams * @return */ public static PageResult<Object> queryDataByPage(PageParams<Object> pageParams) { PageResult<Object> data = new PageResult<>(pageParams); GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName((String) pageParams.getParams().get("cacheName")); ConcurrentMap<Object, Object> cacheMap = cache.getCache().asMap(); data.setTotalRecord(cacheMap.size()); data.setTotalPage((cacheMap.size()-1)/pageParams.getPageSize()+1); //遍历 Iterator<Map.Entry<Object, Object>> entries = cacheMap.entrySet().iterator(); int startPos = pageParams.getStartPos()-1; int endPos = pageParams.getEndPos()-1; int i=0; Map<Object, Object> resultMap = new LinkedHashMap<>(); while (entries.hasNext()) { Map.Entry<Object, Object> entry = entries.next(); if(i>endPos){ break; } if(i>=startPos){ resultMap.put(entry.getKey(), entry.getValue()); } i++; } List<Object> resultList = new ArrayList<>(); resultList.add(resultMap); data.setResults(resultList); return data; } } 复制代码
import com.alibaba.dubbo.config.annotation.Service; import com.cn.xxx.xxx.cache.GuavaCacheManager; import com.cn.xxx.xxx.service.cache.CacheService; import java.util.ArrayList; import java.util.Map; /** * @ClassName: CacheServiceImpl * @author lijing * @date 2019.07.06 下午 5:29 * */ @Service(version = "1.0.0") public class CacheServiceImpl implements CacheService { @Override public ArrayList<Map<String, Object>> getAllCacheStats() { return GuavaCacheManager.getAllCacheStats(); } @Override public void resetCache(String cacheName) { GuavaCacheManager.resetCache(cacheName); } } 复制代码
import com.alibaba.dubbo.config.annotation.Reference; import com.cn.xxx.common.core.page.JsonResult; import com.cn.xxx.xxx.service.cache.CacheService; import com.github.pagehelper.PageInfo; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; /** * @ClassName: CacheAdminController * @author LiJing * @date 2018/07/06 10:10 * */ @Controller @RequestMapping("/cache") public class CacheAdminController { @Reference(version = "1.0.0") private CacheService cacheService; @GetMapping("") @RequiresPermissions("cache:view") public String index() { return "admin/system/cache/cacheList"; } @PostMapping("/findPage") @ResponseBody @RequiresPermissions("cache:view") public PageInfo findPage() { return new PageInfo<>(cacheService.getAllCacheStats()); } /** * 清空缓存数据、并返回清空后的统计信息 * @param cacheName * @return */ @RequestMapping(value = "/reset", method = RequestMethod.POST) @ResponseBody @RequiresPermissions("cache:reset") public JsonResult cacheReset(String cacheName) { JsonResult jsonResult = new JsonResult(); cacheService.resetCache(cacheName); jsonResult.setMessage("已经成功重置了" + cacheName + "!"); return jsonResult; } /** * 查询cache统计信息 * @param cacheName * @return cache统计信息 */ /*@RequestMapping(value = "/stats", method = RequestMethod.POST) @ResponseBody public JsonResult cacheStats(String cacheName) { JsonResult jsonResult = new JsonResult(); //暂时只支持获取全部 switch (cacheName) { case "*": jsonResult.setData(GuavaCacheManager.getAllCacheStats()); jsonResult.setMessage("成功获取了所有的cache!"); break; default: break; } return jsonResult; }*/ /** * 返回所有的本地缓存统计信息 * @return */ /*@RequestMapping(value = "/stats/all", method = RequestMethod.POST) @ResponseBody public JsonResult cacheStatsAll() { return cacheStats("*"); }*/ /** * 分页查询数据详情 * @param params * @return */ /*@RequestMapping(value = "/queryDataByPage", method = RequestMethod.POST) @ResponseBody public PageResult<Object> queryDataByPage(@RequestParam Map<String, String> params){ int pageSize = Integer.valueOf(params.get("pageSize")); int pageNo = Integer.valueOf(params.get("pageNo")); String cacheName = params.get("cacheName"); PageParams<Object> page = new PageParams<>(); page.setPageSize(pageSize); page.setPageNo(pageNo); Map<String, Object> param = new HashMap<>(); param.put("cacheName", cacheName); page.setParams(param); return GuavaCacheManager.queryDataByPage(page); }*/ } 复制代码
以上就是gauva缓存,在后台中我们可以重启和清除缓存,管理每一个缓存和查看缓存的统计信息,经常用于缓存一些不经常改变的数据 写的简陋,欢迎大家抨击~ 下面是一个后台页面展示: