当前位置:   article > 正文

Spring Boot中使用Redisson_springboot redisson

springboot redisson

微服务项目中,很多资源需要互斥使用,比如一些分布式任务,比如下单的处理,退货的处理等等。这些都需要用到借助分布式锁来保证处理的唯一性。 一开始我们也手工实现了分布式锁,但是随着业务的发展,我们对锁的特性也要求越来越完善,最后选用了Redis官方推荐的Redisson。

一、Spring Boot中使用Redisson

Spring Boot使用Redisson特别简单,只要引入一个依赖就可以,redis的配置跟其他的redis客户端可以兼容,可以不用再额外配置

二、引入依赖

 

  1. <dependency>
  2. <groupId>org.redisson</groupId>
  3. <artifactId>redisson-spring-boot-starter</artifactId>
  4. <version>3.13.2</version>
  5. </dependency>

三、属性文件Redis配置

 

  1. # Redis服务器地址
  2. spring.redis.host=127.0.0.1
  3. # Redis服务器连接端口
  4. spring.redis.port=6379

四、快速入门

4.1 改造RedisDistributedLockApplication启动类

使用锁RedissonClient,并实现业务逻辑在ApplicationRunner#run()方法。

 

  1. package com.erbadagang.springboot.redisdistributedlock;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.redisson.api.RLock;
  4. import org.redisson.api.RedissonClient;
  5. import org.springframework.boot.ApplicationArguments;
  6. import org.springframework.boot.ApplicationRunner;
  7. import org.springframework.boot.SpringApplication;
  8. import org.springframework.boot.autoconfigure.SpringBootApplication;
  9. import javax.annotation.Resource;
  10. @Slf4j
  11. @SpringBootApplication
  12. public class RedisDistributedLockApplication implements ApplicationRunner {
  13. public static void main(String[] args) {
  14. SpringApplication.run(RedisDistributedLockApplication.class, args);
  15. }
  16. /**
  17. * 直接注入RedissonClient就可以直接使用.
  18. */
  19. @Resource
  20. private RedissonClient redissonClient;
  21. @Override
  22. public void run(ApplicationArguments args) throws Exception {
  23. log.info("spring boot run");
  24. //创建锁
  25. RLock helloLock = redissonClient.getLock("hello");
  26. //加锁
  27. helloLock.lock();
  28. try {
  29. log.info("locked");
  30. Thread.sleep(1000 * 10);
  31. } finally {
  32. //释放锁
  33. helloLock.unlock();
  34. }
  35. log.info("finished");
  36. }
  37. }

4.2 测试

启动Redis和RedisDistributedLockApplication,控制台输出:

 

  1. 2020-08-02 22:51:17.169 INFO 8876 --- [main] c.e.s.r.RedisDistributedLockApplication : spring boot run
  2. 2020-08-02 22:51:36.486 INFO 8876 --- [main] c.e.s.r.RedisDistributedLockApplication : locked
  3. 2020-08-02 22:51:46.493 INFO 8876 --- [main] c.e.s.r.RedisDistributedLockApplication : finished

4.3 Rlock 常用的方法

 

  1. void lock();
  2. void lock(long leaseTime, TimeUnit unit);
  3. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  4. boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
  • 第一个方法void lock():第一表示lock表示去加锁,加锁成功,没有返回值,继续执行下面代码;但是如果redis已经有这个锁了,它会一直阻塞,直到锁的时间失效(默认30秒),再继续往下执行。这个方法是要保证一定要抢到锁的,它的默认过期时间也是30秒,和tryLock()不同的是,它如果没抢占到锁,会一直自旋。
  • 第二个方法void lock(long leaseTime, TimeUnit unit):和第一无参数lock逻辑一样,只是可以直接设置锁失效时间。用法:helloLock.lock(5, TimeUnit.SECONDS);
  • 第三个方法两个参数的boolean tryLock(long time, TimeUnit unit)表示尝试去加锁(第一个参数表示the maximum time to wait for the lock),加锁成功,返回true,继续执行true下面代码;但是如果redis已经有这个锁了他会等待,还拿不到锁它会返回false,执行false的代码块。为了实现waitTime,使用了redis的订阅发布功能。也就是没有抢到锁的线程订阅消息,直至waitTime过期返回false或者被通知新一轮的开始抢占锁。当然,它如果抢占到锁,锁的过期时间也是30秒,同样也会存在一个定时任务续过期时间,保证业务执行时间不会超过过期时间,抢占失败即返回false。

 

  1. String key ="product:001";
  2. RLock lock = redisson.getLock(key);
  3. try {
  4. boolean res = lock.tryLock(10,TimeUnit.SECONDS);
  5. if ( res){
  6. System.out.println("这里是你的业务代码");
  7. }else{
  8. System.out.println("系统繁忙");
  9. }
  10. }catch (Exception e){
  11. e.printStackTrace();
  12. }finally {
  13. lock.unlock();
  14. }

如果把lock.unlock();注释,第一次执行正常加锁,可以跑到业务逻辑代码,快速第二次执行发现他等待10秒,如果拿不到锁就走else的系统繁忙逻辑。

  • 三个参数的tryLock(long waitTime, long leaseTime, TimeUnit unit)表示尝试去加锁(第一个参数表示等待时间,第二个参数表示key的失效时间),加锁成功,返回true,继续执行true下面代码;如果返回false,它会等待第一个参数设置的时间,然后去执行false下面的代码。个方法的参数leaseTime如果不是-1的话,是不会有定时任务续过期时间的,也就存在业务处理时间可能超过过期时间的风险。其他的和tryLock(long waitTime, TimeUnit unit)一致。

 

 boolean res = lock.tryLock(5,3, TimeUnit.SECONDS);

这种情况,锁3秒失效,我们配置的是等待5秒,在单机刷的情况下,肯定每次都能拿到锁。

4.4 异步执行分布式锁

 

  1. /**
  2. * 异步锁
  3. */
  4. lock = redissonClient.getLock("erbadagang-lock");
  5. Future<Boolean> res = null;
  6. try {
  7. // lock.lockAsync();
  8. // lock.lockAsync(100, TimeUnit.SECONDS);
  9. res = lock.tryLockAsync(3, 100, TimeUnit.SECONDS);
  10. if (res.get()) {
  11. System.out.println("这里是你的Async业务代码");
  12. } else {
  13. System.out.println("系统繁忙Async");
  14. }
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. } finally {
  18. if (res.get()) {
  19. lock.unlock();
  20. }
  21. }
  22. log.info("finished");
  23. }

4.5 公平锁(Fair Lock)

Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。

 

  1. /**
  2. * 公平锁测试。
  3. */
  4. @Test
  5. public void testFairLock() {
  6. RLock fairLock = redissonClient.getFairLock("anyLock");
  7. try {
  8. // 最常见的使用方法
  9. fairLock.lock();
  10. // 支持过期解锁功能, 10秒钟以后自动解锁,无需调用unlock方法手动解锁
  11. fairLock.lock(10, TimeUnit.SECONDS);
  12. // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
  13. boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
  14. if (res) {
  15. System.out.println("这里是你的业务代码");
  16. } else {
  17. System.out.println("系统繁忙");
  18. }
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. } finally {
  22. fairLock.unlock();
  23. }
  24. }

Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:

 

  1. RLock fairLock = redisson.getFairLock("anyLock");
  2. fairLock.lockAsync();
  3. fairLock.lockAsync(10, TimeUnit.SECONDS);
  4. Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);

五、扩展实现

Redis单节点配置:

 

  1. server:
  2. port: 8080
  3. spring:
  4. application:
  5. name: redis-distributed-lock
  6. ################ Redis ##############
  7. redis:
  8. host: 127.0.0.1
  9. port: 6379
  10. #password:
  11. timeout: 3000
  12. lettuce:
  13. pool:
  14. max-active: 8
  15. max-wait: -1
  16. max-idle: 8
  17. min-idle: 0
  18. redisson:
  19. config:
  20. # 单节点配置
  21. singleServerConfig:
  22. # 连接空闲超时,单位:毫秒
  23. idleConnectionTimeout: 10000
  24. pingTimeout: 1000
  25. # 连接超时,单位:毫秒
  26. connectTimeout: 10000
  27. # 命令等待超时,单位:毫秒
  28. timeout: 3000
  29. # 命令失败重试次数,如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。
  30. # 如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。
  31. retryAttempts: 3
  32. # 命令重试发送时间间隔,单位:毫秒
  33. retryInterval: 1500
  34. # 重新连接时间间隔,单位:毫秒
  35. reconnectionTimeout: 3000
  36. # 执行失败最大次数
  37. failedAttempts: 3
  38. # 密码
  39. password: null
  40. # 单个连接最大订阅数量
  41. subscriptionsPerConnection: 5
  42. # 客户端名称
  43. clientName: null
  44. # 节点地址
  45. address: redis://127.0.0.1:6379
  46. # 发布和订阅连接的最小空闲连接数
  47. subscriptionConnectionMinimumIdleSize: 1
  48. # 发布和订阅连接池大小
  49. subscriptionConnectionPoolSize: 50
  50. # 最小空闲连接数
  51. connectionMinimumIdleSize: 32
  52. # 连接池大小
  53. connectionPoolSize: 64
  54. # 数据库编号
  55. database: 0
  56. # DNS监测时间间隔,单位:毫秒
  57. dnsMonitoringInterval: 5000
  58. # 线程池数量,默认值: 当前处理核数量 * 2
  59. threads: 0
  60. # Netty线程池数量,默认值: 当前处理核数量 * 2
  61. nettyThreads: 0
  62. # 编码
  63. #codec: !<org.redisson.codec.JsonJacksonCodec> {}
  64. # 传输模式
  65. transportMode: "NIO"

Redis集群配置:

 

  1. spring:
  2. redis:
  3. redisson:
  4. config:
  5. clusterServersConfig:
  6. idleConnectionTimeout: 10000
  7. connectTimeout: 10000
  8. timeout: 3000
  9. retryAttempts: 3
  10. retryInterval: 1500
  11. failedSlaveReconnectionInterval: 3000
  12. failedSlaveCheckInterval: 60000
  13. password: null
  14. subscriptionsPerConnection: 5
  15. clientName: null
  16. loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
  17. subscriptionConnectionMinimumIdleSize: 1
  18. subscriptionConnectionPoolSize: 50
  19. slaveConnectionMinimumIdleSize: 24
  20. slaveConnectionPoolSize: 64
  21. masterConnectionMinimumIdleSize: 24
  22. masterConnectionPoolSize: 64
  23. readMode: "SLAVE"
  24. subscriptionMode: "SLAVE"
  25. nodeAddresses:
  26. - "redis://192.168.35.142:7002"
  27. - "redis://192.168.35.142:7001"
  28. - "redis://192.168.35.142:7000"
  29. scanInterval: 1000
  30. pingConnectionInterval: 0
  31. keepAlive: false
  32. tcpNoDelay: false
  33. threads: 16
  34. nettyThreads: 32
  35. #codec: !<org.redisson.codec.FstCodec> {}
  36. transportMode: "NIO"

多线程测试:

 

  1. package com.erbadagang.springboot.redisdistributedlock;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.junit.jupiter.api.Test;
  4. import org.redisson.api.RLock;
  5. import org.redisson.api.RedissonClient;
  6. import org.springframework.boot.test.context.SpringBootTest;
  7. import javax.annotation.Resource;
  8. import java.util.concurrent.CountDownLatch;
  9. @SpringBootTest
  10. @Slf4j
  11. class RedisDistributedLockApplicationTests {
  12. /**
  13. * 有锁测试共享变量
  14. */
  15. private Integer lockCount = 10;
  16. /**
  17. * 无锁测试共享变量
  18. */
  19. private Integer count = 10;
  20. /**
  21. * 模拟线程数
  22. */
  23. private static int threadNum = 10;
  24. /**
  25. * 直接注入RedissonClient就可以直接使用.
  26. */
  27. @Resource
  28. private RedissonClient redissonClient;
  29. /**
  30. * 模拟并发测试加锁和不加锁2个方法。
  31. *
  32. * @return
  33. */
  34. @Test
  35. public void lock() {
  36. // 计数器
  37. final CountDownLatch countDownLatch = new CountDownLatch(1);
  38. for (int i = 0; i < threadNum; i++) {
  39. MyRunnable myRunnable = new MyRunnable(countDownLatch);
  40. Thread myThread = new Thread(myRunnable);
  41. myThread.start();
  42. }
  43. // 释放所有线程
  44. countDownLatch.countDown();
  45. }
  46. /**
  47. * 加锁测试
  48. */
  49. private void testLockCount() {
  50. String lockKey = "lock-test";
  51. //创建锁
  52. RLock helloLock = redissonClient.getLock(lockKey);
  53. try {
  54. //加锁
  55. helloLock.lock();
  56. lockCount--;
  57. log.info("lockCount值:" + lockCount);
  58. } catch (Exception e) {
  59. log.error(e.getMessage(), e);
  60. } finally {
  61. // 释放锁
  62. helloLock.unlock();
  63. }
  64. }
  65. /**
  66. * 无锁测试
  67. */
  68. private void testCount() {
  69. count--;
  70. log.info("count值:" + count);
  71. }
  72. public class MyRunnable implements Runnable {
  73. /**
  74. * 计数器
  75. */
  76. final CountDownLatch countDownLatch;
  77. public MyRunnable(CountDownLatch countDownLatch) {
  78. this.countDownLatch = countDownLatch;
  79. }
  80. @Override
  81. public void run() {
  82. try {
  83. // 阻塞当前线程,直到计时器的值为0
  84. countDownLatch.await();
  85. } catch (InterruptedException e) {
  86. log.error(e.getMessage(), e);
  87. }
  88. // 无锁操作
  89. testCount();
  90. // 加锁操作
  91. testLockCount();
  92. }
  93. }
  94. }

控制台输出:

 

  1. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-281] s.r.RedisDistributedLockApplicationTests : count值:3
  2. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-283] s.r.RedisDistributedLockApplicationTests : count值:2
  3. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-279] s.r.RedisDistributedLockApplicationTests : count值:5
  4. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-282] s.r.RedisDistributedLockApplicationTests : count值:2
  5. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-284] s.r.RedisDistributedLockApplicationTests : count值:2
  6. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-278] s.r.RedisDistributedLockApplicationTests : count值:5
  7. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-280] s.r.RedisDistributedLockApplicationTests : count值:4
  8. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-275] s.r.RedisDistributedLockApplicationTests : count值:2
  9. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-276] s.r.RedisDistributedLockApplicationTests : count值:2
  10. 2020-08-02 23:55:39.832 INFO 4144 --- [ Thread-277] s.r.RedisDistributedLockApplicationTests : count值:2
  11. 2020-08-02 23:55:39.848 INFO 4144 --- [ Thread-280] s.r.RedisDistributedLockApplicationTests : lockCount值:9
  12. 2020-08-02 23:55:39.848 INFO 4144 --- [ Thread-275] s.r.RedisDistributedLockApplicationTests : lockCount值:8
  13. 2020-08-02 23:55:39.883 INFO 4144 --- [ Thread-281] s.r.RedisDistributedLockApplicationTests : lockCount值:7
  14. 2020-08-02 23:55:39.885 INFO 4144 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
  15. 2020-08-02 23:55:39.885 INFO 4144 --- [ Thread-279] s.r.RedisDistributedLockApplicationTests : lockCount值:6
  16. 2020-08-02 23:55:39.885 INFO 4144 --- [ Thread-277] s.r.RedisDistributedLockApplicationTests : lockCount值:5
  17. 2020-08-02 23:55:39.885 INFO 4144 --- [ Thread-284] s.r.RedisDistributedLockApplicationTests : lockCount值:4
  18. 2020-08-02 23:55:39.903 INFO 4144 --- [ Thread-282] s.r.RedisDistributedLockApplicationTests : lockCount值:3

根据打印结果可以明显看到,未加锁的count--后值是乱序的,而加锁后的结果和我们预期的一样。由于条件问题没办法测试分布式的并发。只能模拟单服务的这种并发,但是原理是一样

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/853062
推荐阅读
相关标签
  

闽ICP备14008679号