"Microservices in Action" Chapter 28 Distributed Lock Framework - Redisson

Series Article Directory

Chapter 28 Distributed Lock Framework-Redisson
Chapter 27 CAS
Chapter 26 Classification of Java Locks
Chapter 25 Java Multithreading Security and Locks
Chapter 2 Application of CountDownLatch and Semaphone
Chapter 1 Application of Java Thread Pool Technology

insert image description here



foreword

Based on the NIO-based Netty framework, Redisson makes full use of a series of advantages provided by the Redis key-value database. Based on the common interfaces in the Java utility toolkit, Redisson provides users with a series of common tool classes with distributed characteristics. This enables the toolkit originally used to coordinate single-machine multi-threaded concurrent programs to obtain the ability to coordinate distributed multi-machine multi-threaded concurrent systems, greatly reducing the difficulty of designing and developing large-scale distributed systems. At the same time, combined with various characteristic distributed services, it further simplifies the cooperation between programs in a distributed environment.

1. Working principle of redisson

insert image description here

2. Watchdog principle

Service A runs first, and service B is running. Before A's lock is released, A hangs up. Will there be a deadlock?
Answer: There is no deadlock, because the bottom layer has a watchdog mechanism
. The default lock time is 30s (watchdog time).
Automatic renewal of the lock: If the business is too long, a new 30s will be automatically locked during operation. Don’t worry that the business time is too long, and the lock will automatically expire. As long as the locked business is running, the current lock will not be renewed. If you do not manually unlock it in time, the lock will be automatically deleted after 30s by default
.

3. The integration of spring boot and redisson

3.1. Add inventory service:

stock-service

3.2. Add dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
   <!-- <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <version>2021.0.4.0</version>
    </dependency>-->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.19.1</version>
    </dependency>

3.3, add configuration

3.3.1, stand-alone

redisson:
  addr:
    singleAddr:
      host: redis://localhost:6379
      password: 123456
      database: 0
      pool-size: 10

3.3.2. Cluster

redisson:
  addr:
    cluster:
      hosts: redis://47.96.11.185: 6370,...,redis://47.96.11.185:6373
      password : 123456

3.3.3, master-slave

redisson:
  addr:
    masterAndSlave:
      masterhost: redis : //47.96.11.185 : 6370
      slavehosts: redis://47.96.11.185: 6371,redis://47.96.11.185:6372
      password : 123456
      database : 0

3.4, configure RedissonClient

3.4.1, stand-alone

/**
 * 配置RedissonClient
 */
@Configuration
public class RedissonConfig {
    
    
    @Value("${redisson.addr.singleAddr.host}")
    private String host;
    @Value("${redisson.addr.singleAddr.password}")
    private String password;
    @Value("${redisson.addr.singleAddr.database}")
    private int database;
    @Value("${redisson.addr.singleAddr.pool-size}")
    private int poolSize;
    @Bean
    public RedissonClient redissonClient(){
    
    
        Config config = new Config();
        config.useSingleServer().setAddress(host)
                .setPassword(password)
                .setDatabase(database)
                .setConnectionPoolSize(poolSize)
                .setConnectionMinimumIdleSize(poolSize);
        return Redisson.create(config);
    }
}

3.4.2. Cluster

/**
 * 配置RedissonClient
 */
@Configuration
public class RedissonConfig {
    
    
    @Value("${redisson.addr.cluster.hosts}")
    private String hosts;
    @Value("${redisson.addr.cluster.password}")
    private String password;

    /**
     * 集群模式
     *
     * @return
     */
    @Bean
    public RedissonClient redissonClient() {
    
    
        Config config = new Config();
        config.useClusterServers().addNodeAddress(hosts.split("[,]"))
                .setPassword(password)
                .setScanInterval(2000)
                .setMasterConnectionPoolSize(10000)
                .setSlaveConnectionPoolSize(10000);
        return Redisson.create(config);
    }
}

3.4.3. Cluster

/**
 * 配置RedissonClient
 */
@Configuration
public class RedissonConfig {
    
    
    @Value("${redisson.addr.masterAndSlave.masterhost}")
    private String masterhost;
    @Value("${redisson.addr.masterAndSlave.slavehosts}")
    private String slavehosts;
    @Value("${redisson.addr.masterAndSlave.password}")
    private String password;
    @Value("${redisson.addr.masterAndSlave.database}")
    private int database;

    /**
     * 主从模式
     *
     * @return
     */
    @Bean
    public RedissonClient redissonClient() {
    
    
        Config config = new Config();
        config.useMasterSlaveServers()
                .setMasterAddress(masterhost)
                .addSlaveAddress(slavehosts.split("[,]"))
                .setPassword(password)
                .setDatabase(database)
                .setMasterConnectionPoolSize(10000)
                .setSlaveConnectionPoolSize(10000);
        return Redisson.create(config);
    }
}

3.5. Use of Redisson

  • Get lock - fair lock and unfair lock
    // get fair lock
    RLock lock = redissonClient . getFairLock ( skuId );
    // get unfair lock
    RLock lock = redissonClient . getLock ( skuId );
  • 加锁 —— 阻塞锁和⾮阻塞锁
    // 阻塞锁(如果加锁成功之后,超时时间为 30s ;加锁成功开启看⻔狗,剩 5s 延⻓过期时间)
    lock . lock ();
    // 阻塞锁(如果加锁成功之后,设置⾃定义 20s 的超时时间)
    lock . lock ( 20 , TimeUnit . SECONDS );
    // ⾮阻塞锁(设置等待时间为 3s ;如果加锁成功默认超时间为 30s )
    boolean b = lock . tryLock ( 3 , TimeUnit . SECONDS );
    // ⾮阻塞锁(设置等待时间为 3s ;如果加锁成功设置⾃定义超时间为 20s )
    boolean b = lock . tryLock ( 3 , 20 , TimeUnit . SECONDS );
  • Release the lock
    lock.unlock();
  • Application example
    // fair non-blocking lock
    RLock lock = redissonClient . getFairLock ( skuId );
    boolean b = lock . tryLock ( 3 , 20 , TimeUnit . SECONDS );
  • Case of inventory reduction and locking

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/stock")
public class StockController {
    
    
    @Autowired
    private RedissonClient redissonClient;
    @GetMapping("/reduceStock")
    public void reduceStock(@RequestParam String productId){
    
    
        // 获取⾮公平锁
        RLock lock = this.redissonClient.getLock("stock:" + productId);
        // 阻塞锁(如果加锁成功之后,设置⾃定义 20s 的超时时间)
        lock.lock(30, TimeUnit.SECONDS);
        System.out.println("加锁成功." + Thread.currentThread().getName());

        try {
    
    
            TimeUnit.SECONDS.sleep(25);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            System.out.println("解锁成功." + Thread.currentThread().getName());
            lock.unlock();
        }

    }
}

Test: The browser initiates two times to reduce inventory
http://localhost:8099/stock/reduceStock?productId=001

3.6, aop realizes distributed lock

3.6.1, Definition Notes

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributeLock {
    
    
    /**
     * 参数下标
     * @return
     */
    int[] lockIndex() default {
    
    -1} ;

    /**
     * 锁的等待时间
     * @return
     */
    long waitTime() default 3000;

    /**
     * 时间单位
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

3.6.2, define the aspect

/**
 * 定义分布式锁的切面
 */
@Component
@Aspect
public class DistributeLockAspect {
    
    
    @Autowired
    private RedissonClient redissonClient;

    @Around(value = "@annotation(lock)")
    public void distibuteLock(ProceedingJoinPoint proceedingJoinPoint, DistributeLock lock){
    
    
        Signature signature = proceedingJoinPoint.getSignature();
        StringBuilder stringBuilder = new StringBuilder();
        //方法所属的类
        String declaringTypeName = signature.getDeclaringTypeName();
        String name = signature.getName();

        stringBuilder.append(declaringTypeName);
        stringBuilder.append(name);

        //获取调用方法的参数
        Object[] args = proceedingJoinPoint.getArgs();

        int[] ints = lock.lockIndex();

        if(args != null) {
    
    
            final int length = args.length;
            if (length >0) {
    
    
                //考虑下标越界
                for (int anInt : ints) {
    
    
                    //把合法下标值放到sb
                    if (anInt >= 0 && anInt < length){
    
    
                        stringBuilder.append(JSON.toJSONString(args[anInt]));
                    }
                }
            }
        }

        //将方法的信息转成md5,作为锁的标识
        String key = SecureUtil.md5(stringBuilder.toString());


        //获取锁
        RLock rLock = redissonClient.getLock(key);
        //从注解获取时间单位
        TimeUnit timeUnit = lock.timeUnit();
        //从注解等待时间
        long waitTime = lock.waitTime();

        //执行业务代码
        try {
    
    
            //加锁
            rLock.tryLock(waitTime,timeUnit);
            System.out.println("成功加锁。" + Thread.currentThread().getName());
            proceedingJoinPoint.proceed();
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            //解锁
            rLock.unlock();
            System.out.println("成功解锁。" + Thread.currentThread().getName());
        }
    }
}

Use of annotations:

@DistributeLock(lockIndex = {
    
    0,1},waitTime = 3,timeUnit = TimeUnit.SECONDS)

Guess you like

Origin blog.csdn.net/s445320/article/details/131132334