Java缓存和锁的使用(Redis)

首先我们要知道什么数据适合放入缓存

1、及时性,数据 一致性要求不高的:

        例如存入数据,不需要马上访问的。

2、访问量大且更新频率不高的数据(读多,写少)

 举例:电商类应用,商品分类,商品列表等


具体流程

伪代码参考如下:

data = cache.load(id);//从缓存加载数据
if(data == null){
    data = db.load(id);//从数据库中加载
    cache.put(id,data);//保存到cache
}
return data;

Java中最简单的缓存方式Map

在类上声明一个Map(本地缓存) 

本地缓存

缺点:

在分布式架构下

        1、当被负载均衡到其他服务器上时,要重新录入缓存

        2、数据被修改时,不仅要在db上改而且还要再其他cache上修改,这样如果是被负载到1号

              服务器上,修改器cache的时候那么与其他的服务器上的本地缓存数据会不一致

所以下面介绍分布式缓存数据的流程

优势:

        1、当内存不足时可以做集群工作。

        2、还可以进行分片操作,使容量提升。


Redis的使用:

        1、引入datra-redis-starter依赖

        2、简单配置redis的host信息
        3、使用spring自带的StringRedisTemplate

ValueOperations<String,String> ops = stringRedisTemplate.opsForValue();

//保存
ops.set("hello","world");

//查询
String hello = ops.get("hello");
System.out.println(hello);

整合逻辑代码: 

//1。缓存逻辑
//JSON跨语言
String catelogJSON = redisTemplate.opsForValue().get("catalogJSON");
if(StringUtils.isEmpty(catalogJSON)){
    //2.缓存中没有,查数据库
    Map<String, List<Catelog2VO>> catelogJsonFromDb = getCatelogFromDb(0
    //3.查到的我数据再放入缓存,要将对象转为json
    String s = JSON.toJSONString(catelogJsonFromDb);
    redisTemplate.opsForValue().set("catelogJSON",s);
    return catelogJsonFromDb;
}

//收到数据后要转为指定对象。
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJSON,new TypeRerence<Map<String,List<Caterlog2VO>);
return result;

高并发下缓存失效问题:

1、缓存穿透

原因:查询一个一定不存在的数据,由于缓存不命中,所以去查询数据库,但是数据库也没有记录,所以就导致了每次查询到要去储存层查询,失去了缓存的意义。

风险:  利用不存在的数据进行攻击,数据库瞬时压力增大,导致最终崩溃。

解决:null结果缓存,并加入短期过期事件

2、缓存雪崩

原因:我们数值缓存时key采用了相同的国企事件,导致缓存存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方法:原有的失效时间的基础上增加一个随机值,重复率降低,就难以引发集体失效的事件。

redisTemplate.opsForValue().set("catelogJSON",1,TimeUnit.DAYS);

3、缓存击穿

原因:对于一些设置了过期事件的key,如果这些key可能会在某些时间超高并发访问,导致频繁查询这个key最终查询都落到db

解决方法:加锁 只让一个人去查,其他人等待,查到后释放锁,

锁:

        本地锁:优点:快,缺点:只能锁住当前进程。

synchronized,JUC(Lock)只能锁住this

        分布式锁:优点:锁住所有进程,缺点:慢。       

        原理:所有的服务去同一个地方占坑,如果占到就执行逻辑。否则等待

阶段一:

        使用SET NX(如果非空,就设置字段),如果保存失败,就是占锁失败。

分布式锁使用方法(保证原子性):

         

String uuid = UUID.randomUUID().toString();
//设置字段的同时设置过期时间
redisTemplate.opsForValue().setIfAbsent("lock","111",300,TimeUnit.SECONDS);
if(lock){
    try{
      //执行逻辑  
    }finally{
    //删除锁
    //唯一标识相同的的时候才能删锁(也要是原子操作)Lua脚本删锁
    String script = "if redis.call("get",KEYS[1]) == ARGV[1]
    then
        return redis.call("del",KEYS[1])
    else
        return 0
    end";

    Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>    
    (script,Integer.class),Arrays.asList("lock",uuid);
    }
}else{
    //休眠100ms
    //枷锁失败,重试,自旋的方式
}

这段代码可以防止其在各个字段出现故障:

1、try finally保证其无论如何都会执行删锁代码,

2、设置过期时间防止执行逻辑过程中崩溃,断电等导致没有及时删锁导致的死锁问题。

3、uuid保证不会在执行代码的的同时锁的时间到而导致其他进程进锁同时占用,删除的时候删到        了后面进来的进程的锁。(保证它只删除自己的锁)

4、Lua脚本:要么全成功要么全失败,保证原子性。


整合工具

Redisson

依赖

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.39.0</version>
</dependency>

简单配置:

要写连接协议名:redis:// 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值