springboot 集成caffeine单体缓存两种方式及算法简介 (注解/手动)

1.简介

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

       Caffeine 因为使用了 Window-TinyLFU 缓存淘汰策略,提供了一个近乎最佳的命中率。综合了 LRU 和 LFU 算法的长处,使其成为本地缓存之王。

        Least Recently Used:如果数据最近被访问过,将来被访问的概率也更高。每次访问就把这个元素放到队列的头部,队列满了就淘汰队列尾部的数据,即淘汰最长时间没有被访问的。缺点是,如果某一时刻大量数据到来,很容易将热点数据挤出缓存,留下来的很可能是只访问一次,今后不会再访问的或频率极低的数据。比如、微博爆出某明星糗事就是一个突发性热点事件。当事件结束后,可能没有啥访问量了,但是由于其极高的访问频率,导致其在未来很长一段时间内都不会被淘汰掉。

        Least Frequently Used:如果数据最近被访问过,那么将来被访问的概率也更高。也就是淘汰一定时间内被访问次数最少的数据(时间局部性原理)需要用 Queue 来保存访问记录,可以用 LinkedHashMap 来简单实现一个基于 LRU 算法的缓存。优点是,避免了 LRU 的缺点,因为根据频率淘汰,不会出现大量进来的挤压掉老的,如果在数据的访问的模式不随时间变化时候,LFU 能够提供绝佳的命中率。其缺点是,偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。

       W-TinyLFU 是 Caffeine 提出的一种全新算法,它可以解决频率统计不准确以及访问频率衰减的问题。这个方法让我们从空间、效率、以及适配举证的长宽引起的哈希碰撞的错误率上做均衡。在 W-TinyLFU 算法中,将整个缓存区划分为两大区域:Window Cache 和 Main Cache 。Window Cache 是一个标准的 LRU 缓存,只占整个缓存内存空间大小的 1% ; Main Cache 则是一个 SLRU (Segmented LRU) ,占整个缓存内存空间大小的 99% ,是缓存的主要区域。里面进一步被划分成两个区域:Probation Cache 观察区和 Protected Cache 保护区。Probation 观察区占 Main Cache 大小的 20%;而 Protected 保护区占 Main Cache 大小的 80% ,是 Main Cache 的主要区域。

2.caffeine相关配置说明

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

Caffeine提供三种数据驱逐策略:基于大小驱逐、基于时间驱逐、基于引用驱逐

  1. maximumSize 和 maximumWeight 不可以同时使用。
  2. expireAfterWrite 和 expireAfterAccess 同时存在时,以 expireAfterWrite 为准
  3. weakValues 和 softValues 不可以同时使用。

Java中四种引用类型:

引用类型被垃圾回收时间用途生存时间
强引用 Strong Reference从来不会对象的一般状态JVM停止运行时终止
软引用 Soft Reference在内存不足时对象缓存内存不足时终止
弱引用 Weak Reference在垃圾回收时对象缓存gc运行后终止
虚引用 Phantom Reference从来不会可以用虚引用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知JVM停止运行时终止

3.springboot集成caffeine

  1. 直接引入 Caffeine 依赖,然后创建 Caffeine 方法实现缓存。
  2. 引入 Caffeine 和 SpringCache 依赖,使用 SpringCache 注解方法实现缓存。(推荐)
  3. 引入jar包 版本也可根据不写 根据springboot版本走
        <!-- caffeine本地缓存 -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.8.5</version>
        </dependency>
        <!--  对于@EnableCaching注解开启-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>
  • @Cacheable:先查询缓存是否存在,如果存在,则返回结果;如果不存在,则执行方法,并将结果保存在缓存中。主要用于查询操作
  • @CachePut:不检查缓存中是否存在,直接执行方法,并将结果保存在缓存中。主要用于数据新增和修改操作
  • @CacheEvict:从缓存中移除指定的key数据,如果allEntries属性为true,则清除全部;如果beforeInvocation属性为true,则在方法执行前删除。主要用于删除操作

3.1 方式一 只用Caffeine


/**
 * caffeine缓存配置 
 * - 优点:可以针对每个cache配置不同的参数,比如过期时长、最大容量(定制化配置) 
 * 可定义多个
 **/
@Configuration
public class CaffeineConfig {
    @Bean
    public Cache<Long, ItemStock> userDtoCache() {
        // 构建cache对象
        return Caffeine.newBuilder()
                //0) 驱逐策略:基于容量,时间,引用。
                //0.1 基于时间
                .expireAfterWrite(10, TimeUnit.MINUTES)
                //0.2.1 基于容量
                //初始容量
                .initialCapacity(100)
                .maximumSize(10_000)
//                //0.2.2 权重
//                .weigher(((key, value) -> {
//                    if (key.equals(1)) {
//                        return 1;
//                    } else {
//                        return 2;
//                    }
//                }))
                //0.3 基于引用
                //0.3.1 当进行GC的时候进行驱逐
//                .softValues()
                //0.3.2 当key和缓存元素都不再存在其他强引用的时候驱逐
//                .weakKeys()
//                .weakValues()
                .build();
    }

测试类

@SpringBootTest(classes = CaffeineApplication.class)
@RunWith(SpringRunner.class)
public class CaffeineTest{

    @Autowired
    private Cache<Long, UserDto> userDtoCache;
    

    @Test
    public void test1() {
        //1) 新增
        // 添加或者更新一个缓存元素
        userDtoCache.put(1L, UserDto.builder()
                .id(1L)
                .name("迪丽热巴")
                .age(18)
                .build());

        // 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回null
        UserDto user2 = userDtoCache.get(2L, key -> {
            // 根据key去数据库查询数据
            return UserDto.builder()
                    .id(key)
                    .name("柳岩")
                    .age(24)
                    .build();
        });


        //2) 查询
        // 取数据,上面的get也算
        UserDto user1 = userDtoCache.getIfPresent(1L);

        System.out.println("user1 = " + user1);
        System.out.println("user2 = " + user2);

        //3) 删除
        // 移除一个缓存元素
        userDtoCache.invalidate(1L);
        user1 = userDtoCache.getIfPresent(1L);
        System.out.println("user1 = " + user1);

        // 批量失效key
        userDtoCache.invalidateAll(Arrays.asList(1L, 2L));
        // 失效所有的key
        userDtoCache.invalidateAll();

        System.out.println("defaultGF2 = " + userDtoCache.getIfPresent("defaultGF"));
    }

3.2 加入SpringCache 通过注解方式

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

}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

  @Autowired
  private UserMapper userMapper;


  @Override
  @Cacheable(key = "#id" , value = "caffeineCacheManager")
  public User selectById(Long id) {
    User user = userMapper.selectById(id);
    return user;
  }

  @Override
  @CachePut(key = "#result.id" , value = "caffeineCacheManager")
  public User addUserInfo(UserDto userDto) {
    User user = new User();
    BeanUtils.copyProperties(userDto, user);
    int insert = userMapper.insert(user);
    return user;
  }

  @Override
  @CachePut(key = "#userDto.id" , value = "caffeineCacheManager")
  public User updateUserInfo(UserDto userDto) {
    User user = new User();
    BeanUtils.copyProperties(userDto, user);
    userMapper.updateById(user);
    return user;
  }

  @Override
  @CacheEvict(key = "#id" , value = "caffeineCacheManager")
  public void deleteById(Long id) {
    UpdateWrapper updateWrapper = new UpdateWrapper();
    updateWrapper.eq("id", id);
    userMapper.delete(updateWrapper);
  }
}

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值