一、缓存的概念

首先引入缓存还是有几个问题要问的
1.为什么要用缓存
2.项目中缓存是如何使用的
3.常见的缓存问题
带着这几个问题进行下面的学习

为什么需要缓存?

我们用缓存主要有两个原因
1,缓存是位于内存中的,而内存天然对并发有出色的支持力(高并发)
2,在缓存中的数据读取速度远远高于数据库,极大的提高了程序的性能

项目中如何使用缓存?

缓存一般用在非实时变化的数据上,当一个请求过来通过DB查询用了很长时间,后面这个数据几个小时可能都不会改变,这个时候我们便可以定义一个Key,把数据放到value中存入缓存。下次再查询的时候直接走缓存,性能上会有巨大的提升。

在高并发的场景下,Mysql数据库是完全不支持的,如果项目高峰期请求过来1万+,Mysql数据库完全有可能直接挂掉,这个时候我们可以把数据放入缓存,通过key进行查询,这个时候并发轻松解决,而且查询速度是Mysql的几十倍。

常见的缓存问题

那么当我们使用了缓存以后,还会出现什么不利的影响吗?回答是肯定的,主要有如下几个问题
1.缓存导致和数据库数据不一致的问题
2.缓存雪崩,缓存穿透
3.缓存并发竞争

一、缓存导致和数据库数据不一致的问题

当我们把数据放到缓存之后有一个请求过来修改了数据库的数据,这个时候便产生了数据不一致的问题。
常用的解决办法就是读的时候先读缓存,如果缓存中没有再去读数据库,然后把读出来的数据放入缓存中,消费的时候修改数据库,然后删除缓存。
这里我们用到了懒加载思想(懒加载又叫延迟加载,他有两个好处,第一:当使用时才加载,而不是一开始就加载,为CPU节省时间做其他的事情,第二:加载之前会判断数据是否为空,如果空是空的才去加载,避免了重复加载数据,系统可能会清理内存使数组为空,这样确保数组不为空)
高并发场景如果缓存删除失败导致依然是老数据
这个时候我们可以先删除缓存,然后再修改数据库,这个时候如果缓存删除失败我们可以让事务回滚,重新进行删除,直到删除成功后再修改数据库。

二、缓存雪崩,缓存穿透

缓存雪崩
如果系统在某一时刻请求数量大幅度提升导致缓存机器宕机或者缓存过期查询为空导致查询数据库,这时候所有请求都会请求数据库,数据库必然也会直接挂掉,这个时候系统直接不可用,这就是缓存雪崩。
解决方案
1.把一些热点数据设置为永不过期,有过期时间的防止在同一时间点内清空缓存
2.设置本地缓存和增加 hystrix 限流/降级

缓存穿透
缓存穿透指请求的数据缓存中和数据库中都没有,用户还在不断访问,这个时候会导致数据库压力过大,容易挂死。
解决方案
1.如果第一次从缓存中没有读到数据,从数据库中也没有读到,我们可以存一个value为空的对象到缓存,有效期设置短一些,这样可以防止同一时刻用户不断访问给数据库带来的压力,后续还可以正常存取。
2.还可以设置拦截器,如果查询条件不正确直接返回错误,防止多次请求数据库。

三、缓存并发竞争

这个主要是针对Redis的写问题,当两个客户端同时操作同一个Key,当第一个客户端还没有设置新值回去的时候,第二个客户端获取到的是相同的Key,例如value为20,两个客户端都要给这个值+1,但是最终设置的都是21,这个便是并发竞争问题。
解决方案
1.增加Redission分布式锁或者利用Zookeeper实现分布式锁
2.CAS乐观锁
3.增加时间戳

二、缓存的实现

一、引入依赖

<!-- ehcache缓存依赖 -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>
        <!--redis缓存依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!-- 1.5的版本默认采用的连接池技术是jedis  2.0以上版本默认连接池是lettuce-->
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 添加jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

二、增加本地缓存配置文件

1.增加xml配置本地缓存位置和缓存名称

<!--
      name:缓存名称
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数
      eternal:对象是否永久有效,一但设置了,timeout将不起作用
      overflowToDisk:是否保存到磁盘
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0
      diskPersistent:是否缓存虚拟机重启期数据
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存
      clearOnFlush:内存数量最大时是否清除
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)
   -->

例1

<cache name="LocalCache"
           maxElementsInMemory="10000"
           eternal="false"
           timeToIdleSeconds="0"
           timeToLiveSeconds="200000"
           diskPersistent="true"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>
    <cache name="RedisCache"
           maxElementsInMemory="10000"
           eternal="false"
           timeToIdleSeconds="0"
           timeToLiveSeconds="150000"
           diskPersistent="true"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>

三、增加Configuration配置实现CacheManager

@Configuration
public class CacheConfiguration {
    /*
     * ehcache 管理器
     */
    @Bean(name = "localCacheManager")
    public CacheManager localCacheManager(){
        return new EhCacheCacheManager();
    }

    /**
     * 重写Redis序列化方式,使用Json方式:
     * 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。
     * Spring Data JPA为我们提供了下面的Serializer:
     * GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。
     * 在此我们将自己配置RedisTemplate并定义Serializer。
     */
    @Bean
    @Primary
    public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        //方便观察数据,序列化成json格式
        Jackson2JsonRedisSerializer JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        JsonRedisSerializer .setObjectMapper(objectMapper);

        RedisSerializationContext.SerializationPair<Object> serializeValues = RedisSerializationContext.SerializationPair.fromSerializer(JsonRedisSerializer);

        //缓存的失效实现统一为2小时
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(2)).serializeValuesWith(serializeValues);
        return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
    }
}

此外我们还要在启动类上面加上@EnableCaching注解

四、使用缓存

注:本次我们都是使用注解方式增加缓存

/*
     * 增加本地缓存@Cacheable
     */
    @Cacheable(cacheManager="localCacheManager", cacheNames ={"LocalCache"},key="#studentId")
    public Integer putLocalCache(String studentId,Integer age){
        return age;
    }
    /*
     * 删除本地缓存@CacheEvict
     */
    @CacheEvict(cacheManager="localCacheManager", cacheNames ={"LocalCache"},key="#studentId")
    public String deleteLocalCache(String studentId){
        return studentId;
    }
/*
     * 增加Redis缓存@Cacheable
     */
   @Cacheable(cacheManager="redisCacheManager", cacheNames ={"RedisCache"},key="#studentId")
    public Integer putRedisCache(String studentId,Integer age){
        return age;
    }
    /*
     * 删除Redis缓存@CacheEvict
     */
    @CacheEvict(cacheManager="redisCacheManager", cacheNames ={"RedisCache"},key="#studentId")
    public String deleteRedisCache(String studentId){
        return studentId;
    }

更多用法搜索@Cacheable&&@CacheEvict

以上就是本地缓存和Redis缓存的简单使用啦,自己也是把学习到的地方记录下来,第一次使用注解,如果有不足的地方和错误的地方还希望小伙伴们可以评论指出,一起分享学习!