redis和redisson实现分布式锁

谁借莪1个温暖的怀抱¢ 2022-12-27 09:30 250阅读 0赞

文章目录

  • redis 实现分布式锁
    • 一、redis原生方式实现分布式锁
    • 二、Redisson实现分布式锁
      • 1.导入jar包
      • 2.设置连接配置
      • 3.编写代码实现分布式锁
    • 三、注解aop的方式加Redisson实现分布式锁(推荐)

redis 实现分布式锁

介绍三种方式实现分布式锁:

  1. Redis原生方式实现分布式锁
  2. Redisson实现分布式锁
  3. 注解aop的方式加Redisson实现分布式锁(推荐)

一、redis原生方式实现分布式锁

Redis 单线程

SETNX (set if not exists)

setnx key value 若key存在则添加失败,若key不存在才会添加存在

redisTempalte.setIfAbsetn(key,value,time) 设置 key 和 value 和 超时时间

  1. try {
  2. // todo 业务
  3. } finally {
  4. // get 值,若 value 与 设置的value相等 则 删除key
  5. }

简单的实现分布式锁:

  1. // 这里使用到了 springboot集成Redis 用到了springboot的 StringRedisTemplate
  2. @GetMapping("/transaction")
  3. public String transaction() {
  4. // 锁的键值
  5. String lockKey = "orderLock";
  6. // 设置客户端id
  7. String clientId = UUID.randomUUID().toString();
  8. try {
  9. // 设置锁 如果redis中已经存在 lockKey 则会添加失败
  10. Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
  11. if (result == null || !result) {
  12. return "error";
  13. }
  14. String stock = stringRedisTemplate.opsForValue().get("stock");
  15. if (StrUtil.isEmpty(stock)) {
  16. return ">>>>>>>> 数据异常请稍后重试";
  17. }
  18. int number = Integer.parseInt(stock);
  19. if (number > 0) {
  20. Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
  21. System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
  22. } else {
  23. System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
  24. }
  25. } finally {
  26. // 如果客户端id相等
  27. if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
  28. stringRedisTemplate.delete(lockKey);
  29. }
  30. }
  31. return "success";
  32. }

二、Redisson实现分布式锁

1.导入jar包

  1. <!-- redisson -->
  2. <dependency>
  3. <groupId>org.redisson</groupId>
  4. <artifactId>redisson</artifactId>
  5. <version>3.12.0</version>
  6. </dependency>

2.设置连接配置

  1. @Getter
  2. @Setter
  3. @ConfigurationProperties(prefix="redisson")
  4. public class RedissonProperties {
  5. private int timeout = 3000;
  6. private String address;
  7. private String password;
  8. private int connectionPoolSize = 64;
  9. private int connectionMinimumIdleSize=10;
  10. private int slaveConnectionPoolSize = 250;
  11. private int masterConnectionPoolSize = 250;
  12. private String[] sentinelAddresses;
  13. private String masterName;
  14. }

// 自动装配

  1. @Configuration
  2. @ConditionalOnClass(Config.class)
  3. @EnableConfigurationProperties(RedissonProperties.class)
  4. public class RedissonAutoConfiguration {
  5. @Autowired
  6. private RedissonProperties redssionProperties;
  7. // import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
  8. // @Autowired
  9. // private RedisProperties redisProperties;
  10. /** * 哨兵模式自动装配 * # 哨兵模式 * #redisson: * # master-name: mymaster * # password: * # sentinel-addresses: 10.47.91.83:26379,10.47.91.83:26380,10.47.91.83:26381 * @return */
  11. // @Bean
  12. // @ConditionalOnProperty(name="redisson.master-name")
  13. // RedissonClient redissonSentinel() {
  14. // Config config = new Config();
  15. // SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
  16. // .setMasterName(redssionProperties.getMasterName())
  17. // .setTimeout(redssionProperties.getTimeout())
  18. // .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
  19. // .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
  20. //
  21. // if(StrUtil.isNotBlank(redssionProperties.getPassword())) {
  22. // serverConfig.setPassword(redssionProperties.getPassword());
  23. // }
  24. // return Redisson.create(config);
  25. // }
  26. /** * 单机模式自动装配 * @return */
  27. @Bean
  28. @ConditionalOnProperty(name="redisson.address")
  29. RedissonClient redissonClient() {
  30. Config config = new Config();
  31. SingleServerConfig serverConfig = config.useSingleServer()
  32. .setAddress(redssionProperties.getAddress())
  33. .setTimeout(redssionProperties.getTimeout())
  34. .setDatabase(0)
  35. .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
  36. .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
  37. if(StrUtil.isNotBlank(redssionProperties.getPassword())) {
  38. serverConfig.setPassword(redssionProperties.getPassword());
  39. }
  40. return Redisson.create(config);
  41. }
  42. }
  43. /** * redis集群下的配置 cluster * @return */
  44. // @Bean
  45. // public RedissonClient redisson() {
  46. // //redisson版本是3.5,集群的ip前面要加上“redis://”,不然会报错,3.2版本可不加
  47. // List<String> clusterNodes = new ArrayList<>();
  48. // for (int i = 0; i < redisProperties.getCluster().getNodes().size(); i++) {
  49. // clusterNodes.add("redis://" + redisProperties.getCluster().getNodes().get(i));
  50. // }
  51. // Config config = new Config();
  52. // // 采用集群模式
  53. // ClusterServersConfig clusterServersConfig = config.useClusterServers()
  54. // .addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()]));
  55. // //设置密码 注意这里集群的 redis 密码都必须一致
  56. // clusterServersConfig.setPassword(redisProperties.getPassword());
  57. // return Redisson.create(config);
  58. // }

yaml中的配置文件

  1. # 哨兵模式
  2. #redisson:
  3. # master-name: mymaster
  4. # password:
  5. # sentinel-addresses: 10.47.91.83:26379,10.47.91.83:26380,10.47.91.83:26381
  6. # redis单机模式
  7. redisson:
  8. address: redis://81.69.43.66:6379
  9. password: root

3.编写代码实现分布式锁

// 实现分布式锁

  1. @GetMapping("/redisson")
  2. public String redisson() {
  3. String lockKey = "orderLock";
  4. // 获取锁
  5. RLock lock = redissonClient.getLock(lockKey);
  6. try {
  7. // 加锁
  8. lock.lock();
  9. String stock = stringRedisTemplate.opsForValue().get("stock");
  10. if (StrUtil.isEmpty(stock)) {
  11. return ">>>>>>>> 数据异常请稍后重试";
  12. }
  13. int number = Integer.parseInt(stock);
  14. if (number > 0) {
  15. Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
  16. System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
  17. } else {
  18. System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
  19. }
  20. } finally {
  21. System.out.println(">>>>>>>>>>>>>>>>> 解除锁定");
  22. // 解锁
  23. lock.unlock();
  24. }
  25. return "success";
  26. }

三、注解aop的方式加Redisson实现分布式锁(推荐)

// 定义注解类

  1. @Target({ ElementType.METHOD,ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface LockAction {
  4. /** * 锁的名称 */
  5. String value() default "";
  6. /** * 描述 */
  7. String description() default "";
  8. /** * 等待锁超时时间,默认 30 * @return */
  9. long waitTime() default 30;
  10. /** * 自动解锁时间,自动解锁时间一定得大于方法执行时间,否则会导致锁提前释放,默认100 * @return */
  11. long leaseTime() default 100;
  12. /** * 时间单位,默认为 秒 * @return */
  13. TimeUnit timeUnit() default TimeUnit.SECONDS;
  14. /** * 默认:可重入锁 * @return */
  15. LockTypeEnum lockType() default LockTypeEnum.REENTRANT_LOCK;
  16. }

// 定义AOP

  1. @Slf4j
  2. @Aspect
  3. @Component
  4. @Order(1) //order越小越是最先执行,但更重要的是最先执行的最后结束。order默认值是2147483647
  5. public class LockAspect implements ApplicationContextAware {
  6. private final String LOCK_CODE = "lockCode";
  7. private final RedissonClient redissonClient;
  8. private ApplicationContext applicationContext;
  9. public LockAspect(RedissonClient redissonClient) {
  10. this.redissonClient = redissonClient;
  11. }
  12. /** * 定义公共的切点 */
  13. @Pointcut("@annotation(LockAction)")
  14. public void log(){
  15. }
  16. /** * 环绕通知 * @param joinPoint * @return * @throws Throwable */
  17. @Around(value = "@annotation(lockAction)")
  18. public Object around(ProceedingJoinPoint joinPoint,LockAction lockAction) throws Throwable {
  19. String code = IdUtil.fastSimpleUUID();
  20. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
  21. .getRequestAttributes();
  22. HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
  23. String remoteUser = request.getRemoteUser();
  24. log.info(">>>>>>>>>>>> remoteUser 远程用户:{}",remoteUser);
  25. String requestURI = request.getRequestURI();
  26. log.info(">>>>>>>>>>>> requestURI 请求地址:{}",requestURI);
  27. request.setAttribute(LOCK_CODE, code);
  28. RLock lock = redissonClient.getLock(lockAction.value());
  29. Object proceed = null;
  30. try {
  31. // lock.lock(); // 这种方式最稳妥,框架自动续期
  32. // 采用自定义时间的方式,注意时间长短
  33. boolean b = lock.tryLock(lockAction.waitTime(), lockAction.leaseTime(), lockAction.timeUnit());
  34. if (b) {
  35. // 类似于 method.invoke 方法
  36. System.out.println(">>>>>>>>>>>>>>>>>>> 加锁成功");
  37. proceed = joinPoint.proceed();
  38. }
  39. } finally {
  40. lock.unlock();
  41. System.out.println(">>>>>>>>>>>>>>>>>>> 解锁成功");
  42. }
  43. // 第一个参数
  44. // String seckillId = joinPoint.getArgs()[0].toString();
  45. // 方法名
  46. // String name = joinPoint.getSignature().getName();
  47. // Class declaringType = joinPoint.getSignature().getDeclaringType();
  48. // Method[] methods = declaringType.getMethods();
  49. // Object proceed = null;
  50. // for (Method method : methods) {
  51. // if(name.equals(method.getName())){
  52. // // 获得方法上面的注解
  53. // String value = AnnotationUtil
  54. // .getAnnotationValue(method, LockAction.class, "value");
  55. // if (StrUtil.isEmpty(value)){
  56. // throw new RuntimeException("运行时发生异常");
  57. // }
  58. // // 获取锁
  59. // RLock lock = redissonClient.getLock(value);
  60. // try {
  61. // // 加锁,加锁结果
  62. // lock.lock();
  63. // System.out.println(">>>>>>>>>>>>>>>>>>> 枷锁成功");
  64. // // 类似于 method.invoke 方法
  65. // proceed = joinPoint.proceed();
  66. // } finally {
  67. // System.out.println(">>>>>>>>>>>>>>>> 解除锁定");
  68. // lock.unlock();
  69. // }
  70. // }
  71. // }
  72. return proceed;
  73. }
  74. /** * 异常通知,当目标方法抛出异常时,该方法会被触发 * @param joinPoint */
  75. @AfterThrowing(value = "log()",throwing = "e")
  76. public void afterThrowing(JoinPoint joinPoint, Exception e){
  77. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  78. HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
  79. String code = (String) request.getAttribute(LOCK_CODE);
  80. log.info(">>>>>>>>>> 错误码:{}", code);
  81. }
  82. @Override
  83. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  84. this.applicationContext = applicationContext;
  85. }
  86. }

// 分布式锁 注解实战使用

  1. @LockAction(value = "lockey")
  2. @GetMapping("/order/{parameter}")
  3. public String order(@PathVariable("parameter") String parameter) {
  4. String stock = stringRedisTemplate.opsForValue().get("stock");
  5. int number = Integer.parseInt(stock);
  6. if (number > 0) {
  7. Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
  8. System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
  9. } else {
  10. System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
  11. }
  12. return "success";
  13. }

发表评论

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

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

相关阅读