Guava Cache
分布式缓存和本地缓存知识
-
什么是缓存
- 程序经常要调用的对象存在内存中,方便其使用时可以快速调用,不必去数据库或者其他持久化设备中查询,主要就是提高性能
- DNS缓存、前端缓存、代理服务器缓存Nginx、应用程序缓存(本地缓存、分布式缓存)、数据库缓存
-
分布式缓存
- 与应用分离的缓存组件或服务,与本地应用隔离一个独立的应用,多个应用可直接的共享缓存
- 常见的分布式缓存 Redis、Memcached等
-
本地缓存
- 和业务程序一起的缓存,例如myabtis的一级或者二级缓存,本地缓存自然是最快的,但是不能在多个节点共享
- 常见的本地缓存:ssm基础课程myabtis 一级缓存、mybatis二级缓存;框架本身的缓存; redis本地单机服务;ehchche;guava cache、Caffeine等
-
选择本地缓存和分布式缓存
- 和业务数据结合去选择
- 高并发项目里面一般都是有本地缓存和分布式缓存共同存在的
Guava Cache
- github地址:https://github.com/google/guava/wiki/CachesExplained
- 全内存的本地缓存实现
- 高性能且功能丰富
- 线程安全,操作简单 (底层实现机制类似ConcurrentMap)
添加依赖
<!--guava依赖包-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
封装api
src/main/java/net/xdclass/online_xdclass/utils/BaseCache.java
package net.xdclass.online_xdclass.utils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class BaseCache {
private Cache<String,Object> tenMinuteCache = CacheBuilder.newBuilder()
//设置缓存初始大小,应该合理设置,后续会扩容
.initialCapacity(10)
//最大值
.maximumSize(100)
//并发数设置
.concurrencyLevel(5)
//缓存过期时间,写入后10分钟过期
.expireAfterWrite(600,TimeUnit.SECONDS)
//统计缓存命中率
.recordStats()
.build();
public Cache<String, Object> getTenMinuteCache() {
return tenMinuteCache;
}
public void setTenMinuteCache(Cache<String, Object> tenMinuteCache) {
this.tenMinuteCache = tenMinuteCache;
}
}
轮播图接口引入缓存
缓存key管理类
package net.xdclass.online_xdclass.config;
/**
* 缓存key管理类
*/
public class CacheKeyManager {
/**
* 首页轮播图缓存key
*/
public static final String INDEX_BANNER_KEY = "index:banner";
}
在service层引入Guava cache
package net.xdclass.online_xdclass.service.impl;
import net.xdclass.online_xdclass.config.CacheKeyManager;
import net.xdclass.online_xdclass.model.entity.Video;
import net.xdclass.online_xdclass.model.entity.VideoBanner;
import net.xdclass.online_xdclass.mapper.VideoMapper;
import net.xdclass.online_xdclass.service.VideoService;
import net.xdclass.online_xdclass.utils.BaseCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class VideoServiceImpl implements VideoService {
@Autowired
private VideoMapper videoMapper;
@Autowired
private BaseCache baseCache;
@Override
public List<Video> listVideo() {
return videoMapper.listVideo();
}
@Override
public List<VideoBanner> listBanner() {
try{
//从缓存找不到就从数据库中找(回调)
Object cacheObj = baseCache.getTenMinuteCache().get(CacheKeyManager.INDEX_BANNER_KEY, ()->{
List<VideoBanner> bannerList = videoMapper.listVideoBanner();
System.out.println("从数据库里面找轮播图列表");
return bannerList;
});
if(cacheObj instanceof List){
List<VideoBanner> bannerList = (List<VideoBanner>)cacheObj;
return bannerList;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public Video findDetailById(int videoId) {
// 需要使用mybaits关联复杂查询
Video video = videoMapper.findDetailById(videoId);
return video;
}
}
测试
http://localhost:8081/api/v1/pub/video/list_banner
只有第一次请求 打印 从数据库里面找轮播图列表
之后10分钟内请求直接从缓存中取出
视频列表引入缓存
缓存key管理类
package net.xdclass.online_xdclass.config;
/**
* 缓存key管理类
*/
public class CacheKeyManager {
/**
* 首页轮播图缓存key
*/
public static final String INDEX_BANNER_KEY = "index:banner:list";
/**
* 首页视频列表缓存key
*/
public static final String INDEX_VIDEL_LIST = "index:video:list";
}
在service层中引入缓存
package net.xdclass.online_xdclass.service.impl;
import net.xdclass.online_xdclass.config.CacheKeyManager;
import net.xdclass.online_xdclass.model.entity.Video;
import net.xdclass.online_xdclass.model.entity.VideoBanner;
import net.xdclass.online_xdclass.mapper.VideoMapper;
import net.xdclass.online_xdclass.service.VideoService;
import net.xdclass.online_xdclass.utils.BaseCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class VideoServiceImpl implements VideoService {
@Autowired
private VideoMapper videoMapper;
@Autowired
private BaseCache baseCache;
@Override
public List<Video> listVideo() {
try{
Object cacheObj = baseCache.getTenMinuteCache().get(CacheKeyManager.INDEX_VIDEL_LIST,()->{
List<Video> videoList = videoMapper.listVideo();
System.out.println("从数据库中查询");
return videoList;
});
if(cacheObj instanceof List){
List<Video> videoList = (List<Video>)cacheObj;
return videoList;
}
}catch (Exception e){
e.printStackTrace();
}
//可以返回兜底数据,业务系统降级-》SpringCloud专题课程
return null;
}
@Override
public List<VideoBanner> listBanner() {
try{
Object cacheObj = baseCache.getTenMinuteCache().get(CacheKeyManager.INDEX_BANNER_KEY, ()->{
List<VideoBanner> bannerList = videoMapper.listVideoBanner();
System.out.println("从数据库里面找轮播图列表");
return bannerList;
});
if(cacheObj instanceof List){
List<VideoBanner> bannerList = (List<VideoBanner>)cacheObj;
return bannerList;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public Video findDetailById(int videoId) {
// 需要使用mybaits关联复杂查询
Video video = videoMapper.findDetailById(videoId);
return video;
}
}
测试
http://localhost:8081/api/v1/pub/video/list
只有第一次请求 打印 从数据库里面找轮播图列表
之后10分钟内请求直接从缓存中取出
视频详情引入缓存
缓存key管理类
package net.xdclass.online_xdclass.config;
/**
* 缓存key管理类
*/
public class CacheKeyManager {
/**
* 首页轮播图缓存key
*/
public static final String INDEX_BANNER_KEY = "index:banner:list";
/**
* 首页视频列表缓存key
*/
public static final String INDEX_VIDEL_LIST = "index:video:list";
/**
* 视频详情缓存key, %s是视频id
*/
public static final String VIDEO_DETAIL = "video:detail:%s";
}
在service层引入缓存
package net.xdclass.online_xdclass.service.impl;
import net.xdclass.online_xdclass.config.CacheKeyManager;
import net.xdclass.online_xdclass.model.entity.Video;
import net.xdclass.online_xdclass.model.entity.VideoBanner;
import net.xdclass.online_xdclass.mapper.VideoMapper;
import net.xdclass.online_xdclass.service.VideoService;
import net.xdclass.online_xdclass.utils.BaseCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class VideoServiceImpl implements VideoService {
@Autowired
private VideoMapper videoMapper;
@Autowired
private BaseCache baseCache;
@Override
public List<Video> listVideo() {
try{
Object cacheObj = baseCache.getTenMinuteCache().get(CacheKeyManager.INDEX_VIDEL_LIST,()->{
List<Video> videoList = videoMapper.listVideo();
return videoList;
});
if(cacheObj instanceof List){
List<Video> videoList = (List<Video>)cacheObj;
return videoList;
}
}catch (Exception e){
e.printStackTrace();
}
//可以返回兜底数据,业务系统降级-》SpringCloud专题课程
return null;
}
@Override
public List<VideoBanner> listBanner() {
try{
Object cacheObj = baseCache.getTenMinuteCache().get(CacheKeyManager.INDEX_BANNER_KEY, ()->{
List<VideoBanner> bannerList = videoMapper.listVideoBanner();
System.out.println("从数据库里面找轮播图列表");
return bannerList;
});
if(cacheObj instanceof List){
List<VideoBanner> bannerList = (List<VideoBanner>)cacheObj;
return bannerList;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public Video findDetailById(int videoId) {
//单独构建一个缓存key,每个视频的key是不一样的
String videoCacheKey = String.format(CacheKeyManager.VIDEO_DETAIL,videoId);
try{
Object cacheObject = baseCache.getOneHourCache().get( videoCacheKey, ()->{
// 需要使用mybaits关联复杂查询
Video video = videoMapper.findDetailById(videoId);
return video;
});
if(cacheObject instanceof Video){
Video video = (Video)cacheObject;
return video;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
测试(三表关联查询特别耗费性能,明显从缓存中读取性能开销要小的多)
http://localhost:8081/api/v1/pub/video/find_detail_by_id?video_id=42
{
"code": 0,
"data": {
"id": 42,
"title": "全新elementUI项目实战教程Vue整合Echarts后台权限",
"summary": "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_frontend/element/elementui_detail.png",
"price": 5980,
"point": 8.7,
"cover_img": "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_frontend/element/elementui.png",
"create_time": "2019-10-11 06:14:00",
"chapter_list": [
{
"id": 450,
"title": "课程介绍",
"ordered": 1,
"video_id": null,
"create_time": "2019-09-06 06:39:59",
"episode_list": [
{
"id": 11690,
"title": "小滴后台管理系统课程介绍",
"num": 1,
"ordered": 1,
"free": 0,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
}
]
},
{
"id": 451,
"title": "Vue全家桶各部分核⼼知识详解",
"ordered": 2,
"video_id": null,
"create_time": "2019-09-06 06:39:59",
"episode_list": [
{
"id": 11691,
"title": "构建vue项目的利器—脚手架vue-cli3详解",
"num": 2,
"ordered": 1,
"free": 0,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11692,
"title": "vue中组件间传值常用的几种方式(上)",
"num": 3,
"ordered": 2,
"free": 0,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11693,
"title": "vue中组件间传值常用的几种方式(下)",
"num": 4,
"ordered": 3,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11694,
"title": "玩转单页面应用的控制中心—vue-router",
"num": 5,
"ordered": 4,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11695,
"title": "状态管理中心—vuex的基础用法",
"num": 6,
"ordered": 5,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11696,
"title": "状态管理中心—vuex的高级用法",
"num": 7,
"ordered": 6,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
}
]
},
{
"id": 452,
"title": "Element常用组件详解",
"ordered": 3,
"video_id": null,
"create_time": "2019-09-06 06:39:59",
"episode_list": [
{
"id": 11697,
"title": "Element常用组件布局组件详解",
"num": 8,
"ordered": 1,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11698,
"title": "Element常用组件之弹出类型组件详解",
"num": 9,
"ordered": 2,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11699,
"title": "Element常用组件—表格组件详解",
"num": 10,
"ordered": 3,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11700,
"title": "Element常用组件—表单组件详解",
"num": 11,
"ordered": 4,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
}
]
},
{
"id": 453,
"title": "实战项⽬之环境准备及配置改装",
"ordered": 4,
"video_id": null,
"create_time": "2019-09-06 06:39:59",
"episode_list": [
{
"id": 11701,
"title": "项目搭建及技术选型",
"num": 12,
"ordered": 1,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11702,
"title": "配置项目的基本环境及项目目录结构总体介绍",
"num": 13,
"ordered": 2,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
}
]
},
{
"id": 454,
"title": "⼩滴课堂后台视频管理系统之公用部分开发",
"ordered": 5,
"video_id": null,
"create_time": "2019-09-06 06:39:59",
"episode_list": [
{
"id": 11703,
"title": "需求分析及模块划分",
"num": 14,
"ordered": 1,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11704,
"title": "路由设计及左侧公用导航菜单开发",
"num": 15,
"ordered": 2,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11705,
"title": "顶部导航菜单及与左侧导航联动的面包屑实现(上)",
"num": 16,
"ordered": 3,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11706,
"title": "顶部导航菜单及与左侧导航联动的面包屑实现(下)",
"num": 17,
"ordered": 4,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11707,
"title": "使用vuex实现切换tab页功能",
"num": 18,
"ordered": 5,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11708,
"title": "构建页面组件,连通公共组件",
"num": 19,
"ordered": 6,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11709,
"title": "页面布局整体样式优化",
"num": 20,
"ordered": 7,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
}
]
},
{
"id": 455,
"title": "⼩D课堂后台视频管理系统之⾸页开发",
"ordered": 6,
"video_id": null,
"create_time": "2019-09-06 06:39:59",
"episode_list": [
{
"id": 11710,
"title": "介绍mock.js及axios全局配置",
"num": 21,
"ordered": 1,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11711,
"title": "使用Mock随机返回主页数据",
"num": 22,
"ordered": 2,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11712,
"title": "使用element布局组件实现首页布局",
"num": 23,
"ordered": 3,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11713,
"title": "完成首页除图表外的内容",
"num": 24,
"ordered": 4,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11714,
"title": "完成首页table部分及ECharts介绍",
"num": 25,
"ordered": 5,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11715,
"title": "谈谈封装一个EChart组件的一些想法",
"num": 26,
"ordered": 6,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11716,
"title": "上手封装一个EChart组件并处理数据展示图表",
"num": 27,
"ordered": 7,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11717,
"title": "修改EChart组件样式及自适应图表(上)",
"num": 28,
"ordered": 8,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11718,
"title": "修改EChart组件样式自适应图表(下)",
"num": 29,
"ordered": 9,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11719,
"title": "Echart组件封装总结",
"num": 30,
"ordered": 10,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
}
]
},
{
"id": 456,
"title": "用户管理页及详解权限管理",
"ordered": 7,
"video_id": null,
"create_time": "2019-09-06 06:39:59",
"episode_list": [
{
"id": 11720,
"title": "用户管理页介绍及页面实现思路讲解",
"num": 31,
"ordered": 1,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11721,
"title": "更完善的表单组件封装及思路讲解",
"num": 32,
"ordered": 2,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11722,
"title": "通用表格组件封装及思路讲解",
"num": 33,
"ordered": 3,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11723,
"title": "完成表格组件的封装",
"num": 34,
"ordered": 4,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11724,
"title": "用户管理页页面功能实现(上)",
"num": 35,
"ordered": 5,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11725,
"title": "用户管理页页面功能实现(下)",
"num": 36,
"ordered": 6,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11726,
"title": "企业开发之权限管理思路讲解",
"num": 37,
"ordered": 7,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11727,
"title": "权限管理之动态返回菜单的实现",
"num": 38,
"ordered": 8,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
},
{
"id": 11728,
"title": "权限管理之路由守卫判断用户登录状态",
"num": 39,
"ordered": 9,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
}
]
},
{
"id": 457,
"title": "项⽬总结",
"ordered": 8,
"video_id": null,
"create_time": "2019-09-06 06:39:59",
"episode_list": [
{
"id": 11729,
"title": "小滴后台管理系统项目总结",
"num": 40,
"ordered": 1,
"free": 1,
"play_url": "xdclass.net/aaa.mp4",
"chapter_id": null,
"video_id": null,
"create_time": null
}
]
}
]
},
"msg": null
}
Jemeter压测
聚合报告参数详解
lable: sampler的名称
Samples: 一共发出去多少请求,例如10个用户,循环10次,则是 100
Average: 平均响应时间
Median: 中位数,也就是 50% 用户的响应时间
90% Line : 90% 用户的响应不会超过该时间 (90% of the samples took no more than this time. The remaining samples at least as long as this)
95% Line : 95% 用户的响应不会超过该时间
99% Line : 99% 用户的响应不会超过该时间
min : 最小响应时间
max : 最大响应时间
Error%:错误的请求的数量/请求的总数
Throughput: 吞吐量——默认情况下表示每秒完成的请求数(Request per Second) 可类比为qps、tps
KB/Sec: 每秒接收数据量
压测参数: 300 5 2000
QPS 压测qps结果为: Throughput: 7563/sec
http 127.0.0.1 8081 /api/v1/pub/video/list_banner
取消缓存 再进行测试 Throughput: 4060/sec