Spring Boot 分布式锁的实现

灰太狼 2020-05-25 06:02 1034阅读 2赞

Spring Boot 分布式锁的实现

前言

面试总是会被问到有没有用过分布式锁、redis 锁,大部分读者平时很少接触到,所以只能很无奈的回答 “没有”。本文通过 Spring Boot 整合 redisson 来实现分布式锁,并结合 demo 测试结果。

首先看下大佬总结的图

正文

添加依赖

  1. <!--redis-->
  2. <dependency>
  3. <groupId>
  4. org.springframework.boot
  5. </groupId>
  6. <artifactId>
  7. spring-boot-starter-data-redis
  8. </artifactId>
  9. </dependency>
  10. <!--redisson-->
  11. <dependency>
  12. <groupId>
  13. org.redisson
  14. </groupId>
  15. <artifactId>
  16. redisson-spring-boot-starter
  17. </artifactId>
  18. <version>
  19. 3.10.6
  20. </version>
  21. </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. public class RedissonConfig {
  8. @Value("${spring.redis.host}")
  9. private String host;
  10. @Value("${spring.redis.port}")
  11. private String port;
  12. @Value("${spring.redis.password:}")
  13. private String password;
  14. @Bean
  15. public RedissonClient redissonClient() {
  16. Config config = new Config();
  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. return Redisson.create(config);
  29. }
  30. }

Redisson 工具类

  1. /**
  2. * redis分布式锁帮助类
  3. *
  4. * @author gourd
  5. */
  6. public class RedisLockUtil {
  7. private static DistributedLocker distributedLocker = SpringContextHolder.getBean("distributedLocker", DistributedLocker.class);
  8. /**
  9. * 加锁
  10. *
  11. * @param lockKey
  12. * @return
  13. */
  14. public static RLock lock(String lockKey) {
  15. return distributedLocker.lock(lockKey);
  16. }
  17. /**
  18. * 释放锁
  19. *
  20. * @param lockKey
  21. */
  22. public static void unlock(String lockKey) {
  23. distributedLocker.unlock(lockKey);
  24. }
  25. /**
  26. * 释放锁
  27. *
  28. * @param lock
  29. */
  30. public static void unlock(RLock lock) {
  31. distributedLocker.unlock(lock);
  32. }
  33. /**
  34. * 带超时的锁
  35. *
  36. * @param lockKey
  37. * @param timeout 超时时间 单位:秒
  38. */
  39. public static RLock lock(String lockKey, int timeout) {
  40. return distributedLocker.lock(lockKey, timeout);
  41. }
  42. /**
  43. * 带超时的锁
  44. *
  45. * @param lockKey
  46. * @param unit 时间单位
  47. * @param timeout 超时时间
  48. */
  49. public static RLock lock(String lockKey, int timeout, TimeUnit unit) {
  50. return distributedLocker.lock(lockKey, unit, timeout);
  51. }
  52. /**
  53. * 尝试获取锁
  54. *
  55. * @param lockKey
  56. * @param waitTime 最多等待时间
  57. * @param leaseTime 上锁后自动释放锁时间
  58. * @return
  59. */
  60. public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
  61. return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
  62. }
  63. /**
  64. * 尝试获取锁
  65. *
  66. * @param lockKey
  67. * @param unit 时间单位
  68. * @param waitTime 最多等待时间
  69. * @param leaseTime 上锁后自动释放锁时间
  70. * @return
  71. */
  72. publicstaticboolean
  73. tryLock(
  74. String
  75. lockKey,
  76. TimeUnit
  77. unit,
  78. int
  79. waitTime,
  80. int
  81. leaseTime) {
  82. return
  83. distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
  84. }
  85. /**
  86. * 获取计数器
  87. *
  88. * @param name
  89. * @return
  90. */
  91. public static RCountDownLatch getCountDownLatch(String name) {
  92. return distributedLocker.getCountDownLatch(name);
  93. }
  94. /**
  95. * 获取信号量
  96. *
  97. * @param name
  98. * @return
  99. */
  100. public static RSemaphore getSemaphore(String name) {
  101. return distributedLocker.getSemaphore(name);
  102. }
  103. }

底层封装

  1. /**
  2. * @author gourd
  3. */
  4. public interface DistributedLocker {
  5. RLock lock(String lockKey);
  6. RLock lock(String lockKey, int timeout);
  7. RLock lock(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(RLock lock);
  11. }
  12. /**
  13. * @author gourd
  14. */
  15. @Component
  16. public class RedisDistributedLocker implements DistributedLocker {
  17. @Autowired
  18. private
  19. RedissonClient redissonClient;
  20. @Override
  21. public RLock lock(String lockKey) {
  22. RLock lock = redissonClient.getLock(lockKey);
  23. lock.lock();
  24. return lock;
  25. }
  26. @Override
  27. public RLock lock(String lockKey, int leaseTime) {
  28. RLock lock = redissonClient.getLock(lockKey);
  29. lock.lock(leaseTime, TimeUnit.SECONDS);
  30. return lock;
  31. }
  32. @Override
  33. public RLock lock(String lockKey, TimeUnit unit, int timeout) {
  34. RLock lock = redissonClient.getLock(lockKey);
  35. lock.lock(timeout, unit);
  36. return lock;
  37. }
  38. @Override
  39. public boolean
  40. tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
  41. RLock lock = redissonClient.getLock(lockKey);
  42. try {
  43. return lock.tryLock(waitTime, leaseTime, unit);
  44. } catch (InterruptedException e) {
  45. return false;
  46. }
  47. }
  48. @Override
  49. public void
  50. unlock(String lockKey) {
  51. RLock lock = redissonClient.getLock(lockKey);
  52. lock.unlock();
  53. }
  54. @Override
  55. public void
  56. unlock(RLock lock) {
  57. lock.unlock();
  58. }
  59. }

测试

模拟并发测试

  1. /**
  2. * redis分布式锁控制器
  3. *
  4. * @author gourd
  5. * @since 2019-07-30
  6. */
  7. @RestController
  8. @Api(tags = "redisson", description = "redis分布式锁控制器")
  9. @RequestMapping("/redisson")
  10. @Slf4j
  11. public class RedissonLockController {
  12. /**
  13. * 锁测试共享变量
  14. */
  15. private Integer lockCount = 10;
  16. /**
  17. * 无锁测试共享变量
  18. */
  19. private Integer count = 10;
  20. /**
  21. * 模拟线程数
  22. */
  23. private static int hreadNum = 10;
  24. /**
  25. * 模拟并发测试加锁和不加锁
  26. *
  27. * @return
  28. */
  29. @GetMapping("/test")
  30. @ApiOperation(value = "模拟并发测试加锁和不加锁")
  31. public void lock() {
  32. // 计数器
  33. final CountDownLatch countDownLatch = new CountDownLatch(1);
  34. for (int i = 0; i < threadNum; i++) {
  35. MyRunnable myRunnable = new MyRunnable(countDownLatch);
  36. Thread myThread = new Thread(myRunnable);
  37. myThread.start();
  38. }
  39. // 释放所有线程
  40. countDownLatch.countDown();
  41. }
  42. /**
  43. * 加锁测试
  44. */
  45. private void testLockCount() {
  46. String lockKey = "lock-test";
  47. try {
  48. // 加锁,设置超时时间2s
  49. RedisLockUtil.lock(lockKey, 2, TimeUnit.SECONDS);
  50. lockCount--;
  51. log.info("lockCount值:" + lockCount);
  52. } catch (Exception e) {
  53. log.error(e.getMessage(), e);
  54. } finally {
  55. // 释放锁
  56. RedisLockUtil.unlock(lockKey);
  57. }
  58. }
  59. /**
  60. * 无锁测试
  61. */
  62. private void testCount() {
  63. count--;
  64. log.info("count值:" + count);
  65. }
  66. public class MyRunnable implements Runnable {
  67. /**
  68. * 计数器
  69. */
  70. final CountDownLatch countDownLatch;
  71. public MyRunnable(CountDownLatch countDownLatch) {
  72. this.countDownLatch = countDownLatch;
  73. }
  74. @Override
  75. public void run() {
  76. try {
  77. // 阻塞当前线程,直到计时器的值为0
  78. countDownLatch.await();
  79. } catch (InterruptedException e) {
  80. log.error(e.getMessage(), e);
  81. }
  82. // 无锁操作
  83. testCount();
  84. // 加锁操作
  85. testLockCount();
  86. }
  87. }
  88. }

调用接口后打印值:

测试结果

根据打印结果可以明显看到,未加锁的 count— 后值是乱序的,而加锁后的结果和我们预期的一样。

由于条件问题没办法测试分布式的并发。只能模拟单服务的这种并发,但是原理是一样,希望对大家有帮助。如有错误之处,欢迎指正。

发表评论

表情:
评论列表 (有 0 条评论,1034人围观)

还没有评论,来说两句吧...

相关阅读

    相关 spring boot redis分布式

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁。分布式锁的实现有很多种,比如基于[数据库][Link 1]、zookeeper等,本文主要介绍使用Redis做分布