Redisson+Redis实现分布式锁Lock

文章目录

    • 高并发下缓存失效问题
      • 缓存穿透
      • 缓存雪崩
      • 缓存击穿
    • 分布式锁
    • Redis分布式锁实现
    • Redisson分布式锁使用
      • 高效分布式锁
        • 1、互斥
        • 2、防止死锁
        • 3、性能
        • 4、重入
      • 依赖
      • 配置文件
      • Redisson看门狗
      • Redisson分布式锁
      • Redisson读写锁
      • Redisson闭锁
      • Redisson信号量
  • 推荐文章
    • 觉得对您有帮助就留下个宝贵的吧!

高并发下缓存失效问题

缓存穿透

缓存穿透是指 查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。
解决方法:缓存空结果、并且设置短的过期时间。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。
解决方法:原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

缓存击穿

对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。
这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所有对这个 key 的数据查询都落到 db,我们称为缓存击穿。
解决方法:加锁。大量并发只让一个人去查,其他人等待,查到之后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去查数据库。

分布式锁

Redisson+Redis实现分布式锁Lock_第1张图片

Redis分布式锁实现

/**
     * 从数据库查询
     * 分布式锁:自己实现的
     */
    public Map<String, List<Test>> getmDbWithRedisLock() {
     
        // 1、占本分布式锁。去redis占坑,同时设置过期时间
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
     
            // 加锁成功....执行业务【内部会判断一次redis是否有值】
            Map<String, List<Test>> dataFromDB = null;
            try {
     
                /**
                 * 1、首先还是要先查询redis是否有数据,有数据直接返回,因为可能已经先有人拿到了锁查询了数据
                 * 2、查询数据库获取数据库数据
                 * 3、将查询数据库的数据设置redis
                 */
                dataFromDB = formDb();
            } finally {
     
                // 2、查询UUID是否是自己,是自己的lock就删除
                // 查询+删除 必须是原子操作:lua脚本解锁
                String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call('del',KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                // 删除锁
                redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class),
                        Arrays.asList("lock"), uuid);
            }
            return dataFromDB;
        } else {
     
            // 加锁失败....重试
            // 休眠100ms重试
            try {
     
                Thread.sleep(200);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            return getCatalogJsonFromDbWithRedisLock();// 自旋的方式
        }
    }

Redisson分布式锁使用

官方文档:https://github.com/redisson/redisson/wiki

高效分布式锁

当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,这里我认为以下几点是必须要考虑的。

1、互斥

在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,这是最基本的一点。

2、防止死锁

在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。

所以分布式非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。

3、性能

对于访问量大的共享资源,需要考虑减少锁等待的时间,避免导致大量线程阻塞。

所以在锁的设计时,需要考虑两点。

1、锁的颗粒度要尽量小。比如你要通过锁来减库存,那这个锁的名称你可以设置成是商品的ID,而不是任取名称。这样这个锁只对当前商品有效,锁的颗粒度小。

2、锁的范围尽量要小。比如只要锁2行代码就可以解决问题的,那就不要去锁10行代码了。

4、重入

我们知道ReentrantLock是可重入锁,那它的特点就是:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用。

依赖



<dependency>
  <groupId>org.redissongroupId>
  <artifactId>redissonartifactId>
  <version>3.16.1version>
dependency>

配置文件

@Configuration
public class MyRedissonConfig {
     

    /**
     * 所有对Redisson的使用都是通过RedissonClient对象
     *
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod = "shutdown")
    RedissonClient redisson(@Value("${spring.redis.host}") String host) throws IOException {
     
        // 1、创建配置
        Config config = new Config();
        // 集群模式
//        config.useClusterServers()
//                .addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
        config.useSingleServer().setAddress("redis://" + host + ":6379");
        // 2、根据config创建出RedissonClient实例
        return Redisson.create(config);
    }

}

Redisson看门狗

结果死锁问题:
1.watch dog 在当前节点存活时每10s给分布式锁的key续期 30s;
2.watch dog 机制启动,且代码中没有释放锁操作时,watch dog 会不断的给锁续期;
3.如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到 finally {} 中;

Redisson+Redis实现分布式锁Lock_第2张图片

Redisson分布式锁

public Map<String, List<Test>> getJsonFromDbWithRedissonLock() {
     
        // 1、锁的名字。锁的粒度:越细越快
      
        RLock lock = redisson.getLock("json-lock");
        lock.lock();

        Map<String, List<Test>> dataFromDB = null;
        try {
     
            Thread.sleep(30000);
            // 加锁成功....执行业务【内部会再判断一次redis是否有值】
            dataFromDB = formDb();
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        } finally {
     
            // 2、查询UUID是否是自己,是自己的lock就删除
            // 查询+删除 必须是原子操作:lua脚本解锁
            lock.unlock();
        }
        return dataFromDB;
    }

Redisson读写锁

保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁
读锁:共享锁
写锁:互斥,排他锁
写锁没释放读就必须等待
读读:相当于无锁,并发读,只会在redis中记录好,所有当前的读锁。他们都会同时加锁成功
写读:等待写锁释放
写写:阻塞方式
读写,有读锁。写也需要等待。
只要有写的存在,都必须等待
使用:
1、获取同一把锁
2、获得写锁:lock.writeLock()
3、获得读锁:lock.ReadLock()

/**
     * 读锁
     *
     * @date: 2021/8/14 12:50
     */
    @ResponseBody
    @GetMapping(value = "readLock")
    public String readRedsson() {
     
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
        RLock rLock = readWriteLock.readLock();
        String s = "";
        try {
     
            System.out.println("读锁加锁成功~~~~~~~~~~~~~~~~~");
            rLock.lock();
            s = (String) redisTemplate.opsForValue().get("ReadWriteValue");
        } catch (Exception e) {
     

        } finally {
     
            rLock.unlock();
            System.out.println("读锁释放成功~~~~~~~~~~~~~~~~~~~");
        }
        return s;
    }


    /**
     * 写锁
     *
     * @param:
     * @return: void
     * @date: 2021/8/14 12:54
     */
    @ResponseBody
    @GetMapping(value = "writeLock")
    public String writeRedsson() {
     
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
        RLock rLock = readWriteLock.writeLock();
        String s = "";
        try {
     
            System.out.println("写锁加锁成功~~~~~~~~~~~~~~~~~");
            rLock.lock();
            Thread.sleep(30000);

            s = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set("ReadWriteValue", s);
        } catch (Exception e) {
     

        } finally {
     
            rLock.unlock();
            System.out.println("写锁释放成功~~~~~~~~~~~~~~~~~~~");
        }
        return s;
    }

Redisson闭锁

闭锁:
闭锁是计数的一个状态。
等到计数减完的时候,才能释放闭锁

/**
     * 下班 锁门
     * 1、下班没人了
     * 5个部门全部走完,我们可以锁大门
     */
    @ResponseBody
    @GetMapping(value = "lockDoor")
    public String lockDoor() throws InterruptedException {
     
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.trySetCount(5);
        door.await();//等待闭锁都完成
        return "下班了。。。。锁门";
    }


    @ResponseBody
    @GetMapping(value = "gogogo/{id}")
    public String gogogo(@PathVariable("id") Long id) {
     
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown();//计数减一
        return id + "部门的人都走完了....";
    }

Redisson信号量

业务场景:
可以做分布式限流操作,秒杀

车库停车
redis放入park设置总量5

acquire 如果有车位大于1车位减1,如果没有车位一直阻塞等待
tryAcquire 如果有车位大于1车位减1,如果没有车位直接返回false
release 释放也就是在原有的值基础上加1


    @ResponseBody
    @GetMapping(value = "park")
    public String park() throws InterruptedException {
     
        //停车,车位减1
        RSemaphore park = redisson.getSemaphore("park");
        //阻塞等待
        park.acquire();//获取一个信号,获取一个值,占一个车位
        return "ok";
    }

    @ResponseBody
    @GetMapping(value = "park1")
    public String trypark() throws InterruptedException {
     
        //停车,车位减1
        RSemaphore park = redisson.getSemaphore("park");
        //尝试获取,不成功直接返回
        boolean b = park.tryAcquire();//获取一个信号,获取一个值,占一个车位
        return "ok》》》》》" + b;
    }


    @ResponseBody
    @GetMapping(value = "go")
    public String go() throws InterruptedException {
     
        //开走,车位加1
        RSemaphore park = redisson.getSemaphore("park");
        park.release();//释放一个车位
        return "ok";
    }

推荐文章

Spring Cloud Alibaba 系列学习笔记
SpringCloud Alibaba Nacos
SpringCloud Alibaba Sentinel
@SentinelResource注解总结,异常、降级兜底
SpringCloud Alibaba Sentine 规则持久化
SpringCloud Alibaba RocketMQ
Seata1.4.2分布式事务整合nacos+SpringCloudAlibaba

觉得对您有帮助就留下个宝贵的吧!

你可能感兴趣的:(分布式,redis,redisson,redis)