美文网首页
SpringCache

SpringCache

作者: 乙腾 | 来源:发表于2020-12-01 21:21 被阅读0次

Introduce

Spring 3.1 引入了激动人心的基于凝视(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(比如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中加入少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

springCache 是spring整合的

image.png

缓存官方文档

https://docs.spring.io/spring-framework/docs/5.2.11.RELEASE/spring-framework-reference/integration.html#cache

image.png

Cache Abstraction 缓存抽象,如其名字,它本质上不是一个具体的缓存实现方案(比如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中加入少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

不用SpringCache的缓存

 //缓存改写2:使用redis作为本地缓存
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)) {
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoriesDb();
            String toJSONString = JSON.toJSONString(categoriesDb);
            ops.set("catalogJson",toJSONString);
           return categoriesDb;
       }
       Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
      return listMap;

使用SpringCache

pom

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--jedis客户端-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<!--缓存-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

redis.yml

spring:
  redis:
    host: 192.168.16.129
    port: 6379
  cache:
    type: redis
    redis:
      time-to-live: 3600000
      #是否缓存空值,防止缓存穿透
      cache-null-values: true

bootstrap-dev.properties

spring.profiles.active=dev
spring.application.name=gulimail-product-config
spring.cloud.nacos.config.server-addr=xxxxxx:8848
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.group=gulimail_product_group
spring.cloud.nacos.config.namespace=72616c9f-6977-460d-9a1b-b43859b57beb

spring.cloud.nacos.config.ext-config[0].data-id=gulimail-product-redis-dev.yaml
spring.cloud.nacos.config.ext-config[0].group=gulimail_product_group
spring.cloud.nacos.config.ext-config[0].refresh=true

配置类

package com.pl.gulimail.product.config;

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@EnableConfigurationProperties(CacheProperties.class)//不加这个注解,配置文件不生效
@Configuration
@EnableCaching
public class MyCacheConfig {
    //要想配置文件生效 @EnableConfigurationProperties(CacheProperties.class)
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        //序列化key value
        config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //过期时间
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        //缓存件有没有前缀
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        //是否缓存空数据
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        //是否用缓存前缀
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }
}

开启缓存

@EnableCaching

package com.pl.gulimail.product;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@EnableCaching
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.pl.gulimail.product.sql.dao")
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients(basePackages = {"com.pl.gulimail.product.sql.feign"})
public class GulimalProductApplication {

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

}

测试使用缓存

  • @Cacheable: Triggers cache population. 处罚将数据保存到缓存的操作。
  • @CacheEvict: Triggers cache eviction. 触发将数据从缓存中删除的操作。
  • @CachePut: Updates the cache without interfering with the method execution. 不影响方法执行更行缓存。
  • @Caching: Regroups multiple cache operations to be applied on a method. 组合史上多个操作。
  • @CacheConfig: Shares some common cache-related settings at class-level.在类级别共享缓存配置。

code

读模式

//value 缓存分区 按照业务类型分区,
//@Cacheable(value = {"category","sss"},key = "#root.methodName",sync = true)  这条数据往category,sss两个区中存放数据
@Cacheable(value = {"category"})
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithSpringCache() {
    return getCategoriesDb();
}

SimpleKey是默认给的名字,数据是jdk序列化的样子


image.png

指定key并引入配置类,value数据为json格式后

//value 缓存分区 按照业务类型分区,
//@Cacheable(value = {"category","sss"},key = "#root.methodName",sync = true)  这条数据往category,sss两个区中存放数据
@Cacheable(value = {"category"},key = "#root.methodName",sync = true)
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithSpringCache() {
    return getCategoriesDb();
}

可以发现key为方法名,TTL失效时间也按照配置文件中指定的一样生效了。该方法只有在第一次访问或者缓存失效后会被调用查询数据库,其余时间直接从缓存中读取数据。

image.png
image.png

notice:

n1使用@Cacheable的特点:

1.如果方法中有姐不在调用了。

2.缓存的数据,默认的key是默认生成的,缓存名字:: SimpleKey(自动生成的key值)

3.缓存的value,默认使用jdk序列化机制,将序列化后的数据缓存到redis。

4.默认ttl过期时间 -1。永不过期。

自定义:

1.指定生成缓存中的key(注解上指定key)

2.指定缓存存活时间

3.将数据保存为json格式

key:

image.png

支持SqEL动态获取key,

如果想用方法名作为key

 #root.methodName

如果想获取方法的参数
{@code #root.args[1]}, {@code #p1} or {@code #a1}

配置文件中指定前缀

image.png
测试缓存空值和指定前缀
image.png
不使用缓存前缀
image.png

没有前缀,只显示指定的key

image.png

写模式

指定删除指定的一个key

/**
* 级联更新所有数据
* CacheEvict: 失效模式
* @param category
*/
@Override
@Transactional
@CacheEvict(value = {"category"},key = "'getCatalogJsonDbWithSpringCache'") //指定删除哪个分区,哪个key
public void updateCascade(CategoryEntity category) {
    this.updateById(category);
}

key是遵循SqEL表达式的,字符串需要加单引号。

执行更行操作

image.png image.png

指定删除多个key

/**
* 级联更新所有数据
* CacheEvict: 失效模式
* @param category
*/
@Override
@Transactional  
@Caching(evict = {
@CacheEvict(value = {"category"},key = "'getCategoryLeve1'"),
@CacheEvict(value = {"category"},key = "'getCatalogJsonDbWithSpringCache'")
})
public void updateCascade(CategoryEntity category) {
  this.updateById(category);
}

指定删除区中的的所有key

/**
* 级联更新所有数据
* CacheEvict: 失效模式
* @param category
*/
@Override
@Transactional
@CacheEvict(value = {"category"},key = "'getCatalogJsonDbWithSpringCache'",allEntries = true)
public void updateCascade(CategoryEntity category) {
    this.updateById(category);
}

双写模式

这次更新完事后,将数据放到缓存中一份,方法中必须有返回值

/**
* 级联更新所有数据
* CacheEvict: 失效模式
* @param category
*/
@Override
@Transactional
@CacheEvict(value = {"category"},key = "'getCatalogJsonDbWithSpringCache'",allEntries = true)
@cachePut
public CategoryEntity updateCascade(CategoryEntity category) {
    return this.updateById(category);
}

Spring-Cache的不足之处

1.读模式(@Cacheable)

缓存穿透:查询一个null数据。解决方案:缓存空数据,可通过spring.cache.redis.cache-null-values=true

缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;

使用sync = true来解决击穿问题

缓存雪崩:大量的key同时过期。解决:加随机时间。加上过期时间,即使通过配置文件指定了相同的随机时间,但是每次出发写缓存的时间都不一致,所以就算TTL时间相同,但是彼此失效的时间不同。

可以看出Spring-Cache对读操作的所有问题,都是有对应的解决办法的。

notice:

只有@Cacheable有锁,其他注解没有加锁的属性。

<wiz_code_mirror><pre class=" CodeMirror-line " role="presentation">@Cacheable(value = {"category"},key = "#root.methodName",sync = true)</pre></wiz_code_mirror>

2.写模式:(缓存与数据库一致)

因为@CacheEvict,@CachePut都没有锁属性,所以都不可以加锁,这些注解对于缓存雪崩的场景是没法应对的。

解决办法:

a、读写加锁。

b、引入Canal,感知到MySQL的更新去更新Redis

c 、读多写多,直接去数据库查询就行

3.总结:

常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):

写模式(只要缓存的数据有过期时间就足够了)

特殊数据:特殊设计

相关文章

网友评论

      本文标题:SpringCache

      本文链接:https://www.haomeiwen.com/subject/eplewktx.html