Redisson实现分布式锁

2 篇文章 0 订阅
1 篇文章 0 订阅
本文详细介绍了如何在SpringBoot项目中集成Redission,包括依赖导入、创建Redis客户端、实现分布式锁(可重入、公平、联锁、红锁、闭锁等)以及其底层原理。还探讨了不同类型的锁如读写锁和信号量的使用方法。
摘要由CSDN通过智能技术生成

一、Redission在SpringBoot中的集成

1.导入依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.6</version>
</dependency>

2.创建Redis客户端

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");//.setPassword("123456");
        return Redisson.create(config);
    }
}

3.实现分布式锁

 	@Autowired
    private RedissonClient redissonClient;

    @Test
    public void testLock(){
        RLock rLock = redissonClient.getLock("lock_stock");
        //阻塞式等待,过期时间30s
        rLock.lock();	
        try{
            //执行业务
        }finally {
            //释放锁
            rLock.unlock();
        }
    }

二、Redission实现分布式锁底层原理

        如果负责存储分布式锁的Redission节点发生了宕机,并且锁没有被释放,就会出现死锁的现象。因此,为防止这种现象的产生,Redission有一个看门狗的机制,用来监控锁,若Redission实例未关闭,就会不断的延长锁的有效期,防止程序执行期间自动过期删除的问题。

        Redisson加锁有以下两种情况:

        (1)设置了过期时间

          若设置了过期时间,会把设定的时间作为过期时间,然后使用Lua脚本获取到锁,未获取到锁的线程则会自旋重入,不停地尝试获取锁。

          如果我们通过lease Time(rLock.lock(20, TimeUnit.SECONDS))的参数指定了加锁的时间,那么Redisson就不会再进行续期了,锁到达过期时间会自动释放锁,无需unlock手动解锁,如果出现锁的误删除情况时,Redisson会抛出异常。

        (2)未设置过期时间

        未设置过期时间的,Redisson加锁会有一个默认的过期时间为30s,线程获取到锁后,用了Lua来保证原子性,会开启一个定时任务,每隔10s看门狗会执行一次定时任务,若锁还在,重新将过期时间设置成30s。

三、Redisson锁的分类

1.可重入锁

    @Autowired
    private RedissonClient redissonClient;
 	
    @Test
    public void testLock(){
        //获取锁
        RLock rLock = redissonClient.getLock("lock_stock");
        //加锁,锁的过期时间是10s,10s后锁自动释放
		rLock.lock(10, TimeUnit.SECONDS);
        try{
            //执行业务
        }finally {
            //释放锁,设置了锁的过期时间,不会手动调这个方法释放锁
            rLock.unlock();
        }
    }

2.公平锁

        公平锁保证了当多个Redisson客户端线程同时请求加锁时,会按照请求的顺序进行加锁,遵循先到先得的原则。

     @Autowired
     private RedissonClient redissonClient;
    
     @Test
     public void testLock5() {
         RLock fairLock = redissonClient.getFairLock("anyLock");
         try{
             fairLock.lock();
         }finally {
             //释放锁
             fairLock.unlock();
         }
     }

3.联锁

        通过RedissonMultiLock对象可以将多个RLock锁关联为一个联锁,其中每个RLock对象实例都可以来自不同的Redisson实例。

    RLock lockA = redissonInstanceA.getLock("lockA");
    RLock lockB = redissonInstanceB.getLock("lockB");
    RLock lockC = redissonInstanceC.getLock("lockC");

    // 同时加锁:lockA, lockB, lockC
    RedissonMultiLock lock = new RedissonMultiLock(lockA, lockB, lockC);

    // 所有的都加锁成功才是成功
    lock.lock();

    // 释放锁
    lock.unlock();

4.红锁

        可以将多个RLock关联成一个红锁,每个RLock对象实例可以来自于不同的Redisson实例,RedLock可以保证以下特性:

        (1)容错性:只要大多数节点的redis实例能正常运行就可以加锁和释放锁。

        (2)互斥性:只有一个客户端可以获取到锁,即使发生宕机,也不会造成死锁。

        (3)一致性:降低了数据不一致的可能,但不能完全保证数据的一致性。

    RLock lockA = redissonInstanceA.getLock("lockA");
    RLock lockB = redissonInstanceB.getLock("lockB");
    RLock lockC = redissonInstanceC.getLock("lockC");

    // 同时加锁:lockA, lockB, lockC
    RedissonRedLock = new RedissonRedLock(lockA, lockB, lockC);

    // 大部分节点加锁成功就算成功
    lock.lock();

    // 释放锁
    lock.unlock();

5.闭锁

        Redisson提供了RCountDownLatch接口,它是一个计数降低到0时触发的锁。它可以实现在多个线程都执行完才结束的效果,否则就会闭锁来等待。

    RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
    
    //设置数量为2
    latch.trySetCount(2);

    //await方法等待其他线程完成所有的trySetCount(2)就会结束闭锁
    latch.await();

    //在其他线程或其他JVM里
    RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
    
    //完成第1个countDown
    latch.countDown();

    // 在其他线程或其他JVM里
    RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");

    //完成第2个countDown,闭锁完成
    latch.countDown();

6.读写锁

        Redisson的读写锁RReadWriteLock Java对象实现了ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。

        分布式的读写锁允许同时有多个读锁和一个写锁处在加锁的状态,也就是使用同一个RReadWriteLock来加写锁和读锁,读锁需要等待写锁释放锁之后才能够加锁成功。

    @Autowired
    private RedissonClient redissonClient;
   
    @Test
    public void testWriteLock() {
        //获取读写锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("ReadWriteLock");
        //获取写锁
        RLock rLock = readWriteLock.writeLock();
        try{
            //加写锁,读等待
            rLock.lock();
            Thread.sleep(200000);
            //执行写业务
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rLock.unlock();
        }
    }
    @Test
    public void testReadLock() {
        //获取读写锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("ReadWriteLock");
        //获取读锁
        RLock rLock = readWriteLock.readLock();
        try{
            //加上读锁,若写锁没释放,等待
            rLock.lock();
            //处理读业务
        }finally {
            rLock.unlock();
        }
    }

7.信号量

        Redisson的信号量可以看做是在Redis中保存了一个数字,可以实现原子性的加或减,比如取10件商品做库存,就可以把这个库存做成信号量,然后实现原子性加减。防止了库存出现负数的情况。

    @Autowired
    private RedissonClient redissonClient;

    @Test
    public void testReadLock() throws InterruptedException {
        //获得1个信号量
        RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
        //设置信号量的值
        boolean setPermits = semaphore.trySetPermits(1000);
    }
    @Test
    public void testReadLock6() throws InterruptedException {
        //获得到1个信号量
        RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
        //获取2个信号量 , 值会减去2 , 若获取不到,方法会阻塞
        semaphore.acquire(2);

        //尝试获取2个信号量 , 值会减去2 , 若获取不到,方法不会阻塞
        boolean tryAccquireSuccess = semaphore.tryAcquire(2);
    }

    @Test
    public void testReadLock7() throws InterruptedException {
        //获得到1个信号量
        RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
        //释放2个值,数量会加回去
        semaphore.release(2);
    }

        除了上述的信号量,还提供了一个可过期的信号量,在RSemaphore对象的基础上,为每个信号增加了一个过期时间。每个信号可通过ID来进行区分,释放时也只能通过提交的ID才能释放。

RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String id = semaphore.acquire();
// 获取1个信号,有效期只有1秒钟。
String id = semaphore.acquire(1, TimeUnit.SECONDS);

semaphore.release(id);

  • 43
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值