springboot、cloud分布式缓存springCache结合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>

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

二、编写配置application.yml

spring:
  cache:
  	#指定缓存类型为redis
    type: redis
    redis:
      # 指定redis中的过期时间为1h
      time-to-live: 3600000
      # 每一个缓存的前缀,方便和普通使用的redis存的内容区分
      # 建议默认使用分区名作为前缀即可,这样数据在redis中以友好的:冒号在可视化软件中方便查看
      #key-prefix: CACHE_
      # 是否使用缓存名前缀,建议打开
      use-key-prefix: true
      # 是否缓存空值,可以有效防止缓存穿透
      cache-null-values: true
      # 是否开启缓存统计,默认就是false
      enable-statistics: false

三、开启缓存功能

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

//CacheProperties本身是没有注入spring容器的,为了能取到
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
//单纯的开启缓存把这个注解写在启动类上就行了
@EnableCaching
public class MyCacheConfig {

//    @Autowired
//    CacheProperties cacheProperties;

    /**
     * 使用自己的RedisCacheConfiguration,application.yml中的配置就不会应用上了
     * 原始详见org.springframework.boot.autoconfigure.cache的80行
     * 其中使其取值生效的写法:
     *      @ConfigurationProperties(prefix = "spring.cache")
     *      public class CacheProperties
     * 我使用注解 @EnableConfigurationProperties(CacheProperties.class)
     * 在形参上传入CacheProperties cacheProperties,它会自动找spring容器要这个对象
     * 也可以写个成员属性@Autowrite
     * @return
     */
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        //使存入redis中的 k-v 按我们指定的格式存入,默认的方式,Value会是jdk的序列化机制,Value根本没有可读性,这里使用的是阿里巴巴的通用型fastjson,也可以用org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;spring自带的通用json
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        //自己手动让application.yml中的配置生效
        //与源码一致,详细作用见源码
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }
}

四、认识注解

	@Cacheable    触发将数据保存到缓存的操作

 	@CacheEvict    触发将数据移除缓存的操作

 	@CachePut    更新缓存而不会干扰方法执行

 	@Caching    重新组合要在方法上应用的多个缓存操作

 	@CacheConfig    在类级别共享一些常见的缓存相关设置

五、简单使用

更新操作:

	/**
     * @CacheEvict:失效模式,key加单引号,不加单引号一律以为动态取值SpEl表达式
     * allEntries = true,表示删除category分区下的所有缓存
     * 所以约定:只要存储同一类型的数据,都可以指定到同一个分区,删除就可以批量删除
     * @CachePut:双写模式下使用,因为该方法没有返回值,所以不具备再次写入新值的能力
     * @Caching:组合操作
     * @param category
     */
    //@CacheEvict(value = "category",key = "'getLevel1Categorys'")
    //@CacheEvict(value = "category",allEntries = true)
    //@CachePut("………………")
    @Caching(evict = {
            @CacheEvict(value = "category",key = "'getLevel1Categorys'"), //一级菜单
            @CacheEvict(value = "category", key = "'getCatalogJson'") //所有菜单json格式
    })
    @Override
    @Transactional
    public void updateCategoryDetail(CategoryEntity category) {
    	//本表
        this.updateById(category);
        //同步更新其它关联表的冗余数据
        //分类名不为空
        if (!StringUtils.isEmpty(category.getName())) {
            //品牌分类关联表
            categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
        }
    }

查询操作:

 	/**
     * @Cacheable(value = {"分区名"}, key = "自定义,可以用SpEL表达式,#开头,也可以自己写,需要单引号")
     * @return
     */
	@Cacheable(value = {"category"}, key = "#root.methodName")
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        //找出所有1级分类
        List<CategoryEntity> level1Categorys = getParentCid(selectList, 0L);
        Map<String, List<Catelog2Vo>> map = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //当前拿到的是1级分类,查到它的2级分类
            List<CategoryEntity> level2 = getParentCid(selectList, v.getCatId());
            //封装上面的结果
            List<Catelog2Vo> collect = null;
            if (level2 != null && level2.size() > 0) {
                collect = level2.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //找当前二级分类的三级分类
                    List<CategoryEntity> level3 = getParentCid(selectList, l2.getCatId());
                    if (level3 != null && level3.size() > 0) {
                        //封装成指定格式
                        List<Catelog2Vo.Catelog3Vo> catelog3Vos = level3.stream().map(l3 -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(
                                    l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(catelog3Vos);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return collect;
        }));
        return map;
    }

六、SpringCache原理与不足

1)、读模式
	缓存穿透:查询一个null数据。解决方案:缓存空数据,可通过spring.cache.redis.cache-null-values=true
	缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;
	使用sync = true来解决击穿问题
	缓存雪崩:大量的key同时过期。解决:加随机时间。
2)、写模式:(缓存与数据库一致)
读写加锁。
	引入Canal,感知到MySQL的更新去更新Redis
	读多写多,直接去数据库查询就行
3)、总结:
	常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):
	写模式(只要缓存的数据有过期时间就足够了)
特殊数据:特殊设计

你可能感兴趣的:(spring,cloud,boot,缓存,redis,分布式)