spring boot:使用spring cache+caffeine做进程内缓存(本地缓存)(spring boot 2.3.1)
Posted 刘宏缔的架构森林
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring boot:使用spring cache+caffeine做进程内缓存(本地缓存)(spring boot 2.3.1)相关的知识,希望对你有一定的参考价值。
一,为什么要使用caffeine做本地缓存?
1,spring boot默认集成的进程内缓存在1.x时代是guava cache
在2.x时代更新成了caffeine,
功能上差别不大,但后者在性能上更胜一筹,
使用caffeine做本地缓存,取数据可以达到微秒的级别,
一次取数据用时经常不足1毫秒,
这样可以及时响应请求,在高并发的情况下把请求拦截在上游,
避免把压力带到数据库,
所以我们在应用中集成它对于系统的性能有极大的提升
2,与之相比,即使是本地的redis,
响应时间也比进程内缓存用时要更久,
而且在应用服务器很少有专门配备redis缓存的做法,
而是使用专门的redis集群做为分布式缓存
说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest
对应的源码可以访问这里获取: https://github.com/liuhongdi/
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,演示项目的相关信息
1,项目地址:
https://github.com/liuhongdi/caffeine
2,项目原理:
我们建立了两个cache:goods,goodslist
分别用来缓存单个商品详情和商品列表
3,项目结构:
如图:
三,配置文件说明
1,pom.xml
<!--local cache begin--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.8.5</version> </dependency> <!--local cache end-->
2,application.properties
#profile
spring.profiles.active=cacheable
这个值用来配置cache是否生效
我们在测试或调试时有时会用关闭缓存的需求
如果想关闭cache,把这个值改变一下即可
3,mysql的数据表结构:
CREATE TABLE `goods` ( `goodsId` int(11) NOT NULL AUTO_INCREMENT COMMENT \'id\', `goodsName` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT \'\' COMMENT \'name\', `subject` varchar(200) NOT NULL DEFAULT \'\' COMMENT \'标题\', `price` decimal(15,2) NOT NULL DEFAULT \'0.00\' COMMENT \'价格\', `stock` int(11) NOT NULL DEFAULT \'0\' COMMENT \'stock\', PRIMARY KEY (`goodsId`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT=\'商品表\'
四,java代码说明
1,CacheConfig.java
@Profile("cacheable") //这个当profile值为cacheable时缓存才生效 @Configuration @EnableCaching //开启缓存 public class CacheConfig { public static final int DEFAULT_MAXSIZE = 10000; public static final int DEFAULT_TTL = 600; private SimpleCacheManager cacheManager = new SimpleCacheManager(); //定义cache名称、超时时长(秒)、最大容量 public enum CacheEnum{ goods(600,3000), //有效期600秒 , 最大容量3000 goodslist(600,1000), //有效期600秒 , 最大容量1000 ; CacheEnum(int ttl, int maxSize) { this.ttl = ttl; this.maxSize = maxSize; } private int maxSize=DEFAULT_MAXSIZE; //最大數量 private int ttl=DEFAULT_TTL; //过期时间(秒) public int getMaxSize() { return maxSize; } public int getTtl() { return ttl; } } //创建基于Caffeine的Cache Manager @Bean @Primary public CacheManager caffeineCacheManager() { ArrayList<CaffeineCache> caches = new ArrayList<CaffeineCache>(); for(CacheEnum c : CacheEnum.values()){ caches.add(new CaffeineCache(c.name(), Caffeine.newBuilder().recordStats() .expireAfterWrite(c.getTtl(), TimeUnit.SECONDS) .maximumSize(c.getMaxSize()).build()) ); } cacheManager.setCaches(caches); return cacheManager; } @Bean public CacheManager getCacheManager() { return cacheManager; } }
说明:根据CacheEnum这个enum的内容来生成Caffeine Cache,并保存到cachemanager中
如果需要新增缓存,保存到CacheEnum中
@Profile("cacheable"): 当spring.profiles.active的值中包括cacheable时cache才起作用,不包含此值时则cache被关闭不生效
@EnableCaching : 用来开启缓存
recordStats:记录统计数据
expireAfterWrite:在写入到达一定时间后过期
maximumSize:指定最大容量
2,GoodsServiceImpl.java
@Service public class GoodsServiceImpl implements GoodsService { @Resource private GoodsMapper goodsMapper; //得到一件商品的信息 @Cacheable(value = "goods", key="#goodsId",sync = true) @Override public Goods getOneGoodsById(Long goodsId) { System.out.println("query database"); Goods goodsOne = goodsMapper.selectOneGoods(goodsId); return goodsOne; } //获取商品列表,只更新缓存 @CachePut(value = "goodslist", key="#currentPage") @Override public Map<String,Object> putAllGoodsByPage(int currentPage) { Map<String,Object> res = getAllGoodsByPageDdata(currentPage); return res; } //获取商品列表,加缓存 //@Cacheable(key = "#page+\'-\'+#pageSize") 多个参数可以用字符串连接起来 @Cacheable(value = "goodslist", key="#currentPage",sync = true) @Override public Map<String,Object> getAllGoodsByPage(int currentPage) { Map<String,Object> res = getAllGoodsByPageDdata(currentPage); return res; } //从数据库获取商品列表 public Map<String,Object> getAllGoodsByPageDdata(int currentPage) { System.out.println("-----从数据库得到数据"); Map<String,Object> res = new HashMap<String,Object>(); PageHelper.startPage(currentPage, 5); List<Goods> goodsList = goodsMapper.selectAllGoods(); res.put("goodslist",goodsList); PageInfo<Goods> pageInfo = new PageInfo<>(goodsList); res.put("pageInfo",pageInfo); return res; } }
说明:
@Cacheable(value = "goodslist", key="#currentPage",sync = true):
当缓存中存在数据时,则直接从缓存中返回,
如果缓存中不存在数据,则要执行方法返回数据后并把数据保存到缓存.
@CachePut(value = "goodslist", key="#currentPage"):
不判断缓存中是否存在数据,只更新缓存
3,HomeController.java
//商品列表 参数:第几页 @GetMapping("/goodslist") public String goodsList(Model model, @RequestParam(value="p",required = false,defaultValue = "1") int currentPage) { Map<String,Object> res = goodsService.getAllGoodsByPage(currentPage); model.addAttribute("pageInfo", res.get("pageInfo")); model.addAttribute("goodslist", res.get("goodslist")); return "goods/goodslist"; } //更新 @ResponseBody @GetMapping("/goodslistput") public String goodsListPut(@RequestParam(value="p",required = false,defaultValue = "1") int currentPage) { Map<String,Object> res = goodsService.putAllGoodsByPage(currentPage); return "cache put succ"; } //清除 @CacheEvict(value="goodslist", allEntries=true) @ResponseBody @GetMapping("/goodslistevict") public String goodsListEvict() { return "cache evict succ"; }
说明:
@CacheEvict(value="goodslist", allEntries=true):用来清除goodslist中的缓存
4,StatsController.java
@Profile("cacheable") @Controller @RequestMapping("/stats") public class StatsController { //统计,如果是生产环境,需要加密才允许访问 @Resource private CacheManager cacheManager; @GetMapping("/stats") @ResponseBody public String stats() { CaffeineCache caffeine = (CaffeineCache)cacheManager.getCache("goodslist"); Cache goods = caffeine.getNativeCache(); String statsInfo="cache名字:goodslist<br/>"; Long size = goods.estimatedSize(); statsInfo += "size:"+size+"<br/>"; ConcurrentMap map= goods.asMap(); statsInfo += "map keys:<br/>"; for(Object key : map.keySet()) { statsInfo += "key:"+key.toString()+";value:"+map.get(key)+"<br/>"; } statsInfo += "统计信息:"+goods.stats().toString(); return statsInfo; } }
五,效果测试
1,访问地址:
http://127.0.0.1:8080/home/goodslist/?p=1
如图:
刷新几次后,可以看到,在使用cache时:
costtime aop 方法doafterreturning:毫秒数:0
使用的时间不足1毫秒
2,查看统计信息,访问:
http://127.0.0.1:8080/stats/stats
如图:
3,清除cache信息:
http://127.0.0.1:8080/home/goodslistevict
查看缓存的统计:
cache名字:goodslist size:0 map keys: 统计信息:CacheStats{hitCount=1, missCount=3, loadSuccessCount=3, loadFailureCount=0, totalLoadTime=596345574, evictionCount=0, evictionWeight=0}
可以看到数据已被清空
六,查看spring boot的版本
. ____ _ __ _ _ /\\\\ / ___\'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\ ( ( )\\___ | \'_ | \'_| | \'_ \\/ _` | \\ \\ \\ \\ \\\\/ ___)| |_)| | | | | || (_| | ) ) ) ) \' |____| .__|_| |_|_| |_\\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.1.RELEASE)
以上是关于spring boot:使用spring cache+caffeine做进程内缓存(本地缓存)(spring boot 2.3.1)的主要内容,如果未能解决你的问题,请参考以下文章