高性能本地缓存-caffeine

高性能本地缓存-caffeine

Scroll Down

高性能本地缓存-caffeine

简介

1、Caffeine 是基于 JAVA 8 的高性能缓存库。并且在 spring5 (springboot 2.x) 后,spring 官方放弃了 Guava,而使用了性能更优秀的 Caffeine 作为默认缓存组件
2、支持异步加载,事件提交队列
3、内部使用W-TinyLFU算法,它的命中率非常高,内存占用更加的小
4、一般在redis之后,做二级缓存
     

优势

性能比较:
8个线程同时从缓存中读取
image.png
8个线程同时从缓存中写入
image.png
6个线程读取,2个线程写入
image.png

使用场景

1、二级缓存

原生使用方式

填充策略

1、手动加载

// 初始化
public static void main(String[] args) {
    Cache<String, Object> cache = Caffeine.newBuilder()
            .expireAfterWrite(1, TimeUnit.SECONDS)
            .expireAfterAccess(1, TimeUnit.SECONDS)
            .maximumSize(10)
            .build();
    String key = "testKey";
    //如果一个key不存在,那么会进入指定的函数生成value
    Object value = cache.get(key, t -> setValue(key).apply(key));
    cache.put("hello",value);
    //判断是否存在如果不存返回null
    Object ifPresent = cache.getIfPresent(key);
    System.out.println(ifPresent);
    //移除一个key
    cache.invalidate(key);
}

public static Function<String, Object> setValue(String key){
    return t -> key + "value";
}

2、同步加载

构造Cache时候,build方法传入一个CacheLoader实现类。实现load方法,通过key加载value

public static void main(String[] args) {
    String key = "key";
    LoadingCache<String, Object> cache = Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .build(k -> setValue(key).apply(key));
    System.out.println(cache.get(key));
}

public static Function<String, Object> setValue(String key){
    return t -> key + "value";
}

3、异步加载

AsyncLoadingCache是继承自LoadingCache类的,异步加载使用Executor去调用方法并返回一个CompletableFuture。异步加载缓存使用了响应式编程模型

public static void main(String[] args) {
    String key = "key";
    AsyncLoadingCache<String, Object> cache = Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .buildAsync(k -> setValue(key).get());
    // 异步获取
    CompletableFuture<Object> graph = cache.get(key);
    graph.thenAccept(s -> {
        System.out.println(s);
    });
}

public static CompletableFuture<Object> setValue(String key){
    return CompletableFuture.supplyAsync(() -> {
        return key + "value";
    });
}

springboot集成springboot-cache使用

1、添加jar依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

2、配置@EnableCaching注解

@EnableCaching
@Configuration
public class LocalCacheConfig {

    @Bean("caffeineCacheManager")
    public CacheManager cacheManager(){
        	CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).initialCapacity(100).maximumSize(5000));
        return caffeineCacheManager;
    }
}

3、使用

@Slf4j
@Service
@CacheConfig(cacheNames = "caffeineCacheManager")
public class SafetyInspectionTrackServiceImpl implements SafetyInspectionTrackService {

    @Autowired
    private SafetyInspectionTrackMapper safetyInspectionTrackMapper;

    @Override
    @Cacheable(key = "#id")
    public SafetyInspectionTrackDO getById(Long id) {
        log.info("进入缓存:{}",id);
        return safetyInspectionTrackMapper.selectById(id);
    }

    @Override
    @CacheEvict
    public void clear(Long id){
        log.info("清除缓存");
    }
}

@Cacheable:

配置在 getUsersByName方法上表示其返回值将被加入缓存。同时在查询时,会先从缓存中
获取,若不存在才再发起对数据库的访问
@CachePut:
配置于方法上时,能够根据参数定义条件来进行缓存,其与 @Cacheable不同的是使用 
@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都
会执行该方法并将执行结果以键值对的形式存入指定的缓存中,所以主要用于数据新增和修改
操作上

@CacheEvict:

配置于方法上时,表示从缓存中移除相应数据

Caffeine常用配置说明

1、初始化参数

initialCapacity: 初始的缓存空间大小
maximumSize: 缓存的最大条数
maximumWeight: 缓存的最大权重
expireAfterAccess: 最后一次写入或访问后经过固定时间过期
expireAfterWrite: 最后一次写入后经过固定时间过期
refreshAfterWrite: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
weakKeys: 打开key的弱引用
weakValues:打开value的弱引用
softValues:打开value的软引用
recordStats:开发统计功能

注意:
expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。
maximumSize和maximumWeight不可以同时使用
weakValues和softValues不可以同时使用

2、自动刷新

refreshAfterWrite就是设置写入后多就会刷新,expireAfterWrite和refreshAfterWrite
的区别是,当缓存过期后,配置了expireAfterWrite,则调用时会阻塞,等待缓存计算完成,返
回新的值并进行缓存,refreshAfterWrite则是返回一个旧值,并异步计算新值并缓存

3、回收策略

(1)基于大小

maximumSize 初始给定缓存大小,超过设置的值后,自动回收

(2)基于时间

expireAfterAccess:在最后一次访问或者写入后开始计时
expireAfterWrite:在最后一次写入缓存后开始计时
expireAfte:自定义策略

(3)基于引用

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

淘汰策略算法

1、FIFO:先进先出

在这种淘汰算法中,先进入缓存的会先被淘汰,会导致命中率很低。

2、LRU:最近最少使用算法

每次访问数据都会将其放在我们的队尾,如果需要淘汰数据,就只需要淘汰队首即可。仍然有个问题,如果有个数据在 1 分钟访问了 1000次,再后 1 分钟没有访问这个数据,但是有其他的数据访问,就导致了我们这个热点数据被淘汰。

3、LFU:最近最少频率使用

利用额外的空间记录每个数据的使用频率,然后选出频率最低进行淘汰。这样就避免了 LRU 不能处理时间段的问题。

4、W-tinyLFU算法

详细介绍 https://www.cnblogs.com/liujinhua306/p/9808500.html

源码简析

1、在Caffeine中有个LocalCacheFactory类,他会根据你的配置进行具体Cache的创建。
image.png
2、在Caffeine中有个scheduleDrainBuffers方法,用来进行我们的过期任务的调度
image.png

常见问题(Faq)

1、固定数据(Pinning Entries)

固定数据是不能通过驱逐策略去将数据删除的。当数据是一个有状态的资源时(如锁)

个人博客地址