Caching - Caffeine doesn't exactly point north

insert image description here


official website

https://github.com/ben-manes/caffeine

wiki:

https://github.com/ben-manes/caffeine/wiki

insert image description here


overview

Caffeine is a high-performance caching framework for Java applications. It provides a powerful and easy-to-use caching library that can be used in applications to improve the speed and efficiency of data access.

Here are some key features of the Caffeine caching framework:

  1. High performance: One of the design goals of Caffeine is to provide excellent performance. It achieves fast cache access by using efficient data structures and optimized algorithms. Compared with some other common cache frameworks, Caffeine performs well in cache access speed and response time.

  2. Memory management: Caffeine provides flexible memory management options. It supports size-based, count-based, and weight-based cache size limits. You can choose the appropriate cache size strategy according to the needs of your application, and you can further adjust it through configuration parameters.

  3. Powerful functions: Caffeine provides many powerful functions to meet various needs. It supports asynchronous loading and refreshing cache items, can set expiration time and timing refresh strategy, supports automatic deletion and manual invalidation of cache items, etc. In addition, Caffeine also provides statistical information and a listener mechanism, which can easily monitor and manage the status and changes of the cache.

  4. Thread safety: Caffeine is thread safe and can be used safely in a multi-threaded environment. It uses a fine-grained locking mechanism to protect shared resources and ensure the correctness and consistency of concurrent access.

  5. Ease of Integration: Caffeine is a standalone Java library that can be easily integrated with existing applications. It is compatible with standard Java concurrency libraries and other third-party libraries, and can be seamlessly integrated with various frameworks and technologies (such as Spring, Hibernate, etc.).


Caffeine is a high-performance Java caching framework designed to provide fast and efficient in-memory caching solutions. It was developed by Google and is an upgraded version of the Guava cache.

Caffeine is designed to provide high-throughput, low-latency cache access, and supports various caching strategies and features. Here are some key features of the Caffeine framework:

  1. High performance: The design of Caffeine optimizes the memory access mode of the cache, and uses various techniques to reduce the overhead of cache access, thereby improving performance. It uses a data structure similar to Java ConcurrentHashMap, supports concurrent access, and provides configurable concurrency levels.

  2. Memory management: Caffeine provides flexible memory management options, which can control the size of the cache by setting the maximum size of the cache, the maximum number of entries, or the maximum weight. It also supports automatic cleaning of expired cache entries based on policies such as volume, time, or reference.

  3. Powerful caching strategy: Caffeine provides a variety of caching strategies, including expiration strategies based on access time, write time or custom rules. It also supports other policies such as least recently used (LRU), least recently used (LFU), and fixed size.

  4. Asynchronous loading: Caffeine supports the ability to load cache entries asynchronously. It can automatically trigger the loading process when the required entry does not exist in the cache, and put the result in the cache when the loading is complete.

  5. Statistics and monitoring: Caffeine provides a wealth of statistics and monitoring functions, which can track cache hit rate, loading time, cache size and other indicators. This information is very useful for tuning and performance analysis.

  6. Extensibility: Caffeine's design allows developers to extend the functionality of the framework by customizing policies, cache loaders, and listeners.

Using the Caffeine caching framework is very simple. You can get started by following these steps:

  1. Import Caffeine dependencies: Add Caffeine dependencies to your project, which can be imported through Maven, Gradle or directly downloading JAR files.

  2. Create a cache instance: Use Caffeine's builder mode to create a cache instance, and you can set cache parameters and strategies.

  3. Store and retrieve data: use the cache putmethod to store data into the cache, and use getthe method to retrieve data from the cache. You can choose to trigger an asynchronous load or provide custom load logic if the required data is not present in the cache.

  4. Tuning and configuration: Depending on the needs of the application, the parameters and policies of the cache can be tuned for optimal performance and memory management.

In short, Caffeine is a powerful, high-performance Java caching framework, suitable for various application scenarios, especially memory caching requirements that require fast access, low latency, and high throughput.


design

insert image description here


Code

POM

<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
   <artifactId>caffeine</artifactId>
   <!-- Java 8 users can continue to use version 2.x, which will be supported -->
   <version>2.9.3</version>
</dependency>

If it is caffeine 3.x version, JDK 11 or above is required.


Population

https://github.com/ben-manes/caffeine/wiki/Population

package com.artisan.caffeinedemo.java;

import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.Builder;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Slf4j
public class CaffeineBaseExampleWithJava {
    
    


    @SneakyThrows
    public static void main(String[] args) {
    
    
        //  initFirstWay();
        //  initSecondWay();
        initThirdWay();

        TimeUnit.SECONDS.sleep(10);

    }


    /**
     * Cache手动创建
     * <p>
     * 最普通的一种缓存,无需指定加载方式,需要手动调用put()进行加载。需要注意的是put()方法对于已存在的key将进行覆盖,这点和Map的表现是一致的。
     * 在获取缓存值时,如果想要在缓存值不存在时,原子地将值写入缓存,则可以调用get(key, k -> value)方法,该方法将避免写入竞争。调用invalidate()方法,将手动移除缓存。
     * <p>
     * 在多线程情况下,当使用get(key, k -> value)时,如果有另一个线程同时调用本方法进行竞争,则后一线程会被阻塞,直到前一线程更新缓存完成;
     * 而若另一线程调用getIfPresent()方法,则会立即返回null,不会被阻塞。
     */
    @SneakyThrows
    public static void initFirstWay() {
    
    

        Cache<Object, Object> cache = Caffeine.newBuilder()
                //初始数量
                .initialCapacity(10)
                //最大条数
                .maximumSize(10)
                //expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准
                //最后一次写操作后经过指定时间过期
                .expireAfterWrite(3, TimeUnit.SECONDS)
                //最后一次读或写操作后经过指定时间过期
                .expireAfterAccess(3, TimeUnit.SECONDS)
                //监听缓存被移除
                .removalListener((key, val, removalCause) -> {
    
    
                    log.info("listener remove : {} ,{} ,{}", key, val, removalCause);
                })
                //记录命中
                .recordStats()
                .build();

        cache.put("name", "小工匠");
        //小工匠
        log.info((String) cache.getIfPresent("name"));

        // 结合初始化的时候设置的过期时间, 模拟程序运行5秒后,再此获取
        TimeUnit.SECONDS.sleep(5);
        log.info("5秒后再次获取:{}", (String) cache.getIfPresent("name"));


        //存储的是默认值
        log.info((String) cache.get("noKey", o -> "默认值"));

    }


    /**
     * Loading Cache自动创建
     * <p>
     * LoadingCache是一种自动加载的缓存。其和普通缓存不同的地方在于,当缓存不存在/缓存已过期时,若调用get()方法,则会自动调用CacheLoader.load()方法加载最新值。
     * 调用getAll()方法将遍历所有的key调用get(),除非实现了CacheLoader.loadAll()方法。
     * <p>
     * 使用LoadingCache时,需要指定CacheLoader,并实现其中的load()方法供缓存缺失时自动加载。
     * <p>
     * 在多线程情况下,当两个线程同时调用get(),则后一线程将被阻塞,直至前一线程更新缓存完成。
     */
    @SneakyThrows
    public static void initSecondWay() {
    
    

        Map<Integer, Artisan> map = getArtisanMap();


        log.info("size:{}", map.size());

        LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
                //创建缓存或者最近一次更新缓存后经过指定时间间隔,刷新缓存;refreshAfterWrite仅支持LoadingCache
                .refreshAfterWrite(10, TimeUnit.SECONDS)
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .expireAfterAccess(10, TimeUnit.SECONDS)
                .maximumSize(10)
                //根据key查询数据库里面的值,这里是个lambda表达式
                // TODO  根据其概况初始化 ,比如   build(userId -> getUserFromDatabase(userId));
                //   private User getUserFromDatabase(String userId) {
    
    
                //    // 这里会从数据库查询用户信息
                //    // ...
                //    return user;
                //  }
                .build(key -> map.get(Integer.parseInt(key)).toString());


        // 当执行get的时候,会触发 build 中的 lambda表达式
        log.info(loadingCache.get("1"));
        log.info(loadingCache.get("2"));
        log.info(loadingCache.get("3"));
        log.info(loadingCache.get("4"));
        log.info(loadingCache.get("4", o -> "默认值"));

        // 获取一个不存在的值
        log.info(loadingCache.get("noKey", o -> "默认值"));
    }


    /**
     * AsyncCache是Cache的一个变体,其响应结果均为CompletableFuture,
     * 通过这种方式,AsyncCache对异步编程模式进行了适配。
     * <p>
     * 默认情况下,缓存计算使用ForkJoinPool.commonPool()作为线程池,如果想要指定线程池,则可以覆盖并实现Caffeine.executor(Executor)方法。
     * <p>
     * synchronous()提供了阻塞直到异步缓存生成完毕的能力,它将以Cache进行返回。
     * <p>
     * 在多线程情况下,当两个线程同时调用get(key, k -> value),则会返回同一个CompletableFuture对象。由于返回结果本身不进行阻塞,可以根据业务设计自行选择阻塞等待或者非阻塞。
     */
    @SneakyThrows
    public static void initThirdWay() {
    
    

        Map<Integer, Artisan> map = getArtisanMap();


        log.info("size:{}", map.size());


        AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
                //创建缓存或者最近一次更新缓存后经过指定时间间隔刷新缓存;仅支持LoadingCache
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterAccess(1, TimeUnit.SECONDS)
                .maximumSize(10)
                //根据key查询数据库里面的值
                .buildAsync(key -> {
    
    
                    Thread.sleep(1000);
                    return map.get(Integer.parseInt(key)).toString();
                });

        //异步缓存返回的是CompletableFuture
        CompletableFuture<String> future = asyncLoadingCache.get("1");
        future.thenAccept(System.out::println);

    }

    private static Map<Integer, Artisan> getArtisanMap() {
    
    
        Map<Integer, Artisan> map = new HashMap<>(16);
        map.put(1, Artisan.builder().id(1).name("artisan1").hobbies(Arrays.asList("Java")).build());
        map.put(2, Artisan.builder().id(2).name("artisan2").hobbies(Arrays.asList("AIGC")).build());
        map.put(3, Artisan.builder().id(3).name("artisan3").hobbies(Arrays.asList("HADOOP")).build());
        map.put(4, Artisan.builder().id(4).name("artisan4").hobbies(Arrays.asList("GO")).build());
        return map;
    }

    @Data
    @Builder
    private static class Artisan {
    
    
        private Integer id;
        private String name;
        private List<String> hobbies;
    }
}
    

When expireAfterWrite and expireAfterAccess exist at the same time, expireAfterWrite shall prevail


Eviction Policy

https://github.com/ben-manes/caffeine/wiki/Eviction

package com.artisan.caffeinedemo.java;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Scheduler;
import com.github.benmanes.caffeine.cache.Weigher;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 * @desc: 驱逐策略在创建缓存的时候进行指定。常用的有基于容量的驱逐和基于时间的驱逐。
 * <p>
 * 基于容量的驱逐需要指定缓存容量的最大值,当缓存容量达到最大时,Caffeine将使用LRU策略对缓存进行淘汰;基于时间的驱逐策略如字面意思,可以设置在最后访问/写入一个缓存经过指定时间后,自动进行淘汰。
 * <p>
 * 驱逐策略可以组合使用,任意驱逐策略生效后,该缓存条目即被驱逐。
 * <p>
 * LRU 最近最少使用,淘汰最长时间没有被使用的页面。
 * LFU 最不经常使用,淘汰一段时间内使用次数最少的页面
 * FIFO 先进先出
 * Caffeine有4种缓存淘汰设置
 * <p>
 * 大小 (LFU算法进行淘汰)
 * 权重 (大小与权重 只能二选一)
 * 时间
 * 引用
 */
@Slf4j
public class CaffeineEvictionPolicy {
    
    

    /**
     * 缓存大小淘汰
     */
    @Test
    public void maximumSizeTest() throws InterruptedException {
    
    
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                //超过10个后会使用W-TinyLFU算法进行淘汰
                .maximumSize(10)
                .evictionListener((key, val, removalCause) -> {
    
    
                    log.info("淘汰缓存:key:{} val:{}", key, val);
                })
                .build();

        // 模拟写入数据
        for (int i = 1; i < 20; i++) {
    
    
            cache.put(i, i);
        }

        //缓存淘汰是异步的
        Thread.sleep(1000);

        // 打印还没被淘汰的缓存
        log.info("未淘汰的缓存:{}", cache.asMap());
    }

    /**
     * 权重淘汰
     */
    @Test
    public void maximumWeightTest() throws InterruptedException {
    
    
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                //限制总权重,若所有缓存的权重加起来>总权重就会淘汰权重小的缓存
                .maximumWeight(100)
                .weigher((Weigher<Integer, Integer>) (key, value) -> key)
                .evictionListener((key, val, removalCause) -> {
    
    
                    log.info("淘汰缓存:key:{} val:{}", key, val);
                })
                .build();

        //总权重其实是=所有缓存的权重加起来
        int maximumWeight = 0;
        for (int i = 1; i < 20; i++) {
    
    
            cache.put(i, i);
            maximumWeight += i;
        }
        log.info("总权重={}", maximumWeight);

        //缓存淘汰是异步的
        Thread.sleep(1000);

        // 打印还没被淘汰的缓存
        log.info("未淘汰的缓存:{}", cache.asMap());
    }


    /**
     * 访问后到期(每次访问都会重置时间,也就是说如果一直被访问就不会被淘汰)
     */
    @Test
    public void expireAfterAccessTest() throws InterruptedException {
    
    
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                .expireAfterAccess(1, TimeUnit.SECONDS)
                //可以指定调度程序来及时删除过期缓存项,而不是等待Caffeine触发定期维护
                //若不设置scheduler,则缓存会在下一次调用get的时候才会被动删除
                .scheduler(Scheduler.systemScheduler())
                .evictionListener((key, val, removalCause) -> {
    
    
                    log.info("淘汰缓存:key:{} val:{}", key, val);

                })
                .build();
        cache.put(1, 2);

        log.info("{}", cache.getIfPresent(1));

        Thread.sleep(5000);

        //null
        log.info("{}", cache.getIfPresent(1));

        Thread.sleep(500);
    }

    /**
     * 写入后到期
     */
    @Test
    public void expireAfterWriteTest() throws InterruptedException {
    
    
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.SECONDS)
                //可以指定调度程序来及时删除过期缓存项,而不是等待Caffeine触发定期维护
                //若不设置scheduler,则缓存会在下一次调用get的时候才会被动删除
                .scheduler(Scheduler.systemScheduler())
                .evictionListener((key, val, removalCause) -> {
    
    
                    log.info("淘汰缓存:key:{} val:{}", key, val);
                })
                .build();
        cache.put(1, 2);

        Thread.sleep(3000);

        //null
        log.info("{}", cache.getIfPresent(1));
    }


}
    

Refresh

https://github.com/ben-manes/caffeine/wiki/Refresh

package com.artisan.caffeinedemo.java;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 * <p>
 * <p>
 * refreshAfterWrite()表示x秒后自动刷新缓存的策略可以配合淘汰策略使用,
 * <p>
 * 注意的是刷新机制只支持LoadingCache和AsyncLoadingCache
 */
@Slf4j
public class CaffeineRefreshPolicy {
    
    

    private static int NUM = 0;

    @Test
    public void refreshAfterWriteTest() throws InterruptedException {
    
    
        LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                //模拟获取数据,每次获取就自增1
                .build(integer -> ++NUM);

        //获取ID=1的值,由于缓存里还没有,所以会自动放入缓存
        // 返回结果 1
        log.info("get value = {}", cache.get(1));

        // 延迟2秒后,理论上自动刷新缓存后取到的值是2
        // 但其实不是,值还是1,因为refreshAfterWrite并不是设置了n秒后重新获取就会自动刷新
        // 而是x秒后&&第二次调用getIfPresent的时候才会被动刷新
        Thread.sleep(2000);
        // 返回结果 1
        log.info("get value = {}", cache.getIfPresent(1));

        //此时才会刷新缓存,而第一次拿到的还是旧值 ,这时候拿到的就是新的值了 2
        // 返回结果2
        log.info("get value = {}", cache.getIfPresent(1));
    }

}
    

Statistics

https://github.com/ben-manes/caffeine/wiki/Statistics

package com.artisan.caffeinedemo.java;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Slf4j
public class CaffeineStatstic {
    
    


    @Test
    public void testStatistic() {
    
    

        LoadingCache<String, String> cache = Caffeine.newBuilder()
                //创建缓存或者最近一次更新缓存后经过指定时间间隔,刷新缓存;refreshAfterWrite仅支持LoadingCache
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterAccess(1, TimeUnit.SECONDS)
                .maximumSize(10)
                //开启记录缓存命中率等信息
                .recordStats()
                //根据key查询数据库里面的值
                .build(key -> {
    
    
                    Thread.sleep(1000);
                    return new Date().toString();
                });


        cache.put("1", "shawn");
        cache.get("1");

        /*
         * hitCount :命中的次数
         * missCount:未命中次数
         * requestCount:请求次数
         * hitRate:命中率
         * missRate:丢失率
         * loadSuccessCount:成功加载新值的次数
         * loadExceptionCount:失败加载新值的次数
         * totalLoadCount:总条数
         * loadExceptionRate:失败加载新值的比率
         * totalLoadTime:全部加载时间
         * evictionCount:丢失的条数
         */
        log.info("统计信息如下:\n {}", cache.stats());
    }
}
    

insert image description here

Guess you like

Origin blog.csdn.net/yangshangwei/article/details/131666835