redis使用分布式锁,除了我们自己借助setnx来实现之外,更为推荐的是借助redisson来完成,借助redisson,可以非常方便的使用redis分布锁,但是一个使用姿势不对,将可能导致锁无法释放问题
本文将介绍一下SpringBoot中redisson分布式锁的使用姿势,以及使用不当导致锁无法释放的演示
I. 项目环境 1. pom依赖 本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA + redis进行开发
下面是核心的pom.xml(源码可以再文末获取)
1 2 3 4 5 6 7 8 9 10 11 12 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.redisson</groupId > <artifactId > redisson-spring-boot-starter</artifactId > <version > 3.15.0</version > </dependency > </dependencies >
2. 配置文件 redis的配置,我们这里采用默认的配置,本机启动一个redis实例
1 2 3 4 5 spring: redis: host: 127.0 .0 .1 port: 6379 password:
II. 分布式锁 1. 使用姿势 核心类就是获取一个RedissonClient实例,然后借助它来获取锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Configuration public class RedissonConfig { @Value ("${spring.redis.host}" ) private String host; @Value ("${spring.redis.port}" ) private String port; @Value ("${spring.redis.password:}" ) private String password; @Bean public RedissonClient redissonClient () { Config config = new Config(); config.useSingleServer().setAddress("redis://" + host + ":" + port); if (StringUtils.isEmpty(password)) { config.useSingleServer().setPassword(null ); } else { config.useSingleServer().setPassword(password); } return Redisson.create(config); } }
一种非阻塞的使用方式形如
1 2 3 4 5 6 lock.tryLock(); try { } finally { lock.unlock(); }
这里没有显示设置锁的失效时间,默认持有锁30s,且由watch dog(看门狗)每隔10s续期一波,这里的自动续期,请重点关注,后面会说明因为它导致的锁无法释放
手动设置失效时间: tryLock(time, TimeUnit)
一个具体的分布式锁使用姿势如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void lockReIn (int id) { RLock rLock = redissonClient.getLock("lock_prefix_" + id); if (rLock.tryLock()) { try { System.out.println("------- 执行业务逻辑 --------" + Thread.currentThread()); Thread.sleep(100 ); System.out.println("------- 执行完毕 ----------" + Thread.currentThread()); } catch (InterruptedException e) { e.printStackTrace(); } finally { rLock.unlock(); } } else { System.out.println("get lock failed for " + Thread.currentThread()); } }
如果希望阻塞方式获取分布式锁时,使用RLock#lock()来替换RLock#tryLock()
2. 锁无法释放场景 重点关注下上面的自动续期方式,当我们使用姿势不对的时候,可能导致锁无法释放,那么什么样的场景会导致这个问题呢?
主动释放锁异常失败
watch dog 一直存活,不断的续期
我们借助线程池来演示这个场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 ExecutorService executorService = Executors.newFixedThreadPool(2 , new NamedThreadFactory("fixed-" )); ExecutorService customExecutorService = new ThreadPoolExecutor(0 , 1 , 1L , TimeUnit.MICROSECONDS, new LinkedBlockingQueue<Runnable>(), new NamedThreadFactory("custom-" )); private void unLockFailed (int id) { RLock rLock = redissonClient.getLock("lock_prefix_" + id); if (rLock.tryLock()) { try { System.out.println("------- 执行业务逻辑 " + id + " --------" + Thread.currentThread()); } finally { System.out.println(1 / 0 ); rLock.unlock(); } } else { System.out.println("get lock failed for " + Thread.currentThread()); } } public void testLock () { executorService.submit(new Runnable() { @Override public void run () { try { System.out.println("threadId fix : " + Thread.currentThread().getId()); unLockFailed(2 ); } catch (Exception e) { e.printStackTrace(); } } }); customExecutorService.submit(new Runnable() { @Override public void run () { try { System.out.println("threadId custom : " + Thread.currentThread().getId()); unLockFailed(3 ); } catch (Exception e) { e.printStackTrace(); } } }); }
在使用锁的业务逻辑中,释放锁时模拟了释放失败的case
两个线程池
一个固定大小的线程池(线程不会被回收):再次访问时,之前持有锁的线程依然可以获取锁;另外一个不行
一个普通的线程池(线程会被回收):将没有线程能持有锁
上面这种case最主要的问题在于redissonClient作为单实例,这个实例不回收,看门狗的续期任务也不会取消;因此即便持有锁的业务逻辑走完了,抛异常了,但是续期任务没有感知,依然在默默的执行,从而导致分布式锁一直无法释放,直到redissonClient实例销毁
小结
RedissonClient公用时,主动释放锁失败,但是注意看门狗的任务不注销,分布式锁一直续期,从而导致分布式锁无法有效释放
II. 其他 0. 项目
1. 一灰灰Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
打赏
如果觉得我的文章对您有帮助,请随意打赏。
微信打赏
支付宝打赏