Using Redisson to implement distributed locks

Preface

In the project, the mall homepage is cached, and the problems caused by the cache are solved accordingly. This blog is a summary of the actual operation plan.

My previous blog has summarized the theoretical part of the problem of caching and the solution, the theoretical part <
—Click here to summarize how I implemented this distributed lock.

How to manually implement distributed locks

The prerequisite for implementing distributed locks must ensure that the two operations of acquiring lock + expiration time, acquiring lock + deleting lock are all atomic operations.
Insert picture description here
The picture above is the flow chart of my solution. Next, let's see how my code is implemented.

/**
 * 从数据库查询并封装数据::分布式锁
 * @return
 */
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
    
    

    //1、占分布式锁。去redis占坑      设置过期时间必须和加锁是同步的,保证原子性(避免死锁)
    String uuid = UUID.randomUUID().toString();
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
    if (lock) {
    
    
        System.out.println("获取分布式锁成功...");
        Map<String, List<Catelog2Vo>> dataFromDb = null;
        try {
    
    
            //加锁成功...执行业务(只允许获取到分布式锁的线程去数据库中查)
            dataFromDb = getDataFromDb();
        } finally {
    
    
            // Lua脚本,在脚本中有两步操作:一、获取当前这个分布式锁,判断这个分布式锁是不是我的,二、如果是我的就删除,并返回1,否则返回0
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

            //删除锁
            stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);

        }
        //先去redis查询下保证当前的锁是自己的
        //获取值对比,对比成功删除=原子性 lua脚本解锁
        // String lockValue = stringRedisTemplate.opsForValue().get("lock");
        // if (uuid.equals(lockValue)) {
    
    
        //     //删除我自己的锁
        //     stringRedisTemplate.delete("lock");
        // }

        return dataFromDb;
    } else {
    
    
        System.out.println("获取分布式锁失败...等待重试...");
        //加锁失败...重试机制
        //休眠一百毫秒
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return getCatalogJsonFromDbWithRedisLock();     //自旋的方式
    }
}

Just write it down like this. However, in terms of the expiration time, we need to set a bigger one or write a timed task to renew it regularly (when the business is not completed), then let's take a look at the usage of the official framework, it can be very convenient solve these problems

Official document

Let's take a look at how the official document explains it first! Redisson official documents
Insert picture description here
最常见的一个问题:
Everyone knows that if certain Redis nodes responsible for storing certain distributed locks go down and these locks are in the locked state, these locks will appear locked. In order to avoid this situation, Redisson provides a watchdog that monitors the lock. Its function is to continuously extend the validity period of the lock before the Redisson instance is closed. By default, the timeout period for the watchdog to check the lock is 30 seconds, and it can also be specified by modifying Config.lockWatchdogTimeout. In addition, Redisson also provides the leaseTime parameter through the locking method to specify the lock time. After this time, the lock will be automatically unlocked.
Next, let's take a look at common locks. Redisson also has encapsulation: the
可重入锁:
Redis-based Redisson distributed reentrant lock RLock Java object implements the java.util.concurrent.locks.Lock interface.

@ResponseBody
@GetMapping(value = "/hello")
public String hello() {
    
    

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

    //2、加锁
    myLock.lock();      //阻塞式等待。默认加的锁都是30s
    //1)、锁的自动续期,如果业务超长,运行期间自动锁上新的30s。不用担心业务时间长,锁自动过期被删掉
    //2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题
    // myLock.lock(10,TimeUnit.SECONDS);   //10秒钟自动解锁,自动解锁时间一定要大于业务执行时间
    //问题:在锁时间到了以后,不会自动续期
    //1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是 我们制定的时间
    //2、如果我们指定锁的超时时间,就使用 lockWatchdogTimeout = 30 * 1000 【看门狗默认时间】
    //只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动的再次续期,续成30秒
    // internalLockLeaseTime 【看门狗时间】 / 3, 10s
    try {
    
    
        System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
        try {
    
     TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
    } catch (Exception ex) {
    
    
        ex.printStackTrace();
    } finally {
    
    
        //3、解锁  假设解锁代码没有运行,Redisson会不会出现死锁
        System.out.println("释放锁..." + Thread.currentThread().getId());
        myLock.unlock();
    }
    return "hello";
}

读写锁:Its characteristic is: it is guaranteed to be able to read the latest data. During modification, the write lock is an exclusive lock (mutual exclusion lock, exclusive lock), and the read lock is a shared lock

  • Write lock is not released, read lock must wait
  • Read + Read: Equivalent to lock-free, concurrent read, only recorded in Redis, all current read locks. They will all be locked at the same time
  • Write + Read: must wait for the write lock to be released
  • Write + write: blocking mode
  • Read + Write: There is a read lock. Writing also needs to wait
  • As long as there is read or write memory, you must wait
@GetMapping(value = "/write")
@ResponseBody
public String writeValue() {
    
    
    String s = "";
    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    RLock rLock = readWriteLock.writeLock();
    try {
    
    
        //1、改数据加写锁,读数据加读锁
        rLock.lock();
        s = UUID.randomUUID().toString();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        ops.set("writeValue",s);
        TimeUnit.SECONDS.sleep(10);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    } finally {
    
    
        rLock.unlock();
    }

    return s;
}

@GetMapping(value = "/read")
@ResponseBody
public String readValue() {
    
    
    String s = "";
    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    //加读锁
    RLock rLock = readWriteLock.readLock();
    try {
    
    
        rLock.lock();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        s = ops.get("writeValue");
        try {
    
     TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
    } catch (Exception e) {
    
    
        e.printStackTrace();
    } finally {
    
    
        rLock.unlock();
    }

    return s;
}

信号量:Similar to parking in a garage, you cannot park when the garage is full, and you can continue to park when the car is driven away! Can solve the problem of distributed current limiting

@GetMapping(value = "/park")
@ResponseBody
public String park() throws InterruptedException {
    
    

    RSemaphore park = redisson.getSemaphore("park");
    park.acquire();     //获取一个信号、获取一个值,占一个车位
    boolean flag = park.tryAcquire();

    if (flag) {
    
    
        //执行业务
    } else {
    
    
        return "error";
    }

    return "ok=>" + flag;
}

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

闭锁

/**
 * 放假、锁门
 * 1班没人了
 * 5个班,全部走完,我们才可以锁大门
 * 分布式闭锁
 */

@GetMapping(value = "/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
    
    

    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.trySetCount(5);
    door.await();       //等待闭锁完成

    return "放假了...";
}

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

    return id + "班的人都走了...";
}

Use Redisson to integrate into the project

The above is an example of reentrant locks, read-write locks, semaphores, and locks. Next, let’s see how I use them in the project. Create a read lock to obtain distributed locks and unlock functions.

public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {
    
    

    //1、占分布式锁。去redis占坑
    //(锁的粒度,越细越快:具体缓存的是某个数据,11号商品) product-11-lock
    //RLock catalogJsonLock = redissonClient.getLock("catalogJson-lock");
    //创建读锁
    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("catalogJson-lock");

    RLock rLock = readWriteLock.readLock();

    Map<String, List<Catelog2Vo>> dataFromDb = null;
    try {
    
    
        rLock.lock();
        //加锁成功...执行业务
        dataFromDb = getDataFromDb();
    } finally {
    
    
        rLock.unlock();
    }
    //先去redis查询下保证当前的锁是自己的
    //获取值对比,对比成功删除=原子性 lua脚本解锁
    // String lockValue = stringRedisTemplate.opsForValue().get("lock");
    // if (uuid.equals(lockValue)) {
    
    
    //     //删除我自己的锁
    //     stringRedisTemplate.delete("lock");
    // }

    return dataFromDb;

}

Guess you like

Origin blog.csdn.net/MarkusZhang/article/details/107977048