基于Redis集群SpringBoot整合Redisson实现分布式锁

分布式锁使用场景

​ 用锁保护共享资源,例如 生成唯一的序列号、电商系统下单前确保库存足够等。

RedLock算法的核心原理:

使用N个完全独立、没有主从关系的Redis master节点以保证他们大多数情况下都不会同时宕机,N一般为奇数。一个客户端需要做如下操作来获取锁:

  1. 获取当前时间(单位是毫秒)。
  2. 轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。
  3. 客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁((N/2) +1),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
  4. 如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
  5. 如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

简单来说,就是利用多个的主节点,在超过半数以上的主节点获取锁成功,才算成功;否则算失败,回滚–删除之前在所有节点上获取的锁。

源码

Maven 依赖

    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starterartifactId>
    dependency>

    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-webartifactId>
    dependency>

    <dependency>
      <groupId>org.redissongroupId>
      <artifactId>redisson-spring-boot-starterartifactId>
      <version>3.15.5version>
    dependency>

    <dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
      <version>1.18.12version>
    dependency>

application.yml

server:
  port: 8091
spring:
  redisson:
    address: 127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 # Redis集群地址
    try-time: 0   #尝试时间
    lock-time: 4  #锁的时间
    password:     # redis密码
    scanInterval: 1000 # 扫描间隔
    retryAttempts: 5   # 命令失败重试次数
    timeout: 3000    # 超时时间

Redission 配置类

@Configuration
public class RedissonConfig {
     

    @Value(value = "${spring.redisson.address}")
    private String redissonAddress;

    @Value(value = "${spring.redisson.password}")
    private String redissonPassword;

    @Value(value = "${spring.redisson.scanInterval}")
    private int redissonScanInterval;

    @Value(value = "${spring.redisson.retryAttempts}")
    private int redissonRetryAttempts;

    @Value(value = "${spring.redisson.timeout}")
    private int redissonTimeout;

    @Bean
    public RedissonClient redissonClient() {
     
        String[] nodes = redissonAddress.split(",");
        for (int i = 0; i < nodes.length; i++) {
     
            nodes[i] = "redis://" + nodes[i];
        }

        Config config = new Config();
        config.useClusterServers() //这是用的集群server
                .setScanInterval(redissonScanInterval) //设置集群状态扫描时间
                .addNodeAddress(nodes).setRetryAttempts(redissonRetryAttempts).setTimeout(redissonTimeout);
        if (StringUtils.isNotEmpty(redissonPassword)) {
     
            config.useClusterServers().setPassword(redissonPassword);
        }
        return Redisson.create(config);
    }
}

Controller 测试类

@RestController
@RequestMapping("api/redisson" )
@Slf4j
public class RedissonLockController {

    /**
     * 锁测试共享变量
     */
    private Integer lockCount = 10;

    /**
     * 无锁测试共享变量
     */
    private Integer count = 10;

    /**
     * 模拟线程数
     */

    private static int threadNum = 1000;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 模拟并发测试加锁和不加锁
     * @return
     */
    @GetMapping("/test")
    public void lock(){
        // 计数器
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < threadNum; i ++) {
            MyRunnable myRunnable = new MyRunnable(countDownLatch);
            Thread myThread = new Thread(myRunnable);
            myThread.start();
        }
        // 释放所有线程
        countDownLatch.countDown();
    }

    /**
     * 加锁测试
     */
    private void testLockCount() {
        RLock lock = redissonClient.getLock("myLock");
        try {
            // 加锁,设置超时时间2秒 上锁以后10秒自动解锁
            boolean getLock = lock.tryLock(2000,10000, TimeUnit.MILLISECONDS);
            if (!getLock) {
                log.info("当前线程:[{}]没有获得锁", Thread.currentThread().getName());
                return ;
            }
            lockCount--;
            log.info("lockCount值:"+lockCount);
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }finally {
            // 释放锁
            if(null != lock && lock.isLocked() && lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }

    /**
     * 无锁测试
     */
    private void testCount() {
        count--;
        log.info("count值:"+count);
    }

    public class MyRunnable implements Runnable {
        /**
         * 计数器
         */
        final CountDownLatch countDownLatch;
        public MyRunnable(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                // 阻塞当前线程,直到计时器的值为0
                countDownLatch.await();
            } catch (InterruptedException e) {
                log.error(e.getMessage(),e);
            }
            // 无锁操作
            //testCount();
            // 加锁操作
            testLockCount();
        }
    }
}

RedisLockApplication 启动类

@SpringBootApplication
public class RedisLockApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisLockApplication.class, args);
    }
}

测试

​ 访问:http://localhost:8091/api/redisson/test,控制台输出同步的序列号如下:
基于Redis集群SpringBoot整合Redisson实现分布式锁_第1张图片

RedLock 锁存在的问题

​在极端环境下,RedLock 存在锁超时不安全的问题,参考。

参考

https://redis.io/topics/distlock

https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers

https://github.com/redisson/redisson/blob/master/redisson-spring-boot-starter/README.md

源码

你可能感兴趣的:(SpringBoot)