本地缓存

百度百科:本地缓存是指将客户机本地的物理内存划分出一部分空间用来缓冲客户机回写到服务器的数据,因其在回写上的突出贡献,因此本地缓存一般称为本地回写。本地缓存概念首次出现是在无盘领域,作为PXD无盘的一项核心技术被广泛应用。
作用:该技术将客户机回写的数据不再先写入服务器硬盘,而是将回写数据先写入本地回写缓存,当缓存空间达到一定的阈值时,再将数据回写到服务器。有了本地回写缓存功能之后,可大大降低服务器读写压力和网络负载。

个人理解:本地缓存既是将数据缓存到单台服务的jvm的内存中,分布式集群项目由于多台服务器构成,本地缓存只能保证单台服务器数据的缓存,有局限性,无法共享缓存数据,但查询效率高,适合做一级缓存,配合redis(分布式缓存,以后出文章介绍)构成多级缓存框架。

为什么用Caffeine做本地缓存

官网介绍:https://github.com/ben-manes/caffeine/wiki

Caffeine是基于Java 8的高性能缓存库,可提供接近最佳的命中率。

缓存与ConcurrentMap相似,但并不完全相同。最根本的区别是ConcurrentMap会保留添加到其中的所有元素,直到将其明确删除为止。Cache另一方面,通常将A配置为自动退出条目,以限制其内存占用量。在某些情况下,LoadingCache或者AsyncLoadingCache如果它不逐出条目,由于其自动加载缓存可能是有用的,甚至。

咖啡因提供了灵活的构造来创建具有以下功能组合的缓存:

  • 自动将条目自动加载到缓存中,可以选择异步加载
  • 基于频率和新近度超过最大值时基于大小的逐出
  • 自上次访问或上次写入以来测得的基于时间的条目到期
  • 发生第一个陈旧的条目请求时,异步刷新
  • 键自动包装在弱引用中
  • 值自动包装在弱引用或软引用中
  • 逐出(或以其他方式删除)条目的通知
  • 写入传播到外部资源
  • 缓存访问统计信息的累积

SpringBoot2.0+如何集成 Caffeine

因为SpringBoot2.0+默认使用的本地缓存就是caffeine,所及集成起来极其方便。

引入依赖

<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.6.2</version>
</dependency>

开启缓存

启动类添加 @EnableCaching注解,开启缓存使用

@SpringBootApplication
@EnableCaching

容器配置

如果使用了多个cahce,比如redis、caffeine等,必须指定某一个CacheManage为@primary

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.assertj.core.util.Lists;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @Author: Wxy
 * @Date: 2020/11/7 16:56
 * @Description
 */
@Configuration
public class CaffeineConfig {

    /**
     * 创建基于Caffeine的Cache Manager
     *
     * @return
     */
    @Bean
    @Primary
    public CacheManager caffeineCacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        ArrayList<CaffeineCache> caches = Lists.newArrayList();
        Map<String, Object> map = getCacheType();
        for (String name : map.keySet()) {
            caches.add(new CaffeineCache(name, (Cache<Object, Object>) map.get(name)));
        }
        cacheManager.setCaches(caches);
        return cacheManager;
    }

    /**
     * 初始化自定义缓存策略
     *
     * @return
     */
    private static Map<String, Object> getCacheType() {
        Map<String, Object> map = new ConcurrentHashMap<>();
        map.put("name1", Caffeine.newBuilder().recordStats()
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .maximumSize(100)
                .build());
        map.put("name2", Caffeine.newBuilder().recordStats()
                .expireAfterWrite(50, TimeUnit.SECONDS)
                .maximumSize(50)
                .build());
        return map;
    }
}

驱逐策略

基于大小的回收策略有两种方式:基于缓存大小,基于权重,基于时间。
maximumSize : 根据缓存的计数进行驱逐 同一缓存策略 缓存的数据量,以访问先后顺序,以最大100为例,超出100驱逐最晚访问的数据缓存。
maximumWeight : 根据缓存的权重来进行驱逐(权重只是用于确定缓存大小,不会用于决定该缓存是否被驱逐)。
maximumWeight与maximumSize不可以同时使用。

Caffeine提供了三种定时驱逐策略:

  • expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。
  • expireAfterWrite(long, TimeUnit): 在最后一次写入缓存后开始计时,在指定的时间后过期。
  • expireAfter(Expiry): 自定义策略,过期时间由Expiry实现独自计算
    缓存的删除策略使用的是惰性删除和定时删除。这两个删除策略的时间复杂度都是O(1)

开发使用

主要基于Spring缓存注解@Cacheable、@CacheEvict、@CachePut的方式使用

  • @Cacheable :改注解修饰的方法,若不存在缓存,则执行方法并将结果写入缓存;若存在缓存,则不执行方法,直接返回缓存结果。
  • @CachePut :执行方法,更新缓存;该注解下的方法始终会被执行
  • @CacheEvict :删除缓存
  • @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
  • @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)

注意 :@Cacheable 默认使用标@primary 注释的CacheManage

    /**
     * 先查缓存,如果查不到,执行方法体并将结果写入缓存,若查到,不执行方法体,直接返回缓存结果
     * @param id
     */
    @Cacheable(value = "name1", key = "#id", sync = true)
    public void getUser(long id){
        //TODO 查找数据库
    }

    /**
     * 更新缓存,每次都会执行方法体
     * @param user
     */
    @CachePut(value = "name1", key = "#user.id")
    public void saveUser(User user){
        //todo 保存数据库
    }

    /**
     * 删除
     * @param user
     */
    @CacheEvict(value = "name1",key = "#user.id")
    public void delUser(User user){
        //todo 保存数据库
    }

参考博客

https://blog.csdn.net/xiaolyuh123/article/details/78794012
https://www.cnblogs.com/rickiyang/p/11074158.html

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐