Redisson实现锁以及redis缓存一致性问题

目录

 RedissonClient实现最基本的锁

RedissonClient实现读写锁

RedissonClient实现闭锁

RedissonClient信号量

缓存不一致问题解决方案

一、双写模式

二、失效模式


 RedissonClient实现最基本的锁

        // 1、获取一把锁,只要锁的名字一样,就是同一把锁
        RLock mylock = redisson.getLock("mylock");

        // 2、获取锁并执行业务
        //mylock.lock(); // 加锁,阻塞式等待,默认加的锁都是30s时间
        // 1) 锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s(看门狗机制),不用担心业务时间长导致锁自动过期被删掉
        // 2) 加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认30s以后自动删除
        mylock.lock(10, TimeUnit.SECONDS);
        // 上面这个锁时间到了以后,不会自动续费
        // 1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占领,默认超时时间就是我们指定的时间
        // 2、如果我们未指定锁的超时时间,就是用30*1000【lockWatchDogTimeout看门狗的默认时间】
        //  只要占领成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动续期
        //  internalLockLeaseTime【看门狗时间】 / 3 == 10s  10s一续期
        try {
            System.out.println("加锁成功,执行业务," + Thread.currentThread().toString());
        } catch (Exception e) {

        } finally {
            // 无论业务执行的如何都得释放锁
            mylock.unlock();
            System.out.println("释放锁," + Thread.currentThread().toString());
        }

RedissonClient实现读写锁

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

    @GetMapping("/read")
    @ResponseBody
    public String readValue() {
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        String s = "";
        // 写锁
        RLock rLock = lock.readLock();
        try {
            // 写锁上锁
            rLock.lock();
            System.out.println("读锁加锁成功。。。" + Thread.currentThread().toString());
            s = UUID.randomUUID().toString();
            Thread.sleep(30000);
            s = redisTemplate.opsForValue().get("writeValue");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
            System.out.println("读锁释放。。。" + Thread.currentThread().toString());
        }
        return s;
    }

RedissonClient实现闭锁

    // 等待房间内的5个人离开后闭锁才能成功,否则await一直阻塞
    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
        RCountDownLatch lock = redisson.getCountDownLatch("door");
        lock.trySetCount(5);
        lock.await(); // 阻塞等待闭锁完成
        
        return "锁门成功";
    }

    @GetMapping("/leave/{id}")
    @ResponseBody
    public String leave(@PathVariable("id")Long id) throws InterruptedException {
        RCountDownLatch lock = redisson.getCountDownLatch("door");
        lock.countDown();  // 计数-1

        return id + "离开了房间";
    }

RedissonClient信号量

    // 需要先在redis中存储semaphore这个key并设置数量
    // 信号量也可以用作分布式限流
    @GetMapping("/semaphore")
    @ResponseBody
    public String park() throws InterruptedException {
        RSemaphore semaphore = redisson.getSemaphore("semaphore");
        semaphore.acquire(); // 获得信号量,必须获得,否则阻塞
        /*
        boolean b = semaphore.tryAcquire();// 尝试获得,可通过返回值进行一定的操作
        if(b) {
            // ....
        } else {
            // .... return error啥的
        }
        */
        return "获得信号量成功";
    }

    @GetMapping("/go")
    @ResponseBody
    public String go() throws InterruptedException {
        RSemaphore semaphore = redisson.getSemaphore("semaphore");
        semaphore.release();  // 释放信号量
        return "go";
    }

缓存不一致问题解决方案

一、双写模式

在更新数据库后立即更新缓存,但是有漏洞,

由于卡顿等原因,导致写缓存 2 在最前,写缓存 1 在后面就出现了不一致
这是暂时性的脏数据问题,但是在数据稳定,缓存过期以后,又能得到最新的正确数据,即能够保证最终一致性

二、失效模式

在更新数据库后将缓存直接删除,简单方便,但是也可能出现漏洞

读缓存线程读取的仍然是旧数据,写进缓存的也是旧的数据 

无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?
  • 1、如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
  • 2、如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
  • 3、缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
  • 4、通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁(业务不关心脏数据,允许临时脏数据可忽略);
总结:
  • 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
  • 我们不应该过度设计,增加系统的复杂性
  • 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。

猜你喜欢

转载自blog.csdn.net/m0_62946761/article/details/132156218
今日推荐