Spring Boot 集成Redisson实现分布式锁

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

前言

上一章讲解了Spring Boot集成Redis实现单机分布式锁,但是针对单机分布式锁还是存在锁定续期、可重入的问题,本文将采用Spring Boot 集成Ression实现分布式锁进行详细讲解。

分布式锁实现

引入jar包

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
      <exclusion>
         <groupId>io.lettuce</groupId>
         <artifactId>lettuce-core</artifactId>
      </exclusion>  
    </exclusions>
   </dependency>
	  
      <dependency>
         <groupId>org.redisson</groupId>
         <artifactId>redisson-spring-boot-starter</artifactId>
         <version>3.13.6</version>
     </dependency>
复制代码

说明:关于集成Redisson,我们需要注意与Spring Boot的版本对应。具体对应的关系如下:

图片.png

注意:3.13.6对应的Spring Boot的版本为2.3.0,而redis-spring-data为redis-spring-data-23。我们可以通过查看pom文件的引用从而得到依赖关系。

Redisson的配置

application.yml中引入redisson.yml配置

  redis:
    redisson:
      file: classpath:redisson.yml

复制代码

redisson.yml配置

singleServerConfig:
  password: xxxx
  address: "redis://127.0.0.1:6379"
  database: 1
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.FstCodec> {}
transportMode: "NIO"
复制代码

说明:本文配置的是单机环境,如果需要配置集群环境,可以采用如下配置:

 clusterServersConfig:
          idleConnectionTimeout: 10000
          connectTimeout: 10000
          timeout: 3000
          retryAttempts: 3
          retryInterval: 1500
          failedSlaveReconnectionInterval: 3000
          failedSlaveCheckInterval: 60000
          password: null
          subscriptionsPerConnection: 5
          clientName: null
          loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
          subscriptionConnectionMinimumIdleSize: 1
          subscriptionConnectionPoolSize: 50
          slaveConnectionMinimumIdleSize: 24
          slaveConnectionPoolSize: 64
          masterConnectionMinimumIdleSize: 24
          masterConnectionPoolSize: 64
          readMode: "SLAVE"
          subscriptionMode: "SLAVE"
          nodeAddresses:
          - "redis://127.0.0.1:6379"
          - "redis://127.0.0.1:6380"
          - "redis://127.0.0.1:6381"
          scanInterval: 1000
          pingConnectionInterval: 0
          keepAlive: false
          tcpNoDelay: false
        threads: 6
        nettyThreads: 12
        codec: !<org.redisson.codec.MarshallingCodec> {}
        transportMode: "NIO"
复制代码

封装Redisson工具类

@Component
public class RedissonLockUtil
{
    private static final Logger logger = LoggerFactory.getLogger(RedissonLockUtil.class);
    
        @Autowired
        private RedissonClient redissonClient;
     
        /**
         * 加锁
         * @param lockKey
         * @return
         */
        public RLock lock(String lockKey) 
        {
            RLock lock = redissonClient.getLock(lockKey);
            return lock;
        }
     
        /**
         * 公平锁
         * @param key
         * @return
         */
        public RLock fairLock(String key) 
        {
            return redissonClient.getFairLock(key);
        }
        
        /**
         * 带超时的锁
         * @param lockKey
         * @param timeout 超时时间 单位:秒
         */
        public RLock lock(String lockKey, int timeout) 
        {
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock(timeout, TimeUnit.SECONDS);
            return lock;
        }
        
        /**
         * 读写锁
         * @param key
         * @return
         */
        public RReadWriteLock readWriteLock(String key) {
            return redissonClient.getReadWriteLock(key);
        }
     
        /**
         * 带超时的锁
         * @param lockKey
         * @param unit 时间单位
         * @param timeout 超时时间
         */
        public RLock lock(String lockKey, TimeUnit unit ,int timeout) {
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock(timeout, unit);
            return lock;
        }
     
        /**
         * 加锁
         * @param key
         * @param supplier
         * @return
         */
        public <T> T lock(String key, Supplier<T> supplier) {
            RLock lock = lock(key);
            try {
                lock.lock();
                return supplier.get();
            } finally {
                if (lock != null && lock.isLocked()) {
                    lock.unlock();
                }
            }
        }

     
        /**
         * 尝试获取锁
         * @param lockKey
         * @param waitTime 等待时间
         * @param leaseTime 自动释放锁时间
         * @return
         */
        public  boolean tryLock(String lockKey, int waitTime, int leaseTime) {
            RLock lock = redissonClient.getLock(lockKey);
            try {
                return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                return false;
            }
        }
     
        /**
         * 尝试获取锁
         * @param lockKey
         * @param unit 时间单位
         * @param waitTime 等待时间
         * @param leaseTime 自动释放锁时间
         * @return
         */
        public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
            RLock lock = redissonClient.getLock(lockKey);
            try {
                return lock.tryLock(waitTime, leaseTime, unit);
            } catch (InterruptedException e) {
                return false;
            }
        }
     
        /**
         * 释放锁
         * @param lockKey
         */
        public void unlock(String lockKey) {
            RLock lock = redissonClient.getLock(lockKey);
            lock.unlock();
        }
     
     
        /**
         * 释放锁
         * @param lock
         */
        public void unlock(RLock lock) 
        {
            lock.unlock();
        }
    }  
复制代码

模拟秒杀扣减库存

 public int lockStock()
    {
        String lockKey="lock:stock";
        String clientId = UUID.randomUUID().toString();
        
        //加锁
        RLock lock=redissonLockUtil.lock(lockKey);
        lock.lock();
        
        try
        {
           logger.info("加锁成功 clientId:{}",clientId);
           int stockNum= Integer.valueOf((String)redisUtil.get("seckill:goods:stock"));
           if(stockNum>0)
           {   
              stockNum--;
              redisUtil.set("seckill:goods:stock",String.valueOf(stockNum));
              logger.info("秒杀成功,剩余库存:{}",stockNum);
           }
           else
           {
              logger.error("秒杀失败,剩余库存:{}", stockNum);
           }
           //获取库存数量
           return stockNum;
        }
        catch (Exception e)
        {
           logger.error("decry stock eror",e);
        }
        finally
        {
            if(lock!=null)
            {
                lock.unlock(); 
            }
        }
        return 0;
    }
复制代码

测试代码

@RequestMapping("/redisLockTest")
    public void redisLockTest()
    {
        // 初始化秒杀库存数量
        redisUtil.set("seckill:goods:stock", "10");
        
        List<Future> futureList = new ArrayList<>();
        
        //多线程异步执行
        ExecutorService executors = Executors.newScheduledThreadPool(10);
        //
        for (int i = 0; i < 30; i++)
        {
            futureList.add(executors.submit(this::lockStock));
            
            try 
            {
               Thread.sleep(100);
            } 
            catch (InterruptedException e) 
            {
               logger.error("redisLockTest error",e);
            }
        }
        
        // 等待结果,防止主线程退出
        futureList.forEach(t -> {
            try 
            {
                int stockNum =(int) t.get();
                logger.info("库存剩余数量:{}",stockNum);
            }
            catch (Exception e)
            {
               logger.error("get stock num error",e);
            }
        });
    }
复制代码

执行结果如下:

图片.png

总结

本文针对Spring Boot集成Redisson的基本使用,关于Redisson源码的分析将在后续的文章中进行讲解,如有疑问,请随时反馈,

猜你喜欢

转载自juejin.im/post/7128050664336261157