2021年1月20日星期三

Spring Boot 2.x 把 Guava 干掉了,选择本地缓存之王 Caffeine!

作者:超级小豆丁
来源 版本:1.8

  • Caffeine 版本:2.8.0
  • SpringBoot 版本:2.2.2.RELEASE
  • 一、本地缓存介绍

    缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。

    之前介绍过 Redis 这种 NoSql 作为缓存组件,它能够很好的作为分布式缓存组件提供多个服务间的缓存,但是 Redis 这种还是需要网络开销,增加时耗。本地缓存是直接从本地内存中读取,没有网络开销,例如秒杀系统或者数据量小的缓存等,比远程缓存更合适。

    二、缓存组件 Caffeine 介绍

    按 Caffeine Github 文档描述,Caffeine 是基于 JAVA 8 的高性能缓存库。并且在 spring5 (springboot 2.x) 后,spring 官方放弃了 Guava,而使用了性能更优秀的 Caffeine 作为默认缓存组件。

    1、Caffeine 性能

    可以通过下图观测到,在下面缓存组件中 Caffeine 性能是其中最好的。

    2、Caffeine 配置说明

    参数类型描述
    initialCapacityinteger初始的缓存空间大小
    maximumSizelong缓存的最大条数
    maximumWeightlong缓存的最大权重
    expireAfterAccessduration最后一次写入或访问后经过固定时间过期
    refreshAfterWriteduration最后一次写入后经过固定时间过期
    refreshAfterWriteduration创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
    weakKeysboolean打开 key 的弱引用
    weakValuesboolean打开 value 的弱引用
    softValuesboolean打开 value 的软引用
    recordStats-开发统计功能

    注意:

    • weakValuessoftValues 不可以同时使用。
    • maximumSizemaximumWeight 不可以同时使用。
    • expireAfterWriteexpireAfterAccess 同事存在时,以 expireAfterWrite 为准。

    3、软引用与弱引用

    • 软引用: 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
    • 弱引用: 弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
    // 软引用Caffeine.newBuilder().softValues().build();// 弱引用Caffeine.newBuilder().weakKeys().weakValues().build();

    三、SpringBoot 集成 Caffeine 两种方式

    SpringBoot 有俩种使用 Caffeine 作为缓存的方式:

    方式一: 直接引入 Caffeine 依赖,然后使用 Caffeine 方法实现缓存。

    方式二: 引入 Caffeine 和 Spring Cache 依赖,使用 SpringCache 注解方法实现缓存。

    下面将介绍下,这俩中集成方式都是如何实现的。

    Spring Boot 基础就不介绍了,推荐看下这个教程:

    https://github.com/javastacks/spring-boot-best-practice

    四、SpringBoot 集成 Caffeine 方式一

    1、Maven 引入相关依赖

    <?

    2、配置缓存配置类

    import com.github.benmanes.caffeine.cache.Cache;import com.github.benmanes.caffeine.cache.Caffeine;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@Configurationpublic class CacheConfig { @Bean public Cache<String, Object> caffeineCache() {  return Caffeine.newBuilder()    // 设置最后一次写入或访问后经过固定时间过期    .expireAfterWrite(60, TimeUnit.SECONDS)    // 初始的缓存空间大小    .initialCapacity(100)    // 缓存的最大条数    .maximumSize(1000)    .build(); }}

    3、定义测试的实体对象

    import lombok.Data;import lombok.ToString;@Data@ToStringpublic class UserInfo { private Integer id; private String name; private String sex; private Integer age;}

    4、定义服务接口类和实现类

    UserInfoService

    import mydlq.club.example.entity.UserInfo;public interface UserInfoService { /**  * 增加用户信息  *  * @param userInfo 用户信息  */ void addUserInfo(UserInfo userInfo); /**  * 获取用户信息  *  * @param id 用户ID  * @return 用户信息  */ UserInfo getByName(Integer id); /**  * 修改用户信息  *  * @param userInfo 用户信息  * @return 用户信息  */ UserInfo updateUserInfo(UserInfo userInfo); /**  * 删除用户信息  *  * @param id 用户ID  */ void deleteById(Integer id);}

    UserInfoServiceImpl

    import com.github.benmanes.caffeine.cache.Cache;import lombok.extern.slf4j.Slf4j;import mydlq.club.example.entity.UserInfo;import mydlq.club.example.service.UserInfoService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;import java.util.HashMap;@Slf4j@Servicepublic class UserInfoServiceImpl implements UserInfoService { /**  * 模拟数据库存储数据  */ private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>(); @Autowired Cache<String, Object> caffeineCache; @Override public void addUserInfo(UserInfo userInfo) {  log.info("create");  userInfoMap.put(userInfo.getId(), userInfo);  // 加入缓存  caffeineCache.put(String.valueOf(userInfo.getId()),userInfo); } @Override public UserInfo getByName(Integer id) {  // 先从缓存读取  caffeineCache.getIfPresent(id);  UserInfo userInfo = (UserInfo) caffeineCache.asMap().get(String.valueOf(id));  if (userInfo != null){   return userInfo;  }  // 如果缓存中不存在,则从库中查找  log.info("get");  userInfo = userInfoMap.get(id);  // 如果用户信息不为空,则加入缓存  if (userInfo != null){   caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);  }  return userInfo; } @Override public UserInfo updateUserInfo(UserInfo userInfo) {  log.info("update");  if (!userInfoMap.containsKey(userInfo.getId())) {   return null;  }  // 取旧的值  UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());  // 替换内容  if (!StringUtils.isEmpty(oldUserInfo.getAge())) {   oldUserInfo.setAge(userInfo.getAge());  }  if (!StringUtils.isEmpty(oldUserInfo.getName())) {   oldUserInfo.setName(userInfo.getName());  }  if (!StringUtils.isEmpty(oldUserInfo.getSex())) {   oldUserInfo.setSex(userInfo.getSex());  }  // 将新的对象存储,更新旧对象信息  userInfoMap.put(oldUserInfo.getId(), oldUserInfo);  // 替换缓存中的值  caffeineCache.put(String.valueOf(oldUserInfo.getId()),oldUserInfo);  return oldUserInfo; } @Override public void deleteById(Integer id) {  log.info("delete");  userInfoMap.remove(id);  // 从缓存中删除  caffeineCache.asMap().remove(String.valueOf(id)); }}

    5、测试的 Controller 类

    import mydlq.club.example.entity.UserInfo;import mydlq.club.example.service.UserInfoService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@RestController@RequestMappingpublic class UserInfoController { @Autowired private UserInfoService userInfoService; @GetMapping("/userInfo/{id}") public Object getUserInfo(@PathVariable Integer id) {  UserInfo userInfo = userInfoService.getByName(id);  if (userInfo == null) {   return "没有该用户";  }  return userInfo; } @PostMapping("/userInfo") public Object createUserInfo(@RequestBody UserInfo userInfo) {  userInfoService.addUserInfo(userInfo);  return "SUCCESS"; } @PutMapping("/userInfo") public Object updateUserInfo(@RequestBody UserInfo userInfo) {  UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);  if (newUserInfo == null){   return "不存在该用户";  }  return newUserInfo; } @DeleteMapping("/userInfo/{id}") public Object deleteUserInfo(@PathVariable Integer id) {  userInfoService.deleteById(id);  return "SUCCESS"; }}

    五、SpringBoot 集成 Caffeine 方式二

    1、Maven 引入相关依赖

    <?

    2、配置缓存配置类

    @Configurationpublic class CacheConfig { /**  * 配置缓存管理器  *  * @return 缓存管理器  */ @Bean("caffeineCacheManager") public CacheManager cacheManager() {  CaffeineCacheManager cacheManager = new CaffeineCacheManager();  cacheManager.setCaffeine(Caffeine.newBuilder()    // 设置最后一次写入或访问后经过固定时间过期    .expireAfterAccess(60, TimeUnit.SECONDS)    // 初始的缓存空间大小    .initialCapacity(100)    // 缓存的最大条数    .maximumSize(1000));  return cacheManager; }}

    3、定义测试的实体对象

    @Data@ToStringpublic class UserInfo { private Integer id; private String name; private String sex; private Integer age;}

    4、定义服务接口类和实现类

    服务接口

    import mydlq.club.example.entity.UserInfo;public interface UserInfoService { /**  * 增加用户信息  *  * @param userInfo 用户信息  */ void addUserInfo(UserInfo userInfo); /**  * 获取用户信息  *  * @param id 用户ID  * @return 用户信息  */ UserInfo getByName(Integer id); /**  * 修改用户信息  *  * @param userInfo 用户信息  * @return 用户信息  */ UserInfo updateUserInfo(UserInfo userInfo); /**  * 删除用户信息  *  * @param id 用户ID  */ void deleteById(Integer id);}

    服务实现类

    import lombok.extern.slf4j.Slf4j;import mydlq.club.example.entity.UserInfo;import mydlq.club.example.service.UserInfoService;import org.springframework.cache.annotation.CacheConfig;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;import java.util.HashMap;@Slf4j@Service@CacheConfig(cacheNames = "caffeineCacheManager")public class UserInfoServiceImpl implements UserInfoService { /**  * 模拟数据库存储数据  */ private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>(); @Override @CachePut(key = "#userInfo.id") public void addUserInfo(UserInfo userInfo) {  log.info("create");  userInfoMap.put(userInfo.getId(), userInfo); } @Override @Cacheable(key = "#id") public UserInfo getByName(Integer id) {  log.info("get");  return userInfoMap.get(id); } @Override @CachePut(key = "#userInfo.id") public UserInfo updateUserInfo(UserInfo userInfo) {  log.info("update");  if (!userInfoMap.containsKey(userInfo.getId())) {   return null;  }  // 取旧的值  UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());  // 替换内容  if (!StringUtils.isEmpty(oldUserInfo.getAge())) {   oldUserInfo.setAge(userInfo.getAge());  }  if (!StringUtils.isEmpty(oldUserInfo.getName())) {   oldUserInfo.setName(userInfo.getName());  }  if (!StringUtils.isEmpty(oldUserInfo.getSex())) {   oldUserInfo.setSex(userInfo.getSex());  }  // 将新的对象存储,更新旧对象信息  userInfoMap.put(oldUserInfo.getId(), oldUserInfo);  // 返回新对象信息  return oldUserInfo; } @Override @CacheEvict(key = "#id") public void deleteById(Integer id) {  log.info("delete");  userInfoMap.remove(id); }}

    5、测试的 Controller 类

    import mydlq.club.example.entity.UserInfo;import mydlq.club.example.service.UserInfoService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@RestController@RequestMappingpublic class UserInfoController { @Autowired private UserInfoService userInfoService; @GetMapping("/userInfo/{id}") public Object getUserInfo(@PathVariable Integer id) {  UserInfo userInfo = userInfoService.getByName(id);  if (userInfo == null) {   return "没有该用户";  }  return userInfo; } @PostMapping("/userInfo") public Object createUserInfo(@RequestBody UserInfo userInfo) {  userInfoService.addUserInfo(userInfo);  return "SUCCESS"; } @PutMapping("/userInfo") public Object updateUserInfo(@RequestBody UserInfo userInfo) {  UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);  if (newUserInfo == null){   return "不存在该用户";  }  return newUserInfo; } @DeleteMapping("/userInfo/{id}") public Object deleteUserInfo(@PathVariable Integer id) {  userInfoService.deleteById(id);  return "SUCCESS"; }}

    参考地址:

    https://www.jianshu.com/p/c72fb0c787fc
    https://www.cnblogs.com/rickiyang/p/11074158.html
    https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-caffeine-cache-example

    近期热文推荐:

    1.Java 15 正式发布, 14 个新特性,刷新你的认知!!

    2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!

    3.我用 Java 8 写了一段逻辑,同事直呼看不懂,你试试看。。

    4.吊打 Tomcat ,Undertow 性能很炸!!

    5.《Java开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!









    原文转载:http://www.shaoqun.com/a/512309.html

    跨境电商:https://www.ikjzd.com/

    tinypic:https://www.ikjzd.com/w/114

    lithium:https://www.ikjzd.com/w/2505


    作者:超级小豆丁来源版本:1.8Caffeine版本:2.8.0SpringBoot版本:2.2.2.RELEASE一、本地缓存介绍缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。之前介绍过Redis这种NoSql作为缓存组件,它能够很好的作为分布式缓存组件提供多个服务间的缓存,但是Redis这种还是需要网络开销,增加时
    promoted:promoted
    首信易支付:首信易支付
    泰国有哪些免税店?具体地址在哪里?:泰国有哪些免税店?具体地址在哪里?
    亚马逊产品表现不好?看这篇就够了!:亚马逊产品表现不好?看这篇就够了!
    巽寮湾特产-----扒皮鱼干 - :巽寮湾特产-----扒皮鱼干 -

    没有评论:

    发表评论