King of Java Local Cache: Caffeine Nanny-Level Tutorial


1. Introduction to Caffeine

1. Cache introduction

Cache (Cache) is ubiquitous in the code world. From the underlying CPU multi-level cache to the client's page cache, there are caches everywhere. In essence, caching is a means of exchanging space for time. By arranging data in a certain space, the next data access can be accelerated.

As far as Java is concerned, there are many commonly used caching solutions, such as the database caching framework EhCache, distributed caching Memcached, etc. These caching solutions are actually designed to improve throughput efficiency and avoid excessive pressure on the persistence layer.

For common cache types, it can be divided into two types: local cache and distributed cache. Caffeine is an excellent local cache, and Redis can be used as distributed cache.

2. Introduction to Caffeine

Official Caffeine:

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

Caffeine is a high-performance local cache library based on Java 1.8, improved by Guava, and the default cache implementation starting from Spring 5 replaces the original Google Guava with Caffeine. The official statement points out that its cache hit rate is close to the optimal value. In fact, local caches like Caffeine are very similar to ConcurrentMap, that is, they support concurrency and data access with O(1) time complexity. The main difference between the two is:

  • ConcurrentMap will store all stored data until you explicitly remove it;
  • Caffeine will automatically remove "infrequently used" data through the given configuration to keep the memory occupied reasonably.

Therefore, a better way to understand it is: Cache is a Map with storage and removal strategies.

b9b1b69aed3851df8fc06cbf43bd0e69.jpeg

2. Basics of Caffeine

To use Caffeine, you need to introduce the following dependencies in the project

<dependency>
&nbsp;&nbsp;&nbsp;&nbsp;<groupId>com.github.ben-manes.caffeine</groupId>
&nbsp;&nbsp;&nbsp;&nbsp;<artifactId>caffeine</artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;<!--https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeinez找最新版-->
&nbsp;&nbsp;&nbsp;&nbsp;<version>3.0.5</version>
</dependency>

1. Cache loading strategy

1.1 Cache manual creation

The most common type of cache does not need to specify the loading method, and needs to manually call put() to load. It should be noted that the put() method will overwrite the existing key, which is consistent with the performance of Map. When getting the cached value, if you want to atomically write the value to the cache when the cached value does not exist, you can call the get(key, k -> value) method, which will avoid write competition. Calling the invalidate() method will manually remove the cache.

In the case of multi-threading, when using get(key, k -> value), if another thread calls this method to compete at the same time, the latter thread will be blocked until the previous thread updates the cache; if another thread If a thread calls the getIfPresent() method, it will return null immediately without being blocked.

Cache<Object, Object> cache = Caffeine.newBuilder()
          //初始数量
          .initialCapacity(10)
          //最大条数
          .           .expireAfterWrite(1, TimeUnit.SECONDS) nbsp ;   //The specified time expires after the last write operation
maximumSize(10)           //When expireAfterWrite and expireAfterAccess exist at the same time, expireAfterWrite shall prevail        &


&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//最后一次读或写操作后经过指定时间过期
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.expireAfterAccess(1,&nbsp;TimeUnit.SECONDS)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//监听缓存被移除
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.removalListener((key,&nbsp;val,&nbsp;removalCause)&nbsp;->&nbsp;{&nbsp;})
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//记录命中
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.recordStats()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.build();

&nbsp;&nbsp;cache.put( "1", "张三");
&nbsp;&nbsp;//张三
  System.out.println(cache.getIfPresent( "1"));
  //Store the default value
  System.out.println(cache.get( "2",o  ;->  "Default Value"));
1.2 Automatic creation of Loading Cache

LoadingCache is an autoloaded cache. The difference between it and ordinary cache is that when the cache does not exist or the cache has expired, if the get() method is called, the CacheLoader.load() method will be automatically called to load the latest value. Calling the getAll() method will traverse all keys and call get(), unless the CacheLoader.loadAll() method is implemented. When using LoadingCache, you need to specify CacheLoader, and implement the load() method in it for automatic loading when the cache is missing.


In the case of multi-threading, when two threads call get() at the same time, the latter thread will be blocked until the previous thread updates the cache.

LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
        //The specified time interval has elapsed since the cache was created or the latest cache update , Refresh the cache; refreshAfterWrite only supports LoadingCache ;
&
nbsp ;.expireAfterWrite(10, TimeUnit.SECONDS)
        .expireAfterAccess(10, TimeUnit.SECONDS)
; ;  .
maximumSize(10)         //Query the value in the database according to the key, here is a lamba expression          &
nbsp ;.build(key -> new Date().toString());
1.3 Async Cache asynchronous acquisition

AsyncCache is a variant of Cache, and its response results are CompletableFuture. In this way, AsyncCache adapts to the asynchronous programming model. By default, the cache calculation uses ForkJoinPool.commonPool() as the thread pool. If you want to specify the thread pool, you can override and implement the Caffeine.executor(Executor) method. synchronous() provides the ability to block until the asynchronous cache is generated, and it will return as Cache.

In the case of multi-threading, when two threads call get(key, k -> value) at the same time, the same CompletableFuture object will be returned. Since the returned result itself does not block, you can choose to block waiting or non-blocking according to the business design.

AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
         //The specified time interval has elapsed since the cache was created or the latest cache update Refresh cache; only supports LoadingCache
        .refreshAfterWrite(1, TimeUnit.SECONDS)
;. expireAfterWrite(1, TimeUnit.SECONDS)
        .expireAfterAccess(1, TimeUnit.SECONDS)       &
nbsp ; . maximumSize(10
)         //Query the value in the database according to key
        .buildAsync(key  -> {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(1000);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return&nbsp;new&nbsp;Date().toString();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});

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

2. Expulsion strategy

The eviction policy is specified when the cache is created. Commonly used are capacity-based eviction and time-based eviction.

Capacity-based eviction needs to specify the maximum value of the cache capacity. When the cache capacity reaches the maximum, Caffeine will use the LRU strategy to eliminate the cache; the time-based eviction policy can be set to access/write a cache at the end of the specified time. After the time, it will be eliminated automatically.

Eviction strategies can be used in combination. After any eviction strategy takes effect, the cache entry will be evicted.

  • LRU is the least recently used, and the pages that have not been used for the longest time are eliminated.
  • LFU least frequently used, evicts the least frequently used pages over a period of time
  • FIFO first in first out

Caffeine has 4 cache elimination settings

  • Size (LFU algorithm for elimination)
  • Weight (you can only choose one of size and weight)
  • time
  • Citation (not commonly used, not covered in this article)
@Slf4j
public&nbsp;class&nbsp;CacheTest&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;/**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;缓存大小淘汰
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/
&nbsp;&nbsp;&nbsp;&nbsp;@Test
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;maximumSizeTest()&nbsp;throws&nbsp;InterruptedException&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Cache<Integer,&nbsp;Integer>&nbsp;cache&nbsp;=&nbsp;Caffeine.newBuilder()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//超过10个后会使用W-TinyLFU算法进行淘汰
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.maximumSize(10)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.evictionListener((key,&nbsp;val,&nbsp;removalCause)&nbsp;->&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info( "淘汰缓存:key:{} val:{}",&nbsp;key,&nbsp;val);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.build();

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for&nbsp;(int&nbsp;i&nbsp;=&nbsp;1;&nbsp;i&nbsp;<&nbsp;20;&nbsp;i++)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cache.put(i,&nbsp;i);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(500);//缓存淘汰是异步的

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;打印还没被淘汰的缓存
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(cache.asMap());
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;/**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;权重淘汰
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/
&nbsp;&nbsp;&nbsp;&nbsp;@Test
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;maximumWeightTest()&nbsp;throws&nbsp;InterruptedException&nbsp;{
         Cache<Integer, Integer> cache = Caffeine.newBuilder()
;           //Limit the total weight, if the weight of all caches add up > the total weight will eliminate caches with small weight    &
nbsp ;            .maximumWeight(100)            &nb sp
;      .weigher((Weigher<Integer, Integer>) (key,  value) -> key)
                .evictionListener((key, val, removalCause) -> {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info( "淘汰缓存:key:{} val:{}",&nbsp;key,&nbsp;val);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.build();

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//总权重其实是=所有缓存的权重加起来
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;maximumWeight&nbsp;=&nbsp;0;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for&nbsp;(int&nbsp;i&nbsp;=&nbsp;1;&nbsp;i&nbsp;<&nbsp;20;&nbsp;i++)&nbsp;{
            cache.put(i, i);
            maximumWeight += i;
        }
        System.out.println( "总权重=" + maximumWeight);
        Thread.      *  ; Expires after visit (the time will be reset every visit, that is to say, if it has been visited all the time, it will not be eliminated)     /**     } ; ;  System.out.println(cache.asMap());

sleep(500);//Cache elimination is asynchronous        // Print the cache that has not been eliminated






     */
    @Test
    public void expireAfterAccessTest() throws InterruptedException {
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                .expireAfterAccess(1, TimeUnit.                 //If the scheduler is not set, the cache will call get next time will be deleted passively
SECONDS)                  //You can specify a scheduler to delete expired cache items in time instead of waiting Caffeine triggers regular maintenance

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.scheduler(Scheduler.systemScheduler())
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.evictionListener((key,&nbsp;val,&nbsp;removalCause)&nbsp;->&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info( "淘汰缓存:key:{} val:{}",&nbsp;key,&nbsp;val);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.build();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cache.put(1,&nbsp;2);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(cache.getIfPresent(1));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(3000);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(cache.getIfPresent(1));//null
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;/**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;写入后到期
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/
&nbsp;&nbsp;&nbsp;&nbsp;@Test
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;expireAfterWriteTest()&nbsp;throws&nbsp;InterruptedException&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Cache<Integer,&nbsp;Integer>&nbsp;cache&nbsp;=&nbsp;Caffeine.newBuilder()
                 .expireAfterWrite(1, TimeUnit.SECONDS)    &nb
sp ;            //You can specify a scheduler to delete expired cache items in time, instead of waiting for Caffeine to trigger regular maintenance  &
nbsp ;             //If the scheduler is not set, the cache will be passively deleted when the next call to get &
nbsp ;               .scheduler(Scheduler. systemScheduler())
                .evictionListener((key, val, removalCause) -> {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info( "淘汰缓存:key:{} val:{}",&nbsp;key,&nbsp;val);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.build();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cache.put(1,&nbsp;2);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(3000);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(cache.getIfPresent(1));//null
&nbsp;&nbsp;&nbsp;&nbsp;}
}

3. Refresh mechanism

refreshAfterWrite() indicates that the strategy of automatically refreshing the cache after x seconds can be used in conjunction with the elimination strategy. Note that the refresh mechanism only supports LoadingCache and AsyncLoadingCache

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      System.out.println(cache.get(1));// 1

);     //Get the value of ID=1, since it is not in the cache, it will be automatically put into the cache


    // After a delay of 2 seconds, theoretically the value obtained after automatically refreshing the cache is 2     // But it is not, the value is still 1,
because refreshAfterWrite is not set to automatically refresh after reacquisition after n seconds
     //  but passively refreshes after x seconds && when getIfPresent is called for the second time
      ;Thread.sleep(2000);
    System.out.println(cache.getIfPresent(1));// 1

    //It will be refreshed at this time Cache, but the old value is still obtained for the first time
    System.out.println(cache.getIfPresent(1));// 2
}

4. Statistics

LoadingCache<String, String> cache = Caffeine.newBuilder()
        //The specified time interval has elapsed since the cache was created or the latest cache update
, Refresh the cache; refreshAfterWrite only supports LoadingCache
  ;.expireAfterWrite(1, TimeUnit.SECONDS)
        .expireAfterAccess(1, TimeUnit.SECONDS)       &
nbsp ;  . maximumSize(10
)         //Enable information such as record cache hit rate        .recordStats()
&
nbsp ;       //Query the value in the database according to the key
        .build(key -> {
            Thread.sleep(1000);
             return new Date().toString();
        });


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

/*
 * hitCount :命中的次数
 * missCount:未命中次数
 * requestCount:Number of requests  * loadExceptionCount: Times of failing to load new values  * loadSuccessCount: Times of successfully loading new values  * missRate: Missing rate
 * hitRate: Hit rate



 * totalLoadCount: the total number
of items * loadExceptionRate: the rate of failure to load new values
 * totalLoadTime: the total loading time
 * evictionCount: the number of missing items
 */
System. out.println(cache.stats());

5. Summary

Some of the above strategies can be freely combined when creating, generally there are two methods

  • Set maxSize, refreshAfterWrite, do not set expireAfterWrite/expireAfterAccess, set expireAfterWrite, and when the cache expires, it will lock and acquire the cache synchronously, so when setting expireAfterWrite, the performance is better, but sometimes old data will be fetched, which is suitable for scenarios where old data is allowed to be fetched
  • Set maxSize, expireAfterWrite/expireAfterAccess, do not set refreshAfterWrite, the data consistency is good, and the old data will not be obtained, but the performance is not so good (in comparison), it is suitable for scenarios where data acquisition is not time-consuming

3. SpringBoot integrates Caffeine

1. Comments about @Cacheable

1.1 Related dependencies

If you want to use the @Cacheable annotation, you need to introduce related dependencies and add the @EnableCaching annotation to any configuration class file

<dependency>
&nbsp;&nbsp;&nbsp;&nbsp;<groupId>org.springframework.boot</groupId>
&nbsp;&nbsp;&nbsp;&nbsp;<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
1.2 Common annotations
  • @Cacheable : Indicates that the method supports caching. When the annotated method is called, if the corresponding key already exists in the cache, the method body will not be executed, but will be returned directly from the cache. When the method returns null, no cache operation will be performed.
  • @CachePut : Indicates that after the method is executed, its value will be updated to the cache as the latest result, and the method will be executed every time.
  • @CacheEvict : Indicates that after the method is executed, the cache clearing operation will be triggered.
  • @Caching : Used to combine the first three annotations, for example:
@Caching(cacheable&nbsp;=&nbsp;@Cacheable( "CacheConstants.GET_USER"),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;evict&nbsp;=&nbsp;{@CacheEvict( "CacheConstants.GET_DYNAMIC",allEntries&nbsp;=&nbsp; true)}
public&nbsp;User&nbsp;find(Integer&nbsp;id)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp; return&nbsp;null;
}
1.3 Common annotation properties
  • cacheNames/value : The name of the cache component, that is, the name of the cache in the cacheManager.
  • key : The key used when caching data. By default, method parameter values ​​are used, and can also be written using SpEL expressions.
  • keyGenerator : use one of the two keys.
  • cacheManager : Specifies the cache manager to use.
  • condition : Check before the method execution starts, and cache if the condition is met
  • unless : Check after the method is executed, and do not cache if it matches unless
  • sync : Whether to use synchronous mode. If synchronous mode is used, when multiple threads load a key at the same time, other threads will be blocked.
1.4 Cache synchronization mode

Sync is turned on or off, and the performance in Cache and LoadingCache is inconsistent:

  • In Cache, sync indicates whether all threads need to wait synchronously
  • In LoadingCache, sync indicates whether to execute the annotated method when reading a non-existent/evicted key

2. Actual combat

2.1 Introducing dependencies
<dependency>
&nbsp;&nbsp;&nbsp;&nbsp;<groupId>org.springframework.boot</groupId>
&nbsp;&nbsp;&nbsp;&nbsp;<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
&nbsp;&nbsp;&nbsp;&nbsp;<groupId>com.github.ben-manes.caffeine</groupId>
&nbsp;&nbsp;&nbsp;&nbsp;<artifactId>caffeine</artifactId>
</dependency>
2.2 Cache Constants CacheConstants

Create a cache constant class, extract a layer of common constants, and reuse them. Here, you can also load these data through configuration files, such as @ConfigurationProperties and @Value

public&nbsp;class&nbsp;CacheConstants&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;/**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;默认过期时间(配置类中我使用的时间单位是秒,所以这里如&nbsp;3*60&nbsp;为3分钟)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;final&nbsp;int&nbsp;DEFAULT_EXPIRES&nbsp;=&nbsp;3&nbsp;*&nbsp;60;
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;final&nbsp;int&nbsp;EXPIRES_5_MIN&nbsp;=&nbsp;5&nbsp;*&nbsp;60;
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;final&nbsp;int&nbsp;EXPIRES_10_MIN&nbsp;=&nbsp;10&nbsp;*&nbsp;60;

&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;final&nbsp;String&nbsp;GET_USER&nbsp;=&nbsp; "GET:USER";
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;final&nbsp;String&nbsp;GET_DYNAMIC&nbsp;=&nbsp; "GET:DYNAMIC";

}
2.3 Cache configuration class CacheConfig
@Configuration
@EnableCaching
public class CacheConfig {
    /**
      * Caffeine configuration instructions:
;     * ; initialCapacity=[integer]: Initial cache space size
     * maximumSize=[long]: Maximum number of cache items
*  maximumWeight=[long]: Maximum weight of the cache
     * expireAfterAccess=[duration]: Expires after a fixed time after the last write or access
;    * expireAfterWrite=[duration]:
 Fixed time expires after the last write     * refreshAfterWrite=[duration]: Cache is created or updated after a fixed time interval, refresh the cache











&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SimpleCacheManager&nbsp;cacheManager&nbsp;=&nbsp;new&nbsp;SimpleCacheManager();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List<CaffeineCache>&nbsp;list&nbsp;=&nbsp;new&nbsp;ArrayList<>();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//循环添加枚举类中自定义的缓存,可以自定义
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for&nbsp;(CacheEnum&nbsp;cacheEnum&nbsp;:&nbsp;CacheEnum.values())&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;list.add(new&nbsp;CaffeineCache(cacheEnum.getName(),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Caffeine.newBuilder()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.initialCapacity(50)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.maximumSize(1000)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.expireAfterAccess(cacheEnum.getExpires(),&nbsp;TimeUnit.SECONDS)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.build()));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cacheManager.setCaches(list);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return&nbsp;cacheManager;
&nbsp;&nbsp;&nbsp;&nbsp;}
}
2.4 Call cache

It should be noted here that Cache, like @Transactional, also uses proxies, and calls within the class will be invalid

/**
 * value: The prefix of the cache key.
 * key: The suffix of the cache key.
 * sync: Set whether only one request is sent to the database if the cache expires, and other requests are blocked. The default is false (according to personal needs).
 * unless: do not cache null values, if not used here, an error will be reported
 * Query user information class
 * If you need to add a custom string, you need to use single quotes
 * If the query is null, will also be cached
 */
@Cacheable(value = CacheConstants.GET_USER,key =  "'user'+#userId",sync =  true ) @CacheEvict public UserEntity & nbsp
;
getUserByUserId (Integer userId){
    UserEntity userEntity = userMapper.

&nbsp;&nbsp;&nbsp;&nbsp; return&nbsp;userEntity;
}


Guess you like

Origin blog.csdn.net/zhaomengsen/article/details/132217900