Spring Boot - 实用功能15 - Caffeine缓存加持

caffeine本地缓存

一:本地缓存Caffeine介绍

一般情况下,缓存针对的主要是读操作。当你的功能遇到下面的场景时,就可以选择使用缓存组件进行性能优化:

  • 存在数据热点,缓存的数据能够被频繁使用;
  • 读操作明显比写操作要多;
  • 下游功能存在着比较悬殊的性能差异,下游服务能力有限;
  • 加入缓存以后,不会影响程序的正确性,或者引入不可预料的复杂性

日常开发中,基本上每个项目中都会使用到Redis、MongoDB等缓存中间件

它能够很好的作为分布式缓存组件提供多个服务间的缓存,但是还是需要网络开销,增加时耗

除了分布式缓存,其实还有一种缓存 - 本地缓存:直接从本地内存中读取,没有网络开销,在某些场景比远程缓存更合适。

Guava cache、ehcache、Caffeine是目前比较流行的本地缓存组件,但Caffeine号称是本地缓存绝对的王者

Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库。缓存和ConcurrentMap有点相似,但还是有所区别,最根本的区别是ConcurrentMap将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。

Caffeine的底层使用了ConcurrentHashMap,支持按照一定的规则或者自定义的规则使缓存的数据过期,然后销毁。

在 Spring5 (springboot 2.x) 后,Spring 官方放弃了 Guava,而使用了性能更优秀的 Caffeine 作为默认缓存组件

二:Caffeine功能和性能

Caffeine提供了多种灵活的构造方法,从而可以创建多种特性的本地缓存。

  • 自动把数据加载到本地缓存中,并且可以配置异步
  • 基于数量剔除策略
  • 基于失效时间剔除策略,这个时间是从最后一次操作算起【访问或者写入】
  • 异步刷新
  • Key会被包装成Weak引用
  • Value会被包装成Weak或者Soft引用,从而能被GC掉,而不至于内存泄漏
  • 数据剔除提醒
  • 写入广播机制
  • 缓存访问可以统计

不管在并发读、并发写还是并发读写的场景下,Caffeine 的性能都大幅领先于其他本地开源缓存组件

三:Caffeine 配置说明

Caffeine主要提供了以下一些配置:

  • initialCapacity=[integer]: 设置初始缓存的空间大小;
  • maximumSize=[long]: 设置缓存的最大条数;
  • maximumWeight=[long]: 设置缓存的最大权重;
  • expireAfterAccess=[持续时间]: 最后一次写入或者访问后经过多久时间过期;
  • refreshAfterWrite=[持续时间]: 创建缓存或者最后一次更新缓存后经过多久时间间隔,刷新缓存;
  • weakKeys: 打开key的弱引用;
  • weakValues: 打开value的弱引用;
  • softValues: 打开value的软引用;
  • recordStats: 打开统计功能;

注意下面的问题

  • weakValues 和 softValues 不可以同时使用
  • maximumSize 和 maximumWeight 不可以同时使用
  • expireAfterWrite 和 expireAfterAccess 同时存在时,以expireAfterWrite为准

四:SpringBoot多级缓存

首先我们要明白为什么要使用多级缓存?

  • 如果只使用redis来做缓存我们会有大量的请求到redis,但是每次请求的数据都是一样的,假如这一部分数据就放在应用服务器本地,那么就省去了请求redis的网络开销,请求速度就会快很多;
  • 如果只使用Caffeine来做本地缓存,我们的应用服务器的内存是有限,并且单独为了缓存去扩展应用服务器是非常不划算。所以,只使用本地缓存也是有很大局限性的;

因此在项目中,我们可以将热点数据放本地缓存,作为一级缓存,将非热点数据放redis缓存,作为二级缓存,减少Redis的查询压力。

使用流程大致如下:

  1. 首先从一级缓存(caffeine-本地应用内)中查找数据;
  2. 如果没有的话,则从二级缓存(redis-内存)中查找数据;
  3. 如果还是没有的话,再从数据库(数据库-磁盘)中查找数据;

SpringBoot 有两种使用 Caffeine 作为缓存的方式:

  1. 方式一:直接引入 Caffeine 依赖,然后使用 Caffeine 方法实现缓存;
  2. 方式二:引入 Caffeine 和 Spring Cache 依赖,使用 SpringCache 注解方法实现缓存;

方式二举例

第一步:依赖 & 配置

<!-- caffeine -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>2.7.13</version>
</dependency>
spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=50,expireAfterWrite=30s

第二步:本地缓存配置类

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
 * <p>
 * 功能描述:咖啡因进程内缓存配置
 * </p>
 *
 * @author cui haida
 * @date 2024/07/03/10:39
 */
@Configuration
@ConditionalOnExpression("'${spring.cache.type}'.equals('caffeine')") // 当前仅当spring.cache.type=caffeine时生效
public class CaffeineCacheConfig {
    /**
     * 这个方式一般封装成为工具类
     * 存储
     * Cache<String, Object> caffeine = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
     * caffeine.put(key, value);
     * 获取
     * Cache<String, Object> caffeine = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
     * Object value = caffeine.asMap().get(key);
     */
    @Bean
    public Cache<String, Object> caffeineCache() {
        // 创建一个名为"myCache"的缓存,使用caffeineConfig()方法获取的Caffeine配置。
        return Caffeine.newBuilder()
                .maximumSize(50)
                .expireAfterWrite(Duration.ofSeconds(30))
                .build();
    }


    /**
     * 配置缓存管理器。
     * 使用CaffeineCacheManager来管理缓存,它基于Caffeine构建,提供了缓存的创建和管理能力。
     * 配置Caffeine缓存。
     *
     * 方式一:@Cacheable(value = "xxx")
     * 优点:通过配置不同的缓存名称,可以实现每个缓存大小独立管理
     * 问题:面这种方式,无法手动set缓存,只有在获取的时候,才会塞缓存进去
     * 方式二:CacheManager类
     * Cache caffeineCacheBuilder = cacheManager.getCache("xxx");
     * caffeineCacheBuilder.put(key, value);
     *
     * @return 返回配置好的CaffeineCacheManager实例。
     */
    @Bean(name = "myCacheManager")
    public CacheManager myCacheManager() {
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder().maximumSize(50)
                // 设置缓存条目的过期时间,这里设置为30秒,超过30秒未访问的缓存条目将被自动移除。
                .expireAfterWrite(Duration.ofSeconds(30));
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(caffeine);
        return cacheManager;
    }
}

第三步:主启动类中加入@EnableCache

@EnableCaching
@SpringBootApplication
public class ParagraphSplittingTesterApplication {

    public static void main(String[] args) {
        SpringApplication.run(ParagraphSplittingTesterApplication.class, args);
    }
}

第四步:使用@CacheConfig & @Cacheable

package cn.com.chnsys.logic.impl;

import cn.com.chnsys.entity.ParagraphNameEntity;
import cn.com.chnsys.logic.ParagraphLogic;
import cn.com.chnsys.logic.ParagraphNameLogic;
import cn.com.chnsys.pojo.dto.ParagraphEntityDTO;
import cn.com.chnsys.service.ParagraphNameService;
import cn.com.chnsys.service.ParagraphService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * 段落名称实现类
 */
@Component
@CacheConfig(cacheNames = "paragraphName")
public class ParagraphNameLogicImpl implements ParagraphNameLogic {

    @Resource
    private ParagraphNameService paragraphNameService;

    /**
     * 获取段落名称列表
     * @return 段落名称列表
     */
    @Override
    @Cacheable(key = "'paragraphNameList' + #paragraphType", cacheManager = "myCacheManager")
    public List<ParagraphNameEntity> getParagraphNameList(String paragraphType) {
        LambdaQueryWrapper<ParagraphNameEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ParagrphNameEntity::getFileType, paragraphType);
        return paragraphNameService.list(queryWrapper);
    }
}

下面以第一种方式为例说下多级缓存的实现过程

第一步:依赖 & 配置

<!--caffeine-->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 500
        min-idle: 0
    lettuce:
      shutdown-timeout: 0

第二步:本地缓存配置类

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import java.util.concurrent.TimeUnit;
 
/**
 * 本地缓存Caffeine配置类
 */
@Configuration
public class LocalCacheConfiguration {
 
    @Bean("localCacheManager")
    public Cache<String, Object> localCacheManager() {
        return Caffeine.newBuilder()
                //写入或者更新5s后,缓存过期并失效, 实际项目中肯定不会那么短时间就过期,根据具体情况设置即可
                .expireAfterWrite(5, TimeUnit.SECONDS)
                // 初始的缓存空间大小
                .initialCapacity(50)
                // 缓存的最大条数,通过 Window TinyLfu算法控制整个缓存大小
                .maximumSize(500)
            	//打开数据收集功能
                .recordStats()
                .build();
    }
}

第三步:redis缓存配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
 
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //关联
        template.setConnectionFactory(factory);
        //设置key的序列化方式
//        template.setKeySerializer();
        //设置value的序列化方式
//        template.setValueSerializer();
        return template;
    }
}

第四步:定义实体对象 <- 缓存的Value

// 省略全参构造器,get & set,toString
public class User implements Serializable {
    private String id;
    private String name;
}

第五步:定义Service层

public interface UserService {
    // add
    void add(User user);
    // 通过id查找
    User getById(String id);
    // 更新
    User update(User user);
    // 删除
    void deleteById(String id);
}
import com.alibaba.fastjson.JSON;
import com.github.benmanes.caffeine.cache.Cache;
import com.wsh.springboot_caffeine.entity.User;
import com.wsh.springboot_caffeine.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
 
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
 
@Service
public class UserServiceImpl implements UserService {
    /**
     * 模拟数据库存储数据
     */
    private static HashMap<String, User> userMap = new HashMap<>();
    private final RedisTemplate<String, Object> redisTemplate;
    private final Cache<String, Object> caffeineCache;
 
    @Autowired
    public UserServiceImpl(RedisTemplate<String, Object> redisTemplate, @Qualifier("localCacheManager") Cache<String, Object> caffeineCache) {
        this.redisTemplate = redisTemplate;
        this.caffeineCache = caffeineCache;
    }
 
    static {
        userMap.put("1", new User("1", "zhangsan"));
        userMap.put("2", new User("2", "lisi"));
        userMap.put("3", new User("3", "wangwu"));
        userMap.put("4", new User("4", "zhaoliu"));
    }
 
 
    @Override
    public void add(User user) {
        // 1.保存Caffeine缓存
        caffeineCache.put(user.getId(), user);
 
        // 2.保存redis缓存
        redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);
 
        // 3.保存数据库(模拟)
        userMap.put(user.getId(), user);
    }
 
    @Override
    public User getById(String id) {
        // 1.先从Caffeine缓存中读取
        Object o = caffeineCache.getIfPresent(id);
        if (Objects.nonNull(o)) {
            System.out.println("从Caffeine中查询到数据...");
            return (User) o;
        }
 
        // 2.如果缓存中不存在,则从Redis缓存中查找
        String jsonString = (String) redisTemplate.opsForValue().get(id);
        User user = JSON.parseObject(jsonString, User.class);
        if (Objects.nonNull(user)) {
            System.out.println("从Redis中查询到数据...");
 
            // 保存Caffeine缓存
            caffeineCache.put(user.getId(), user);
            return user;
        }
 
        // 3.如果Redis缓存中不存在,则从数据库中查询
        user = userMap.get(id);
        if (Objects.nonNull(user)) {
            // 保存Caffeine缓存
            caffeineCache.put(user.getId(), user);
 
            // 保存Redis缓存,20s后过期
            redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);
        }
        System.out.println("从数据库中查询到数据...");
        return user;
    }
 
    @Override
    public User update(User user) {
        User oldUser = userMap.get(user.getId());
        oldUser.setName(user.getName());
        // 1.更新数据库
        userMap.put(oldUser.getId(), oldUser);
 
        // 2.更新Caffeine缓存
        caffeineCache.put(oldUser.getId(), oldUser);
 
        // 3.更新Redis数据库
        redisTemplate.opsForValue().set(oldUser.getId(), JSON.toJSONString(oldUser), 20, TimeUnit.SECONDS);
        return oldUser;
    }
 
    @Override
    public void deleteById(String id) {
        // 1.删除数据库
        userMap.remove(id);
 
        // 2.删除Caffeine缓存
        caffeineCache.invalidate(id);
 
        // 3.删除Redis缓存
        redisTemplate.delete(id);
    }
 
}

使用方式二进行多级缓存实现

第一步:依赖 & 配置

<!--spring cache-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
  cache:
    type: caffeine
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 500
        min-idle: 0
    lettuce:
      shutdown-timeout: 0

第二步:caffeine自动配置类,注意:如果使用了多个cahce,比如redis、caffeine等,必须指定某一个CacheManage为@Primary,在@Cacheable注解中没指定 cacheManager 的话,则使用标记为primary的那个。

/**
 * @Description: Caffeine自动配置类
 */
//自动配置功能
@Configuration
//开启缓存功能
@EnableCaching
public class CaffeineCacheConfig {
 
    @Bean
    @Primary
    public CacheManager caffeineCacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        List<CaffeineCache> caches = CaffeineCacheInitializer.initCaffeineCache();
        if (CollectionUtils.isEmpty(caches)) {
            return cacheManager;
        }
        cacheManager.setCaches(caches);
        return cacheManager;
    }
 
}
 
/**
 * @Description: CaffeineCache初始化器
 */
public class CaffeineCacheInitializer {
 
    public static List<CaffeineCache> initCaffeineCache() {
        List<CaffeineCache> caffeineCacheList = new ArrayList<>();
        CaffeineCache userCache = new CaffeineCache(CacheKey.USER_CACHE_KEY, Caffeine.newBuilder().recordStats()
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .maximumSize(100)
                .build());
        caffeineCacheList.add(userCache);
 
        //将所有需要定义的CaffeineCache添加到容器中
        //....
        return caffeineCacheList;
    }
}
 
/**
 * @Description: 缓存Key常量,统一维护
 */
public class CacheKey {
    public static final String USER_CACHE_KEY = "userCache";
}

第三步:redis配置类

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //关联
        template.setConnectionFactory(factory);
        //设置key的序列化方式
//        template.setKeySerializer();
        //设置value的序列化方式
//        template.setValueSerializer();
        return template;
    }
}

第四步:接口实现类

import com.alibaba.fastjson.JSON;
import com.wsh.springboot_caffeine2.constant.CacheKey;
import com.wsh.springboot_caffeine2.entity.User;
import com.wsh.springboot_caffeine2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
 
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
 
@Service
public class UserServiceImpl implements UserService {
    /**
     * 模拟数据库存储数据
     */
    private static HashMap<String, User> userMap = new HashMap<>();
    private final RedisTemplate<String, Object> redisTemplate;
 
    @Autowired
    public UserServiceImpl(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
 
    static {
        userMap.put("1", new User("1", "zhangsan"));
        userMap.put("2", new User("2", "lisi"));
        userMap.put("3", new User("3", "wangwu"));
        userMap.put("4", new User("4", "zhaoliu"));
    }
 
    @Override
    // 1.保存Caffeine缓存  注意必须返回User对象出去,如果是void的话,Caffeine并不能帮我们存入缓存中
    @CachePut(value = CacheKey.USER_CACHE_KEY, key = "#user.id")
    public User add(User user) {
        // 2.保存redis缓存
        redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);
 
        // 3.保存数据库(模拟)
        userMap.put(user.getId(), user);
        return user;
    }
 
    @Override
    // 1.先从Caffeine缓存中读取
    @Cacheable(value = CacheKey.USER_CACHE_KEY, key = "#id", sync = true)
    public User getById(String id) {
        // 2.如果缓存中不存在,则从Redis缓存中查找
        String jsonString = (String) redisTemplate.opsForValue().get(id);
        User user = JSON.parseObject(jsonString, User.class);
        if (Objects.nonNull(user)) {
            System.out.println("从Redis中查询到数据...");
            return user;
        }
 
        // 3.如果Redis缓存中不存在,则从数据库中查询
        user = userMap.get(id);
        if (Objects.nonNull(user)) {
            // 保存Redis缓存,20s后过期
            redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);
        }
        System.out.println("从数据库中查询到数据...");
        return user;
    }
 
    @Override
    //1.更新Caffeine缓存
    @CachePut(value = CacheKey.USER_CACHE_KEY, key = "#user.id")
    public User update(User user) {
        User oldUser = userMap.get(user.getId());
        oldUser.setName(user.getName());
 
        // 2.更新数据库
        userMap.put(oldUser.getId(), oldUser);
 
        // 3.更新Redis数据库
        redisTemplate.opsForValue().set(oldUser.getId(), JSON.toJSONString(oldUser), 20, TimeUnit.SECONDS);
        return oldUser;
    }
 
    @Override
    //1.删除Caffeine缓存
    @CacheEvict(value = CacheKey.USER_CACHE_KEY, key = "#id")
    public void deleteById(String id) {
        // 2.删除数据库
        userMap.remove(id);
 
        // 3.删除Redis缓存
        redisTemplate.delete(id);
    }
}

五:API说明

  • caffeineCache.put(user.getId(), user):保存本地缓存;
  • caffeineCache.invalidate(id):移除指定的本地缓存;
  • caffeineCache.getIfPresent(id): 从本地缓存中获取值,如果缓存中不存指定的值,则方法将返回 null;
  • caffeineCache.get(id, Function<>): 从本地缓存中获取值,该方法还支持将一个参数为 key 的 Function 作为参数传入。如果缓存中不存在该 key,则该函数将用于提供默认值,该值在计算后插入缓存中,如果缓存的元素无法生成或者在生成的过程中抛出异常而导致生成元素失败,则返回null。
//手动加载
caffeineCache.get(id, new Function<String, Object>() {
    @Override
    public Object apply(String s) {
        return "hello world";
    }
});

// 或者简写成下面这样:
caffeineCache.get(id, s -> "hello world");

caffeine LoadingCache同步加载:

// 同步加载数据指的是,在get不到数据时最终会调用build构造时提供的CacheLoader对象中的load函数
// 如果返回值则将其插入缓存中,并且返回
LoadingCache<Integer, Integer> loadingCache = Caffeine.newBuilder()
    .expireAfterWrite(20, TimeUnit.SECONDS)
    .maximumSize(500)
    // 在get不到数据时最终会调用build构造时提供的CacheLoader对象中的load函数
    .build(new CacheLoader<Integer, Integer>() {
        @Override
        public Integer load(Integer key) {
            return key;
        }
    });

caffeine AsyncCache异步加载:

// 使用executor设置线程池
AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
    .expireAfterWrite(20, TimeUnit.SECONDS)
    .maximumSize(500)
    .executor(Executors.newFixedThreadPool(5)) //当然也可以使用自定义线程池实现
    .buildAsync();

// 具体使用
CompletableFuture<String> completableFuture = asyncCache.get("1", new Function<String, String>() {
    @Override
    public String apply(String s) {
        //执行所在的线程是ForkJoinPool线程池提供的线程
        return "hello world";
    }
});
completableFuture.get();

六:Caffeine的软引用与弱引用

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

注意:key 支持弱引用,而 value 则支持弱引用和软引用。需要注意的是,AsyncCache 不支持软引用和弱引用。

// 软引用,当进行GC的时候进行回收
Caffeine.newBuilder().softValues().build(); 

// 弱引用,当key和缓存元素都不再存在其他强引用的时候回收
Caffeine.newBuilder().weakKeys().weakValues().build();
### spring-boot-starter-data-redis 与 spring-boot-starter-cache 的区别及应用场景 #### 区别 `spring-boot-starter-data-Redis` 主要用于与 Redis 数据库交互,提供了一套完整的操作 Redis 的 API 支持[^1]。它不仅限于缓存场景,还可以作为消息队列、发布/订阅模式等多种用途的数据存储解决方案。 而 `spring-boot-starter-cache` 则专注于实现应用级别的缓存抽象层,旨在简化开发者在项目中加入缓存机制的过程[^2]。此模块并不直接关联任何特定类型的持久化技术;相反,它是通过 SPI (Service Provider Interface) 来支持多种不同的缓存提供商,比如 EhCache, Caffeine 或者 Redis 等。 因此,在引入 `spring-boot-starter-cache` 后还需要额外指定具体的缓存实现方式——如果选择了 Redis,则通常会配合使用 `spring-boot-starter-data-redis` 提供的支持来完成实际的操作逻辑[^4]。 #### 应用场景 对于希望利用 Redis 进行复杂数据结构管理和分布式环境下的高性能读写需求的应用来说,应该优先考虑采用 `spring-boot-starter-data-redis` 。这使得开发人员能够充分利用 Redis 所提供的诸如列表(lists),集合(sets), 排序集(sorted sets)等功能特性[^3]。 另一方面,当只需要简单地提升热点查询效率或是减少数据库负载压力时,可以选择仅使用 `spring-boot-starter-cache` 并搭配合适的本地或远程缓存方案即可满足业务要求。这种方式下,即使不涉及复杂的键值对操作也能有效改善系统的响应时间和吞吐量表现。 ```java // 使用 spring-boot-starter-data-redis 实现简单的字符串设置和获取 @Autowired private StringRedisTemplate stringRedisTemplate; public void setKeyValue(String key, String value){ stringRedisTemplate.opsForValue().set(key,value); } public String getValueByKey(String key){ return stringRedisTemplate.opsForValue().get(key); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值