分享

手把手教你 SpringBoot 分布式锁的实现

 鹰兔牛熊眼 2020-06-27

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

来源:https:///632yIv

推荐:https://www./?p=5035

前言

前段时间面试了一个高级程序员,问他了一些分布式锁的知识,回答的还不错。后来进了我们的团队,刚好让他接手一个新项目,需要用到分布式锁,我说这是你的强项,你来实现。

谁知,项目上线后,各种问题接连爆发出来。后来我找他谈话,主要是说,千万不要重复造轮子,直接吧 A 项目中的 Redisson 用法实现复制过来一份不就行了,干嘛非要自己搞一套,还搞出问题。。。

今天,我们一起来手把手来实现一个高效的 SpringBoot 分布式锁!本文通过 Spring Boot 整合 redisson 来实现分布式锁,并结合 demo 测试结果。

分析设计要点

当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,这里我认为以下几点是必须要考虑的。

1、互斥

在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,这是最基本的一点。

2、防止死锁

在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。

所以分布式非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。

3、性能

对于访问量大的共享资源,需要考虑减少锁等待的时间,避免导致大量线程阻塞。

所以在锁的设计时,需要考虑两点。

1、锁的颗粒度要尽量小。比如你要通过锁来减库存,那这个锁的名称你可以设置成是商品的ID,而不是任取名称。这样这个锁只对当前商品有效,锁的颗粒度小。

2、锁的范围尽量要小。比如只要锁2行代码就可以解决问题的,那就不要去锁10行代码了。

4、重入

我们知道ReentrantLock是可重入锁,那它的特点就是:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用。关于这点之后会做演示。

针对以上Redisson都能很好的满足,下面就来使用它。

首先看下常规Redis锁的处理图

代码实现
添加依赖
  1. <!--redis-->

  2. <dependency>

    1. <groupId>org.springframework.boot</groupId>

    2. <artifactId>spring-boot-starter-data-redis</artifactId>

  3. </dependency>

  4. <!--redisson-->

  5. <dependency>

    1. <groupId>org.redisson</groupId>

    2. <artifactId>redisson-spring-boot-starter</artifactId>

    3. <version>3.10.6</version>

  6. </dependency>

配置信息

  1. spring:

  2. # redis

  3. redis:

  4. host: 47.103.5.190

  5. port: 6379

  6. jedis:

  7. pool:

  8. # 连接池最大连接数(使用负值表示没有限制)

  9. max-active: 100

  10. # 连接池中的最小空闲连接

  11. max-idle: 10

  12. # 连接池最大阻塞等待时间(使用负值表示没有限制)

  13. max-wait: -1

  14. # 连接超时时间(毫秒)

  15. timeout: 5000

  16. #默认是索引为0的数据库

  17. database: 0

配置类

  1. /**

  2. * redisson 配置,下面是单节点配置:

  3. *

  4. * @author gourd

  5. */

  6. @Configuration

  7. publicclassRedissonConfig{

  8. @Value('${spring.redis.host}')

  9. privateString host;

  10. @Value('${spring.redis.port}')

  11. privateString port;

  12. @Value('${spring.redis.password:}')

  13. privateString password;

  14. @Bean

  15. publicRedissonClient redissonClient() {

  16. Config config = newConfig();

  17. //单节点

  18. config.useSingleServer().setAddress('redis://'+ host + ':'+ port);

  19. if(StringUtils.isEmpty(password)) {

  20. config.useSingleServer().setPassword(null);

  21. } else{

  22. config.useSingleServer().setPassword(password);

  23. }

  24. //添加主从配置

  25. // config.useMasterSlaveServers().setMasterAddress('').setPassword('').addSlaveAddress(new String[]{'',''});

  26. // 集群模式配置 setScanInterval()扫描间隔时间,单位是毫秒, //可以用'rediss://'来启用SSL连接

  27. // config.useClusterServers().setScanInterval(2000).addNodeAddress('redis://127.0.0.1:7000', 'redis://127.0.0.1:7001').addNodeAddress('redis://127.0.0.1:7002');

  28. returnRedisson.create(config);

  29. }

  30. }

Redisson 工具类

  1. /**

  2. * redis分布式锁帮助类

  3. *

  4. * @author gourd

  5. *

  6. */

  7. publicclassRedisLockUtil{

  8. privatestaticDistributedLocker distributedLocker = SpringContextHolder.getBean('distributedLocker',DistributedLocker.class);

  9. /**

  10. * 加锁

  11. * @param lockKey

  12. * @return

  13. */

  14. publicstaticRLocklock(String lockKey) {

  15. return distributedLocker.lock(lockKey);

  16. }

  17. /**

  18. * 释放锁

  19. * @param lockKey

  20. */

  21. publicstaticvoid unlock(String lockKey) {

  22. distributedLocker.unlock(lockKey);

  23. }

  24. /**

  25. * 释放锁

  26. * @param lock

  27. */

  28. publicstaticvoid unlock(RLocklock) {

  29. distributedLocker.unlock(lock);

  30. }

  31. /**

  32. * 带超时的锁

  33. * @param lockKey

  34. * @param timeout 超时时间 单位:秒

  35. */

  36. publicstaticRLocklock(String lockKey, int timeout) {

  37. return distributedLocker.lock(lockKey, timeout);

  38. }

  39. /**

  40. * 带超时的锁

  41. * @param lockKey

  42. * @param unit 时间单位

  43. * @param timeout 超时时间

  44. */

  45. publicstaticRLocklock(String lockKey, int timeout,TimeUnit unit ) {

  46. return distributedLocker.lock(lockKey, unit, timeout);

  47. }

  48. /**

  49. * 尝试获取锁

  50. * @param lockKey

  51. * @param waitTime 最多等待时间

  52. * @param leaseTime 上锁后自动释放锁时间

  53. * @return

  54. */

  55. publicstaticboolean tryLock(String lockKey, int waitTime, int leaseTime) {

  56. return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);

  57. }

  58. /**

  59. * 尝试获取锁

  60. * @param lockKey

  61. * @param unit 时间单位

  62. * @param waitTime 最多等待时间

  63. * @param leaseTime 上锁后自动释放锁时间

  64. * @return

  65. */

  66. publicstaticboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {

  67. return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);

  68. }

  69. /**

  70. * 获取计数器

  71. *

  72. * @param name

  73. * @return

  74. */

  75. publicstaticRCountDownLatch getCountDownLatch(String name){

  76. return distributedLocker.getCountDownLatch(name);

  77. }

  78. /**

  79. * 获取信号量

  80. *

  81. * @param name

  82. * @return

  83. */

  84. publicstaticRSemaphore getSemaphore(String name){

  85. return distributedLocker.getSemaphore(name);

  86. }

  87. }

底层封装

  1. /**

  2. * @author gourd

  3. */

  4. publicinterfaceDistributedLocker{

  5. RLocklock(String lockKey);

  6. RLocklock(String lockKey, int timeout);

  7. RLocklock(String lockKey, TimeUnit unit, int timeout);

  8. boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);

  9. void unlock(String lockKey);

  10. void unlock(RLocklock);

  11. }

  12. /**

  13. * @author gourd

  14. */

  15. @Component

  16. publicclassRedisDistributedLockerimplementsDistributedLocker{

  17. @Autowired

  18. privateRedissonClient redissonClient;

  19. @Override

  20. publicRLocklock(String lockKey) {

  21. RLocklock= redissonClient.getLock(lockKey);

  22. lock.lock();

  23. returnlock;

  24. }

  25. @Override

  26. publicRLocklock(String lockKey, int leaseTime) {

  27. RLocklock= redissonClient.getLock(lockKey);

  28. lock.lock(leaseTime, TimeUnit.SECONDS);

  29. returnlock;

  30. }

  31. @Override

  32. publicRLocklock(String lockKey, TimeUnit unit ,int timeout) {

  33. RLocklock= redissonClient.getLock(lockKey);

  34. lock.lock(timeout, unit);

  35. returnlock;

  36. }

  37. @Override

  38. publicboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {

  39. RLocklock= redissonClient.getLock(lockKey);

  40. try{

  41. returnlock.tryLock(waitTime, leaseTime, unit);

  42. } catch(InterruptedException e) {

  43. returnfalse;

  44. }

  45. }

  46. @Override

  47. publicvoid unlock(String lockKey) {

  48. RLocklock= redissonClient.getLock(lockKey);

  49. lock.unlock();

  50. }

  51. @Override

  52. publicvoid unlock(RLocklock) {

  53. lock.unlock();

  54. }

  55. }


测试
模拟并发测试
  1. /**

  2. * redis分布式锁控制器

  3. * @author gourd

  4. * @since 2019-07-30

  5. */

  6. @RestController

  7. @Api(tags = 'redisson', description = 'redis分布式锁控制器')

  8. @RequestMapping('/redisson')

  9. @Slf4j

  10. publicclassRedissonLockController{

  11. /**

  12. * 锁测试共享变量

  13. */

  14. privateInteger lockCount = 10;

  15. /**

  16. * 无锁测试共享变量

  17. */

  18. privateInteger count = 10;

  19. /**

  20. * 模拟线程数

  21. */

  22. privatestaticint threadNum = 10;

  23. /**

  24. * 模拟并发测试加锁和不加锁

  25. * @return

  26. */

  27. @GetMapping('/test')

  28. @ApiOperation(value = '模拟并发测试加锁和不加锁')

  29. publicvoidlock(){

  30. // 计数器

  31. finalCountDownLatch countDownLatch = newCountDownLatch(1);

  32. for(int i = 0; i < threadNum; i ++) {

  33. MyRunnable myRunnable = newMyRunnable(countDownLatch);

  34. Thread myThread = newThread(myRunnable);

  35. myThread.start();

  36. }

  37. // 释放所有线程

  38. countDownLatch.countDown();

  39. }

  40. /**

  41. * 加锁测试

  42. */

  43. privatevoid testLockCount() {

  44. String lockKey = 'lock-test';

  45. try{

  46. // 加锁,设置超时时间2s

  47. RedisLockUtil.lock(lockKey,2, TimeUnit.SECONDS);

  48. lockCount--;

  49. log.info('lockCount值:'+lockCount);

  50. }catch(Exception e){

  51. log.error(e.getMessage(),e);

  52. }finally{

  53. // 释放锁

  54. RedisLockUtil.unlock(lockKey);

  55. }

  56. }

  57. /**

  58. * 无锁测试

  59. */

  60. privatevoid testCount() {

  61. count--;

  62. log.info('count值:'+count);

  63. }

  64. publicclassMyRunnableimplementsRunnable{

  65. /**

  66. * 计数器

  67. */

  68. finalCountDownLatch countDownLatch;

  69. publicMyRunnable(CountDownLatch countDownLatch) {

  70. this.countDownLatch = countDownLatch;

  71. }

  72. @Override

  73. publicvoid run() {

  74. try{

  75. // 阻塞当前线程,直到计时器的值为0

  76. countDownLatch.await();

  77. } catch(InterruptedException e) {

  78. log.error(e.getMessage(),e);

  79. }

  80. // 无锁操作

  81. testCount();

  82. // 加锁操作

  83. testLockCount();

  84. }

  85. }

  86. }

调用接口后打印值:

测试结果
根据打印结果可以明显看到,未加锁的 count-- 后值是乱序的,而加锁后的结果和我们预期的一样。
本文只是实战项目中复制出来的部分代码,实际使用中封装成了 jar,内部系统只需要引用即可。最后,希望对大家有帮助。如有错误之处,欢迎指正!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多