redis 03 springboot 缓存

Something before start

主要解决两种缓存场景
1、实时更新的缓存 即一般的查询时候,把记录写进缓存。
2、不需要实时的缓存 即不需要很准确的数据//比如用户数量什么的。对于这种情况,需要注意缓存雪崩的问题。这里是使用双重检测锁来解决这个问题的。

缓存雪崩:数据到期,大量请求涌入到数据库

依赖

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

配置文件

连接redis

有三种形式连接。另外注意配置redis的配置文件

# 关闭保护模式
protected-mode no
# 把这个注释掉,开启外地连接
#bind 

单机

spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=

哨兵模式集群

# 这个mymaster 可以直接在sentinel.conf里找到,可以更换成自己喜欢的hhhh
spring.redis.sentinel.master=mymaster
# 这里展示的是多种写法,多个哨兵
spring.redis.sentinel.nodes=locathost:26379,127.0.0.1:26380,locathost:26381
spring.redis.sentinel.password=

cluster集群

# 连接redis集群 //cluster模式
spring.redis.cluster.nodes=127.0.0.1:7006,127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005
#spring.redis.password=

指定springboot缓存形式

# 指定cache类型
spring.cache.type=redis
# 缓存区域名称 //随便取都行 同时可以多缓存空间
spring.cache.cache-names=realTimeCache

实体类序列化

很简单,只要实现java的序列化接口就行Serializable
序列化简单理解就是把一个对象转化成字符流

public class Employee implements Serializable {
    private Integer id;
    private String name;
    private int age;
    //省略其他乱七八糟的东西

注解方式

怎么确保缓存准确性

每次更新数据的时候就清理所有的缓存,在查询的时候将数据时写入缓存。因为redis本身性能很高,所以不用担心性能啥的。

启动类开启缓存

@EnableCaching 开启缓存

@EnableCaching
@EnableTransactionManagement
@SpringBootApplication
public class RedisdemoApplication {

更新数据 清理缓存

@CacheEvict: 放在更新(增删改)数据的方法上

  1. value是前面配置的缓存区域名称。
  2. 当注解参数加上allEntries为true时,意思是说这个清除缓存是清除当前value值空间下的所有缓存数据。
    //更新数据 则 更新清理缓存
    @CacheEvict(value = "realTimeCache", allEntries = true)
    @PostMapping("regist")
    public void registHandle(Employee employee) {
        employeeService.regist(employee);
    }

查询数据 更新缓存

@Cacheable:放在查询的方法上

    //value:缓存空间 key:键
    @Cacheable(value = "realTimeCache", key = "'emloyee_'+#id")
    @GetMapping("find")
    public Employee findHandle(int id) {
        return employeeService.findEmployeeById(id);
    }

API方式

什么是双重检测锁

第一次检测:进入方法,如果有值则返回,如果取出来为空则开启同步阻塞其他线程请求。
第二次检测:进入同步线程之后,再次检测是否有值,有则返回,无则将请求送到数据库。
为什么需要第二次检测:第一取不到值的线程进入同步的时候,可能会有其他请求跑进来。第一个线程更新完之后,其他的线程就不许要重新更新,所以需要第二次检测

实现代码

    //使用双重检测锁 解决 热点缓存问题
    @GetMapping("count")
    public Integer countHandle() {
        //获取redis操作对象 和count绑定 count即key
        BoundValueOperations<Object, Object> ops = redisTemplate.boundValueOps("count");

        //从缓存中获得数据
        Object count = ops.get();

        if (count==null){
            //因为上锁之后阻塞了 会有新的请求到这里,所以需要上锁后重新检测保证后面来的不会击穿
            //因为是单例 所以可以用this
            synchronized (this){
                count = ops.get();
                if (count==null){
                    count = employeeService.findEmployeeCount();
                    ops.set(count,10, TimeUnit.SECONDS);//存入 存活10秒
                }
            }
        }
        return (Integer)count;
    }

自动生成key

一样是使用codeConfig方式。
然后就可以把注解里的那个key删掉了
注意这里的para只适合一个参数的情况//其实写一个循环就完事了
暂时懒得写解释,感性理解一下就好/

@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
    //自动生成key结构:类名_方法名_参数
    @Override
    public KeyGenerator keyGenerator() {
        return (target,method,params)->{
            String className = target.getClass().getName();
            String methodName =  method.getName();
            return className+"-"+methodName+"-"+params[0].toString();
        };
    }
}