手把手教学在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方法。看以下代码
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";
}
});
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依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
2.GuavaAbstractLoadingCache 缓存加载方式和基本属性使用基类(我用的是CacheBuilder)
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 2018/11/2 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;
}
}
3.ILocalCache 缓存获取调用接口 (用接口方式 类业务操作)
public interface ILocalCache <K, V> { /**
* 从缓存中获取数据
* @param key
* @return value
*/
public V get(K key);
}
4.缓存获取的实现方法 缓存实例
import com.cn.alasga.merchant.bean.area.Area;
import com.cn.alasga.merchant.mapper.area.AreaMapper;
import com.cn.alasga.merchant.service.area.AreaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; /**
* @author LiJing
* @ClassName: LCAreaIdToArea
* @date 2018/11/2 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);
}
}
至此,以上就完成了,简单缓存搭建,就可以使用了. 其原理就是就是先从缓存中查询,没有就去数据库中查询放入缓存,再去维护缓存,基于你设置的属性,只需集成缓存实现接口就可以扩展缓存了............上面就是举个栗子
4.再来编写缓存管理,进行缓存的管理 这里是统一的缓存管理 可以返回到Controller去统一管理
import com.cn.alasga.common.core.page.PageParams;
import com.cn.alasga.common.core.page.PageResult;
import com.cn.alasga.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 2018/11/2 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;
}
}
缓存service
import com.alibaba.dubbo.config.annotation.Service;
import com.cn.alasga.merchant.cache.GuavaCacheManager;
import com.cn.alasga.merchant.service.cache.CacheService; import java.util.ArrayList;
import java.util.Map; /**
* @ClassName: CacheServiceImpl
* @author zhoujl
* @date 2018.11.6 下午 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.alasga.common.core.page.JsonResult;
import com.cn.alasga.merchant.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/11/6 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缓存,可以reset每一个缓存,管理每一个缓存,更新一些不经常改变的数据. 下面是页面展示~~~~~~~~~~
手把手教学在Springboot中搭建使用Guava cache,包教包会,不会我输一包辣条给你的更多相关文章
- springboot中使用cache和redis
知识点:springboot中使用cache和redis (1)springboot中,整合了cache,我们只需要,在入口类上加 @EnableCaching 即可开启缓存 例如:在service层 ...
- [Java 缓存] Java Cache之 Guava Cache的简单应用.
前言 今天第一次使用MarkDown的形式发博客. 准备记录一下自己对Guava Cache的认识及项目中的实际使用经验. 一: 什么是Guava Guava工程包含了若干被Google的 Java项 ...
- SpringBoot中Redis的set、map、list、value、实体类等基本操作介绍
今天给大家介绍一下SpringBoot中Redis的set.map.list.value等基本操作的具体使用方法 上一节中给大家介绍了如何在SpringBoot中搭建Redis缓存数据库,这一节就针对 ...
- 在MyEclipse中搭建spring-boot+mybatis+freemarker框架
一.创建项目 1.右键-->New-->Project... 2.选中Maven Project,点击next 3.选中第一个 4.添写Group Id,Artifact Id,选择Com ...
- Guava Cache在实际项目中的应用
对于Guava Cache本身就不多做介绍了,一个非常好用的本地cache lib,可以完全取代自己手动维护ConcurrentHashMap. 背景 目前需要开发一个接口I,对性能要求有非常高的要求 ...
- Springboot中的缓存Cache和CacheManager原理介绍
背景理解 什么是缓存,为什么要用缓存 程序运行中,在内存保持一定时间不变的数据就是缓存.简单到写一个Map,里面放着一些key,value数据,就已经是个缓存了 所以缓存并不是什么高大上的技术,只是个 ...
- Spring cache简单使用guava cache
Spring cache简单使用 前言 spring有一套和各种缓存的集成方式.类似于sl4j,你可以选择log框架实现,也一样可以实现缓存实现,比如ehcache,guava cache. [TOC ...
- 使用idea+springboot+Mybatis搭建web项目
使用idea+springboot+Mybatis搭建web项目 springboot的优势之一就是快速搭建项目,省去了自己导入jar包和配置xml的时间,使用非常方便. 1.创建项目project, ...
- springBoot的搭建使用记录
一: 首次搭建:https://blog.csdn.net/u013187139/article/details/68944972 整合mybatis: https://www.jianshu.com ...
随机推荐
- jmeter接口测试-线程组设置(断言失败后用例停止执行)
问题描述: jmeter跑接口用例的时候,其中一条用例的对断言失败后,后面的用例都不执行了! 解决思路1: 考虑应该有地方设置,在菜单栏找了半天没找到,百度也没有查到 解决思路2: jmeter源码导 ...
- Shiro与基本web环境整合登陆验证实例
1. 用maven导入Shiro依赖包 <dependency> <groupId>org.apache.shiro</groupId> <artifactI ...
- C#动态给Word文档填充内容
//filePath:word文档的路径:strOld:需要替换的内容:strNew:替换的新内容: //注意:strOld中的字符数量要与新的strNew中的一一对应 public static v ...
- Skyline TerraExplorer 7.0- 扩展信息树
Skyline TerraExplorer V7增加了一个扩展信息树的控件TEInformationWindowEx. 该控件能够将TE3DWindowEx窗口里面的对象显示为信息树的方式.TEIn ...
- xpath 选取指定文本内容可能是多种情况下的语法
url_list = select.xpath("//ul/li/a[contains(text(),'新闻中心' )]/../../li/a/@href | //ul/li/a[conta ...
- dubbo rest服务 No provider available for the service 错误问题
1.版本 dubbo 2.6.2 2.描述 消费者调用dubbo rest服务报No provider available for the service错误 网络上有讲是实体类未实现Serializ ...
- django2.0再写一行代码
基础 @LTS长期支持 @django-admin startproject xxx python manage.py runserver python manage.py startapp xxx ...
- mysql登录1045错误时 修改登录密码
1.进入 mysql 的 bin 目录下,打开 cmd ,关闭 mysql 数据库. 2.输入 mysqld --skip-grant-tables 回车. 保持窗口不要更改不要关闭 (--skip- ...
- C++中#include<iostream>
#include 是个包含命令,就是把iostream.h这个文件里的内容复制到这个地方 iostream.h是input output stream的简写,意思为标准的输入输出流头文件.它包含: ( ...
- redis 安装启动
1.解压 tar -zxvf redis文件 2. make 3. cp redis-server redis-cli /usr/local/redis cp redis.conf /usr/loca ...