SpringBoot整合Caffeine

SpringBoot整合Caffeine

1. 简介

Caffeine 是基于Java 8 开发的、提供了近乎最佳命中率的高性能本地缓存组件,Spring5 开始不再支持 Guava Cache,改为使用 Caffeine。Caffeine与其他本地缓存的性能比较如下:
在这里插入图片描述
Caffeine具有以下功能:

1. 自动加载条目到缓存中,可选异步方式
2. 可以基于大小剔除
3. 可以设置过期时间,时间可以从上次访问或上次写入开始计算
4. 异步刷新
5. keys自动包装在弱引用中
6. values自动包装在弱引用或软引用中
7. 条目剔除通知
8. 缓存访问统计

2. SpringBoot整合Caffeine

下面介绍SpringBoot使用Caffeine的简单案例
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.young</groupId>
    <artifactId>caffeine02</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.7.0</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <!--引入caffeine依赖-->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

</project>

数据库内容如下图:
在这里插入图片描述
User实体类

package com.young.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;

import java.io.Serializable;

@Data
@TableName(value = "t_user")
@ToString
public class User implements Serializable {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
    private String sex;
    private Integer age;
}

UserMapper.java

package com.young.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.young.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

UserService.java

package com.young.service;

import com.young.entity.User;

public interface UserService {
    Boolean saveUser(User user);
    Boolean updateUser(User user);
    Boolean deleteUserById(Integer id);
    User getUserById(Integer id);
}

UserServiceImpl.java

package com.young.service.impl;

import com.github.benmanes.caffeine.cache.Cache;
import com.young.entity.User;
import com.young.mapper.UserMapper;
import com.young.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
@Slf4j
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Resource
    private Cache<String,Object>caffeineCache;

    @Override
    public Boolean saveUser(User user) {
        return userMapper.insert(user)>0;
    }

    @Override
    public Boolean updateUser(User user) {
        if (user.getId()==null){
            return false;
        }
        if(userMapper.updateById(user)>0){
            //删除缓存
            caffeineCache.asMap().remove(user.getId()+"");
            return true;
        }
        return false;
    }

    @Override
    public Boolean deleteUserById(Integer id) {
        if (userMapper.deleteById(id)>0){
            //删除缓存
            caffeineCache.asMap().remove(id+"");
            return true;
        }
        return false;
    }

    @Override
    public User getUserById(Integer id) {
        User user=(User)caffeineCache.asMap().get(id+"");
        if (user!=null){
            log.info("从缓存中获取==============");
            return user;
        }
        log.info("从数据库中获取===============");
        user=userMapper.selectById(id);
        if (user==null){
            log.info("数据为空===========");
            return null;
        }
        caffeineCache.put(id+"",user);
        return user;
    }
}

常用的配置参数

expireAfterWrite:写入间隔多久淘汰;
expireAfterAccess:最后访问后间隔多久淘汰;
refreshAfterWrite:写入后间隔多久刷新,该刷新是基于访问被动触发的,支持异步刷新和同步刷新,如果和 expireAfterWrite 组合使用,能够保证即使该缓存访问不到、也能在固定时间间隔后被淘汰,否则如果单独使用容易造成OOM;
expireAfter:自定义淘汰策略,该策略下 Caffeine 通过时间轮算法来实现不同key 的不同过期时间;
maximumSize:缓存 key 的最大个数;
weakKeys:key设置为弱引用,在 GC 时可以直接淘汰;
weakValues:value设置为弱引用,在 GC 时可以直接淘汰;
softValues:value设置为软引用,在内存溢出前可以直接淘汰;
executor:选择自定义的线程池,默认的线程池实现是 ForkJoinPool.commonPool();
maximumWeight:设置缓存最大权重;
weigher:设置具体key权重;
recordStats:缓存的统计数据,比如命中率等;
removalListener:缓存淘汰监听器;
writer:缓存写入、更新、淘汰的监听器。

CaffeineCache的配置类,我们配置过期时间为10秒,初始容量100,最大容量200

package com.young.config;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class CaffeineConfig {
    @Bean
    public Cache caffeineCache(){
        return Caffeine.newBuilder()
                //设置10秒后过期,方便后续观察现象
                .expireAfterWrite(10, TimeUnit.SECONDS)
                //初始容量为100
                .initialCapacity(100)
                //最大容量为200
                .maximumSize(200)
                .build();
    }
}

然后创建测试类,进行测试:

package com.young;

import com.young.entity.User;
import com.young.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@Slf4j
public class Caffine02ApplicationTest {
    @Autowired
    private UserService userService;

    @Test
    public void testCache(){
        //获取缓存
        User user = userService.getUserById(1);
        log.info("第一次从数据库获取缓存:{}",user);
        user=userService.getUserById(1);
        log.info("第二次从缓存中获取:{}",user);
        //过期时间为10秒,我们10秒后再获取
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        user=userService.getUserById(1);
        log.info("10秒后再次获取user:{}",user);
    }
}

第一次获取user时,因为缓存中没有内容,所以会从数据库中查询,第二次会从缓存中获取到内容,然后睡眠10秒,此时缓存过期了,因此再次获取user的时候,会从数据库中获取,运行结果如下图所示:
在这里插入图片描述

3. Caffeine的四种类型的加载策略

3.1 Manual手动加载

我们修改CaffeineConfig.java

package com.young.config;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class CaffeineConfig {
    @Bean
    @Qualifier(value = "caffeineCache")
    public Cache caffeineCache(){
        return Caffeine.newBuilder()
                //设置10秒后过期,方便后续观察现象
                .expireAfterWrite(10, TimeUnit.SECONDS)
                //初始容量为100
                .initialCapacity(100)
                //最大容量为200
                .maximumSize(200)
                .build();
    }

	//定义manualCaffeineCache,用来演示手动加载
    @Bean
    @Qualifier(value = "manualCaffeineCache")
    public Cache manualCaffeineCache(){
        return Caffeine.newBuilder()
                .expireAfterWrite(10,TimeUnit.SECONDS)
                .initialCapacity(50)
                .maximumSize(100)
                .build();
    }
}

修改Caffeine02ApplicationTest.java,添加测试用例

package com.young;

import com.github.benmanes.caffeine.cache.Cache;
import com.young.entity.User;
import com.young.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
@Slf4j
public class Caffine02ApplicationTest {
    @Autowired
    private UserService userService;

    @Resource
    @Qualifier(value = "manualCaffeineCache")
    private Cache<String,String> manualCaffineCache;

    @Test
    public void testCache(){
        //获取缓存
        User user = userService.getUserById(1);
        log.info("第一次从数据库获取缓存:{}",user);
        user=userService.getUserById(1);
        log.info("第二次从缓存中获取:{}",user);
        //过期时间为10秒,我们10秒后再获取
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        user=userService.getUserById(1);
        log.info("10秒后再次获取user:{}",user);
    }

    @Test
    public void testManualCaffeineCache(){
        //将数据放入缓存
        manualCaffineCache.put("The best language","java");
        //获取key对应的value,如果不存在,返回null
        String the_best_language = manualCaffineCache.getIfPresent("The best language");
        System.out.println(the_best_language);
        //删除entry
        manualCaffineCache.invalidate("The best language");
        the_best_language= manualCaffineCache.getIfPresent("The best language");
        System.out.println(the_best_language);

        //以map的形式进行增删改查==================
        manualCaffineCache.asMap().put("best","java");
        manualCaffineCache.asMap().put("best1","SpringBoot");
        String best = manualCaffineCache.asMap().get("best");
        String best1 = manualCaffineCache.asMap().get("best1");
        System.out.println("best:"+best);
        System.out.println("best1:"+best1);
        //删除entry
        manualCaffineCache.asMap().remove("best");
        manualCaffineCache.asMap().remove("best1");
        best = manualCaffineCache.asMap().get("best");
        best1 = manualCaffineCache.asMap().get("best1");
        System.out.println("best:"+best);
        System.out.println("best1:"+best1);
    }
}

常用的方法:

getIfPresent(Object key): 获取value值,如果entry不存在,返回null
put(Object key,Object value): 添加entry到缓存中
invalidate(Object key): 删除entry
asMap(): 将cache以map的形式进行操作

测试testManualCaffeineCache,结果如下:
在这里插入图片描述

3.2 loading

LoadingCache通过关联一个CacheLoader来构建Cache, 当缓存未命中会调用CacheLoader的load方法生成V,还可以通过LoadingCache的getAll方法批量查询, 当CacheLoader未实现loadAll方法时, 会批量调用load方法聚合会返回。当CacheLoader实现loadAll方法时, 则直接调用loadAll返回。
我们在CaffeineConfig中添加下面的bean

   @Bean
    @Qualifier(value = "loadingCaffeineCache")
    public LoadingCache<String, Object> loadingCaffeineCache(){
        return Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.SECONDS)
                .maximumSize(500)
                .build(new CacheLoader<String, Object>() {
                    //缓存未命中时,使用下面的方法生成value
                    @Override
                    public @Nullable Object load(@NonNull String key) throws Exception {
                        User user=new User();
                        user.setId(-1);
                        user.setUsername(key);
                        user.setPassword(key);
                        return user;
                    }
                    @Override
                    public Map<String,Object>loadAll(Iterable<? extends String>keys){
                        Map<String,Object>map=new HashMap<>();
                        for (String key:keys){
                            User user=new User();
                            user.setId(-1);
                            user.setUsername(key);
                            user.setPassword(key);
                            map.put(key,user);
                        }
                        return map;
                    }
                });
    }

然后添加测试方法

 @Resource
    @Qualifier(value = "loadingCaffeineCache")
    private LoadingCache<String,Object> loadingCaffeineCache;

    @Test
    public void testLoadingCaffeineCache(){
        User user=new User();
        user.setId(1);
        user.setUsername("cxy");
        user.setPassword("123456");
        user.setAge(20);
        user.setSex("男");
        loadingCaffeineCache.put("1",user);
        User res=(User)loadingCaffeineCache.getIfPresent("1");
        System.out.println("res:"+res);
        res=(User)loadingCaffeineCache.getIfPresent("2");
        System.out.println("res:"+res);
        List<String>list= Arrays.asList("1","2","3");
        Map<@NonNull String, @NonNull Object> resMap = loadingCaffeineCache.getAllPresent(list);
        System.out.println("resMap:"+resMap);
        System.out.println("上面调用的都是IfPresent(),即存在才返回,因此不会触发我们刚才的两个load函数==========");
        res=(User)loadingCaffeineCache.get("2");
        System.out.println("res:"+res);
        resMap= loadingCaffeineCache.getAll(list);
        System.out.println("resMap:"+resMap);
    }

测试结果如下:
在这里插入图片描述

3.3 Asynchronous Manual异步手动

AsyncCache是另一种Cache,它基于Executor计算Entry,并返回一个CompletableFuture
和Cache的区别是, AsyncCache计算Entry的线程是ForkJoinPool线程池. 手动Cache缓存是调用线程进行计算

private static void demo() throws ExecutionException, InterruptedException {
		AsyncCache<String,String> cache = Caffeine.newBuilder()
			.maximumSize(500)
			.expireAfterWrite(10, TimeUnit.SECONDS)
			.buildAsync();

		// Lookup and asynchronously compute an entry if absent
		CompletableFuture<String> future = cache.get("hello", k -> createExpensiveGraph(k));
		System.out.println(future.get());
	}

	private static String createExpensiveGraph(String key){
		System.out.println("begin to query db..."+Thread.currentThread().getName());
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
		}
		System.out.println("success to query db...");
		return UUID.randomUUID().toString();
	}

3.4 异步自动

AsyncLoadingCache 是关联了 AsyncCacheLoader 的 AsyncCache

public static void demo() throws ExecutionException, InterruptedException {
		AsyncLoadingCache<String,String> cache = Caffeine.newBuilder()
			.expireAfterWrite(10, TimeUnit.SECONDS)
			.maximumSize(500)
			.buildAsync(k -> createExpensiveGraph(k));
		CompletableFuture<String> future = cache.get("hello");
		System.out.println(future.get());
	}

	private static String createExpensiveGraph(String key){
		System.out.println("begin to query db..."+Thread.currentThread().getName());
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
		}
		System.out.println("success to query db...");
		return UUID.randomUUID().toString();
	}

4. 配置监听器

修改caffeineConfig,添加监听器

 @Bean
    @Qualifier(value = "listenerCaffeineCache")
    public Cache listenerCaffeineCache(){
        return Caffeine.newBuilder()
                .expireAfterWrite(10,TimeUnit.SECONDS)
                .initialCapacity(100)
                .maximumSize(200)
                .evictionListener(new RemovalListener<String, Object>() {
                    @Override
                    public void onRemoval(@Nullable String key, @Nullable Object value, @NonNull RemovalCause removalCause) {
                        System.out.println("evictionListener:key="+key+",value="+value+",removalCause="+removalCause);
                    }
                }).removalListener((key,value,cause)->{
                    System.out.println("removalListener:key="+key+",value="+value+",cause="+cause);
                })
                .build();
    }

测试代码

@Resource
    @Qualifier(value = "listenerCaffeineCache")
    private Cache<String,Object>listenerCaffeineCache;

    @Test
    public void testListenerCaffeineCache() throws InterruptedException {
        listenerCaffeineCache.put("cxy","程序员");
        listenerCaffeineCache.put("best","java");
        listenerCaffeineCache.invalidate("cxy");
        listenerCaffeineCache.asMap().remove("best");
    }

测试结果如下:
在这里插入图片描述

参考文章:

Caffeine本地缓存详解
高性能缓存 Caffeine 原理及实战

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Spring Boot可以很方便地整合Caffeine缓存。Caffeine是一个高性能的Java缓存库,可以用于减少应用程序的响应时间和提高性能。在Spring Boot中,可以使用Spring Cache抽象来集成Caffeine缓存。只需在pom.xml文件中添加Caffeine依赖项,然后在应用程序中配置缓存管理器即可。可以使用@Cacheable注释来标记需要缓存的方法,并在需要时自动从缓存中获取数据。此外,还可以使用@CachePut注释来更新缓存中的数据,以及@CacheEvict注释来删除缓存中的数据。整合Caffeine缓存可以显著提高应用程序的性能和响应时间,特别是在处理大量数据时。 ### 回答2: Spring Boot是目前非常流行的基于Java的开源框架,主要用于在Web应用程序中创建应用程序,而Caffeine是一种基于Java的本地缓存框架,可以有效地提高应用程序的性能。在本文中,我们将讨论如何使用Spring BootCaffeine整合到我们的应用程序中。 Caffeine是一个类似于Guava缓存的本地缓存框架,但它在性能和代码大小方面更优。Caffeine使用基于内存的缓存,其中对象在缓存中以键值对的形式存储。当需要缓存对象时,应用程序将其存储在缓存中。下次应用程序需要访问缓存对象时,Caffeine将在缓存中查找该对象,而无需重新计算或访问磁盘。 使用Spring Boot整合Caffeine,我们需要执行以下步骤: 1.添加Caffeine依赖 我们需要在应用程序中添加Caffeine Maven或Gradle依赖项。以下是Maven依赖项的示例: ``` <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.8.5</version> </dependency> ``` 2.创建Caffeine缓存 在Spring Boot应用程序中创建Caffeine缓存是非常简单的。我们可以使用Caffeine类来创建缓存,并指定缓存的属性和采用的策略。以下是创建基本缓存的示例: ``` import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("cacheName"); } } ``` 在上面的示例中,我们创建了一个名为“cacheName”的缓存。我们还启用了Spring Boot的缓存支持@EnableCaching。 3.将Caffeine缓存用于方法 现在,我们已经创建了一个Caffeine缓存,可以在我们的应用程序中使用它。我们可以使用Spring Boot注释将缓存添加到我们的方法中,以便它们能够使用缓存数据而不是重复计算。以下是使用Caffeine缓存的方法示例: ``` @Cacheable(value = "cacheName", key = "#key") public Object getObject(String key) { // Some operations to fetch the object } ``` 在上面的示例中,我们使用@Cacheable注释将getObject()方法添加到缓存中。我们还指定了“cacheName”作为缓存的名称。 综上所述,Spring BootCaffeine整合是一种简单但有效的缓存解决方案。我们可以使用它轻松地将本地缓存添加到我们的应用程序中,以提高应用程序的性能和效率。 ### 回答3: Spring Boot是目前最为流行的Java Web开发框架之一,而Caffeine则是一种高效的Java缓存库,它可以将缓存对象存储在本地内存中,从而提高应用程序的性能。 如果将Spring BootCaffeine进行整合,就能够进一步提高应用程序的性能和效率。下面是整合的详细步骤: 第一步:添加Maven依赖 在Spring Boot应用程序的pom.xml文件中添加以下依赖: ``` <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>${caffeine.version}</version> </dependency> ``` 注意,需要将${caffeine.version}替换成实际的Caffeine版本号。 第二步:创建Caffeine缓存配置类 创建一个带有@Configuration注解的Java类,用于定义Caffeine缓存的配置和属性。 ``` @Configuration @EnableCaching public class CaffeineConfig { @Bean public Caffeine<Object, Object> caffeineConfig() { return Caffeine.newBuilder() .maximumSize(100) .expireAfterAccess(10, TimeUnit.MINUTES); } @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(caffeineConfig()); return cacheManager; } } ``` 在这个类中,我们使用了@EnableCaching注解来启用Spring的缓存支持。在@Bean注解下,我们定义了基本的Caffeine缓存配置。这里我们设置了最大缓存数为100个,缓存失效时间为10分钟。接着,我们使用@Bean注解创建一个缓存管理器实例,并将Caffeine配置传递给它。 第三步:定义目标缓存方法,并在方法上添加缓存注解 定义Spring应用程序的缓存逻辑方法,并在该方法上添加@Cacheable注解。这个注解可以用来告诉Spring将结果存储在缓存中,以便后续请求可以更快地检索结果。 ``` @Service public class UserServiceImpl implements UserService { @Override @Cacheable(value = "users", key = "#id") public User getUserById(Long id){ //从数据库获取用户 return userDao.getUserById(id); } } ``` 在这个示例中,通过@Cacheable注解,getUserById()方法的结果将被存储在名为users的缓存中。缓存的键值是方法的输入参数id。 总结 Spring Boot整合Caffeine非常简单。只需添加Maven依赖、创建Caffeine缓存配置类和添加缓存注解,就可以在Spring应用程序中实现高效的缓存处理。这种缓存处理可以非常好地提高应用程序的性能和效率,从而更好地满足用户需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值