一,為什么要使用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)
