Redis锁相关的问题随记

谷粒商城高级随记

复杂json转对象


注://将拿到json串转成指定的对象TypeReference,categoryjson是json串

Map<String, List<Catelog2Vo>> result=JSON.parseObject(categoryjson,new TypeReference<Map<String, List<Catelog2Vo>>>(){});

Redis锁相关问题:

  1. 缓存穿透:恶意攻击,用工具一直去查一个没有东西,由于redis也没存null值,所以一直请求数据库,

    解决方案:放一个标致味,null或者0,并且假如短暂的过期时间,不然该商品突然有了也一直是null或者0;如果再次没查到也放标志味跟短暂过期时间
  2. 缓存雪崩:指所有的健都设置了同一时间过期,然后百万并发一起砸向了数据库.
    在这里插入图片描述
    解决方案:再设置失效期的的时候再加上个随机值,比如1-5分钟,最大程度的避免同时过期
  3. 缓存击穿:比如iphone的预售活动,正好缓存过期,几百万人又同时砸向数据库
    在这里插入图片描述
    **解决方案:**大量的并发,只能让一个人去查,其他人等待,查到以后去释放锁,其他人拿到锁就先去查缓存,就会有数据不用去db查
  4. 单体锁
    在这里插入图片描述
    注:锁的时序问题:假如1号线程进来拿到锁查询->查询缓存没有->查询数据库->释放锁->放入缓存时需要网络交互,但此时2号就已经又拿到锁又去缓存中查,结果1号的还没放进去,于是就又查询数据库,
  5. 所以得调整为:
    在这里插入图片描述
    注:查到数据后先去放到缓存,最后再释放锁,测试单体应用没问题(但是多启动几个商品服务就有每个都查数据库的情况)如下:在这里插入图片描述
    注:本地锁:只能锁住当前的进程,假如商品服务有8个机器,那就8把锁,只能各自锁自己的,在分布式情况下想要锁住所有的只能使用分布式锁
  6. 分布式锁演进一:
    在这里插入图片描述
    redis的占坑命令:在这里插入图片描述模拟4个redis客户端给redis发占坑命令:在这里插入图片描述
    结果:1号:nil 2号ok 3号nil 4号nil
  7. 分布式锁的演进2:在这里插入图片描述
    问题:业务处出现异常导致没有执行删锁,就会进入死锁状态?
    在这里插入图片描述
    解决:获取锁的时候设置过期时间在这里插入图片描述
    出现问题:如果拿到锁设置时间前服务器断电等又成死锁?在这里插入图片描述
  8. 分布式锁的演进3:在这里插入图片描述
    问题:假如我们锁设置10s过期,但是业务代码执行的时间比较长,这时候有可能别人又已经把坑站了.我们采取删锁有可能删的是别人的锁;
  9. 分布式演进4:在这里插入图片描述
    在这里插入图片描述
  10. 分布式最终形态:在这里插入图片描述

在这里插入图片描述

  1. 最优方案:redisson:
    1.引入坐标
    2.配置:
@Configuration
public class MyRedisConfig {
    /**
     * 所有对Redisson的使用都是通过RedissonClient
     * @return
     * @throws
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException {
        //1、创建配置
        Config config = new Config();
        //rediss为启用ssl连接
        config.useSingleServer().setAddress("redis://192.168.2.5:6379");
        //2、根据Config创建出RedissonClient实例
        //Redis url should start with redis:// or rediss://
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }

测试

//测试redisson
    @GetMapping
    @ResponseBody
    public String hello(){
        //获取一把锁,只要锁的名字一样就是同一把锁
        RLock mylock = redissonClient.getLock("mylock");
        //加锁
        //mylock.lock(); //阻塞等待,一直等待拿锁
        mylock.lock(10, TimeUnit.SECONDS); //设置锁的过期时间为10s,下面的业务要执行30s,会不会锁不住???
        //手动指定解锁时间情况,一旦业务大于解锁时间就会锁不住!!!所以一定要解锁时间大于业务时间才行
        try{
            //模拟业务超长执行,睡眠30s,默认加锁都是30s,不够自动续
            Thread.sleep(30000);
            //如果业务超长,redisson会给锁自动续新的30s
            //如果当前业务执行完成,就不会再续时间,即使不手动解锁,也会30s到了自动解锁
            System.out.println("加锁成功,执行业务代码...线程id:"+Thread.currentThread().getId());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //执行完业务解锁
            mylock.unlock(); //问题:假如执行业务时系统断电,会不会出现死锁情况?
            System.out.println("业务执行完毕,释放锁...线程id:"+Thread.currentThread().getId());
        }

        return "hello";
    }

redis中的存储情况在这里插入图片描述
假如业务执行时断电会不会出现死锁?不会
在这里插入图片描述
在这里插入图片描述
看门狗机制:手动设定解锁时间是不会自动续时间的
在这里插入图片描述
注:手动指定解锁时间情况,一旦业务大于解锁时间就会锁不住!!!所以一定要解锁时间大于业务时间才行
推荐:自己手动设定时间,省掉了定时任务的消耗时间

读写锁:

 /**
     *读写锁
     保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共锁
     写锁没释放读就必须等待
     读+读:相当于无锁,并发读,只会在 redis中记录好,所有当前的读锁。他们都会同时加锁成功
     写+读:等待写锁释放
     写+写:阻塞方式
     读+写:有读锁。写也需要等待。
     只要有写的存在,都必须等待
     */
    @GetMapping("/write")
    @ResponseBody
    public String writeValue(){
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        String s="";
        //拿到写锁
        RLock writeLock = readWriteLock.writeLock();
        try{
            writeLock.lock();
            s= UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("writeValue",s);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            writeLock.unlock();
        }
        return s;
    }

    @GetMapping("/read")
    @ResponseBody
    public String readValue(){
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        String s="";
        //拿到读锁
        RLock readLock = readWriteLock.readLock();
        try{
            readLock.lock();
            s= UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().get("writeValue");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readLock.unlock();
        }
        return s;
    }

总结: *读写锁
保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁
写锁没释放读就必须等待
读+读:相当于无锁,并发读,只会在 redis中记录好,所有当前的读锁。他们都会同时加锁成功
写+读:等待写锁释放
写+写:阻塞方式
读+写:有读锁。写也需要等待。
只要有写的存在,都必须等待

信号量:也可以做限流

/**
     * 提前在redis中存入park值为3
     * 信号量
     * 模拟车库停车,3个车位
     */
    @GetMapping("/park")
    @ResponseBody
    public String park() throws Exception {
        RSemaphore park = redissonClient.getSemaphore("park");
        //park.acquire(); //获取一个信号量,获取一个值占一个车位,阻塞方法,获取成功就ok,获取不成功就一直等待获取
        boolean b = park.tryAcquire();  //能停就直接返回true,不能停就直接返回false,不像上面阻塞等待
        if (b){
            System.out.println("执行业务...");
        }else{
          return "erro,当前活动太火爆,请稍候再试";
        }
        return "ok=>"+b;
    }
    @GetMapping("/go")
    @ResponseBody
    public String go(){
        RSemaphore park = redissonClient.getSemaphore("park");
        park.release(); //释放一个信号,释放一个车位
        return "ok";
    }

说明:首先往redis中存入park值为3,调用一次park方法值就减1,到0的时候调park.acquire()就会阻塞等待,直到掉一次go才会阻塞结果返回ok,而park.tryAcquire();则直接返回true跟fasle不用阻塞

闭锁

在这里插入图片描述
场景:比如放假了门卫要锁门,有5个班级,必须等到5个班的人全部走完了才锁门

 /**
     * 闭锁
     * 比如放假了门卫要锁门,有5个班级,必须等到5个班的人全部走完了才锁门**
     */
    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws Exception {
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.trySetCount(5); //设置五个班
        door.await();//等待闭锁都完成,掉完以后处于阻塞状态
        return "放假了";
    }

    @GetMapping("/gogogo/{id}")
    @ResponseBody
    public String gogogo(@PathVariable("id") Long id){
        RCountDownLatch door = redissonClient.getCountDownLatch("door");  //拿到的是同一把锁
        door.countDown();//计数器减1
        return id+"的人都走了!";
    }

优化分类的代码

    //基于redission加锁
    public Map<String, List<Catelog2Vo>> getCategorylogjsonFromDbWithRedissionLock() {
            //获取redis分布式锁,去redis占坑,锁的名字=锁的力度,
            //锁的粒度:具体缓存的是某个数据,11号商品:product-11-lock, 12号商品:product-12-lock
        RLock lock = redissonClient.getLock("catelogJson-lock");
        lock.lock();//加锁
        Map<String, List<Catelog2Vo>> dataFromDb=null;
            try{
                dataFromDb = getDataFromDb();
                //无论业务成功与否都给解锁
            }finally {
                lock.unlock();//解锁
            }
            return dataFromDb;
            
    }

缓存数据一致性问题:

		1)双写模式(会有脏数据假如1先改但是速度还没有后面的改的快)
		2)失效模式
		我们的系统采用读写锁

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小颂雅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值