前言
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的版本对应。
具体对应的关系如下:
注意: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:7004" - "redis://127.0.0.1:7001" - "redis://127.0.0.1:7000" scanInterval: 1000 pingConnectionInterval: 0 keepAlive: false tcpNoDelay: false threads: 16 nettyThreads: 32 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); } }); }
执行结果如下:
总结
本文针对Spring Boot集成Redisson的基本使用,关于Redisson源码的分析将在后续的文章中进行讲解,如有疑问,请随时反馈,