从零开发短视频电商 缓存Cache实战Simple、Caffeine和Redis多缓存管理器

SpringBoot集成缓存Cache

文档:https://docs.spring.io/spring-boot/docs/2.3.12.RELEASE/reference/html/spring-boot-features.html#boot-features-caching

1.增加pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

支持以下几种缓存实现,默认自动根据代码依赖判断使用哪种实现,也可以在配置文件中强制指定使用哪种缓存实现。

当然也可以自实现 cacheManager,随便参照simple或者redis如何实现的即可。

2.启用缓存功能

在配置类中添加@EnableCaching来启用缓存功能

@Configuration
@EnableCaching
public class CachingConfig {
    
    
}

默认情况下,如果我们没有明确指定任何其他缓存,会自动根据依赖环境判断,如果上图的依赖都没有,它默认使用ConcurrentHashMap作为底层缓存。即Simple的类型,参考代码SimpleCacheConfiguration.javaConcurrentMapCacheManager.java

也可以通过配置文件直接指定:

spring:
  cache:
    type: simple

常见缓存操作

  • 使用注解@Cacheable等。
  • 使用缓存管理器CacheManager

缓存

@Cacheable(value = "user", key = "#id", unless="#result == null")
public User getUser(long id) {
    
    ...}

cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存。必填

key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。

keyGenerator:key的生成器;可以自己指定key的生成器的组件id,key/keyGenerator:二选一使用。

cacheManager:当自己配置了CacheManager后可以指定使用哪个缓存管理器,默认使用的是Springboot自动配置的缓存管理器;或者cacheResolver指定获取解析器。

condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存. condition="#userName.length()>2"

unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断。

sync:是否开启同步功能,默认不开启。开启后unless属性将不能使用。

getUser()将首先检查缓存是否存在,不存在则调用实际的方法,最后缓存结果;存在则直接返回缓存结果,跳过调用实际方法。

或者使用缓存管理器。

@Service
public class UserService {
    
    
    @Autowired
    CacheManager cacheManager;
    public User getUser(long id) {
    
    
        if(cacheManager.containsKey(id)) {
    
    
            return cacheManager.get(id);
        }
        // lookup address, cache result, and return it
    }
}

清除缓存

指定删除一个或多个或所有值,以便可以再次将新值加载到缓存中。

@CacheEvict(value="user", allEntries=true)
public User updateUser(long id) {
    
    ...}

allEntries:是否删除所有值。默认false,默认情况下,只删除关联键下的值。注意,不允许将这个参数设置为true并指定一个键。

beforeInvocation:默认false,是否应该在调用方法之前发生驱逐。将此属性设置为true,将导致驱逐发生,而不考虑方法的结果(即,是否抛出异常)。

​ 默认值为false,意味着缓存回收操作将在被建议的方法被成功调用后发生(也就是说,只有在调用没有抛出异常的情况下)。

或者使用缓存管理器

@Autowired
CacheManager cacheManager;
public void evictSingleCacheValue(String cacheName, String cacheKey) {
    
    
    cacheManager.getCache(cacheName).evict(cacheKey);
}
public void evictAllCacheValues(String cacheName) {
    
    
    cacheManager.getCache(cacheName).clear();
}
public void evictAllCaches() {
    
    
    cacheManager.getCacheNames().stream()
      .forEach(cacheName -> cacheManager.getCache(cacheName).clear());
}

更新缓存

实际我们不用这个,高并发下会导致更新丢失问题或者锁问题。这里仅做介绍哈

@CachePut(value="addresses")
public String getAddress(Customer customer) {
    
    ...}

组合缓存

@Caching(evict = {
    
     
  @CacheEvict("addresses"), 
  @CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {
    
    ...}

可以使用@Caching多个缓存注解组合,使用它来实现我们自己的自定义缓存逻辑。

类缓存配置

使用@CacheConfig注解,我们可以在类级别将一些缓存配置简化到一个地方

@CacheConfig(cacheNames={
    
    "addresses"})
public class CustomerDataService {
    
    
    @Cacheable
    public String getAddress(Customer customer) {
    
    ...}

SpEL上下文数据

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

注意:

1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如

@Cacheable(key = "targetClass + methodName +#p0")

2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:

@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")

SpEL提供了多种运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&,||,!,and,or,not,between,instanceof
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],1,$[…]

Cache实现之Redis缓存管理器

文档:https://spring.io/projects/spring-data-redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
需要引入commons-pool2作为连接池 必须!!!
<!--spring2.0集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

版本依赖如下

使用的JavaRedis客户端是Lettuce因为 Spring Boot 默认使用它。

application.yaml文件中配置属性:

spring:
  cache:
    # 指定使用redis 
    type: redis
  redis:
    # reids的连接ip
    host: 127.0.0.1
    port: 6379
    password: laker123
    # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
    database: 0
    # 连接超时时间(毫秒)
    timeout: 10000ms
    #  redis client配置,使用lettuce
    lettuce:
      pool:
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: 1000ms
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 8
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8

注意注意注意!!!,这里redis默认的序列化为JDK序列化,所以上面的User实体类一定要实现序列化public class User implements Serializable,否则会报java.io.NotSerializableException异常。

@Cacheable(value = "user", key = "#id", unless="#result == null")
public User getUser(long id) {
    
    ...}

getUser(1)其对应结果如下图:

这里我们后边会修改其默认的序列化为json格式。

框架会自动生成一个RedisTemplate 实例 ,我们也可以直接使用RedisTemplate操作缓存。

@Autowired
private RedisTemplate redisTemplate;
public void save(User user) {
    
    
    redisTemplate.opsForValue().set(user.getId(), user);
}
public User findById(Long id) {
    
    
    return (User)redisTemplate.opsForValue().get(id);
}

RedisTemplate 是线程安全的哦

默认情况下,Lettuce 会为我们管理序列化和反序列化,我们来自定义配置RedisTemplate

@Configuration
@EnableCaching
public class CachingConfig {
    
    
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
    
    
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setKeySerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        return redisTemplate;
    }
}

Jackson2JsonRedisSerializer和GenericJackson2JsonRedisSerializer的区别

  • 使用Jackson2JsonRedisSerializer需要指明序列化的类Class,可以使用Obejct.class

  • 使用GenericJacksonRedisSerializer比Jackson2JsonRedisSerializer效率低,占用内存高。

  • GenericJacksonRedisSerializer反序列化带泛型的数组类会报转换异常,解决办法存储以JSON字符串存储。

  • GenericJacksonRedisSerializer和Jackson2JsonRedisSerializer都是以JSON格式去存储数据,都可以作为Redis的序列化方式。

来自:https://blog.csdn.net/bai_bug/article/details/81222519

redisTemplate.opsForValue().set(id,user);

回归Cache中RedisCacheManager自定义配置如下:

方式一 RedisCacheConfiguration

@Bean
public RedisCacheConfiguration cacheConfiguration() {
    
    
    return RedisCacheConfiguration.defaultCacheConfig()
      .entryTtl(Duration.ofMinutes(60))
      .disableCachingNullValues()
      .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}

方式二 RedisCacheManagerBuilderCustomizer

@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
    
    
    return (builder) -> builder
      .withCacheConfiguration("itemCache",
        	RedisCacheConfiguration.defaultCacheConfig()
                              .entryTtl(Duration.ofMinutes(10)))
      .withCacheConfiguration("customerCache",
        	RedisCacheConfiguration.defaultCacheConfig()
                              .entryTtl(Duration.ofMinutes(5)));
}

分别为itemCachecustomerCache配置了 10 分钟和 5 分钟的 TTL 值。

方式三 CachingConfigurerSupport

扩展CachingConfigurerSupport类并覆盖cacheManager () 方法。此方法返回一个 bean,它将成为我们应用程序的默认缓存管理器:

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    
    
  	@Override
	public CacheManager cacheManager() {
    
    ...}
	@Override
	public CacheResolver cacheResolver() {
    
    ...}
	@Override
	public KeyGenerator keyGenerator() {
    
    ...}
	@Override
	public CacheErrorHandler errorHandler() {
    
    ...}
}

配置多个缓存管理器并动态切换

在某些情况下,我们可能需要在应用程序中使用多个缓存管理器。

配置类中创建两个缓存管理器 bean。配置其中的一个 bean为@Primary

@Configuration
@EnableCaching
public class MultipleCacheManagerConfig {
    
    
    @Bean
    @Primary
    public CacheManager cacheManager() {
    
    
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("customers", "orders");
        cacheManager.setCaffeine(Caffeine.newBuilder()
          .initialCapacity(200)
          .maximumSize(500)
          .weakKeys()
          .recordStats());
        return cacheManager;
    }
    @Bean
    public CacheManager alternateCacheManager() {
    
    
        return new ConcurrentMapCacheManager("customerOrders", "orderprice");
    }
}

现在,Spring Boot 将使用CaffeineCacheManager作为所有缓存方法的默认值,直到我们为一个方法明确指定了AlternateCacheManager

@Cacheable(cacheNames = "customers")
public Customer getCustomerDetail(Integer customerId) {
    
    
    return customerDetailRepository.getCustomerDetail(customerId);
}
@Cacheable(cacheNames = "customerOrders", cacheManager = "alternateCacheManager")
public List<Order> getCustomerOrders(Integer customerId) {
    
    
    return customerDetailRepository.getCustomerOrders(customerId);
}

整体示例

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    
    
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
    
    
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setKeySerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
    
    
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1)) //设置所有缓存有效时间 为 1个小时。
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer))
                .disableCachingNullValues(); // 禁用缓存空值.
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();
        return cacheManager;
    }
}

Cache实现之Caffeine缓存管理器

CaffeineCacheManagerspring-boot-starter-cache starter 提供。如果依赖存在Caffeine,它将由 Spring 自动配置, Caffeine是一个用 Java 8 编写的缓存库。

增加依赖Caffeine
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

可以自己定制配置,控制缓存行为的主要配置,例如过期、缓存大小限制等

@Bean
public Caffeine caffeineConfig() {
    
    
    return Caffeine.newBuilder()
               .expireAfterWrite(60, TimeUnit.MINUTES);
}
@Bean
public CacheManager cacheManager(Caffeine caffeine) {
    
    
    CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
    caffeineCacheManager.setCaffeine(caffeine);
    return caffeineCacheManager;
}

参考:

  • https://www.baeldung.com/spring-boot-redis-cache

  1. ↩︎

Guess you like

Origin blog.csdn.net/abu935009066/article/details/122369133