码迷,mamicode.com
首页 > 其他好文 > 详细

基于Redis的分布式锁

时间:2019-06-16 09:28:12      阅读:81      评论:0      收藏:0      [点我收藏+]

标签:resource   简单   runtime   原子性   trace   中间   定义   etl   asp   

Redis分布式锁

1、锁场景

  • 需要对同一共享资源进行写操作
  • 对资源的访问是互斥的

任务通过竞争获取锁才能才能对该资源进行操作(竞争锁)

当有一个任务对资源进行操作时(占有锁)

其他任务不能对该资源进行操作(任务阻塞)

直到该任务操作结束(释放锁)

竞争锁 -> 占有锁 -> 任务阻塞 -> 释放锁

graph LR
A(竞争锁) -->B(占有锁) 
B(占有锁) --> C[任务阻塞] 
C[任务阻塞] --> D[释放锁]

2、实现思路

加锁:

通过setnx向特定的key写入一个随机值,并设置失效时间,写值成功即加锁成功

1、必须给锁设置失效时间:避免死锁

2、加锁时,每个节点产生一个随机字符串:避免误删锁

3、写入随机值与设置失效时间同一命令:保证原子性

解锁:

匹配随机值,删除redis上特定的key数据,保证获取数据,判断一致性,以及删除数据也是原子性

if redis.call("get", KEYS[1]) == ARGV[1] then
  return redis.call("del", KEYS[1])
else
  return 0
end

3、代码实现

1、RedisTemplate实现

基于RedisTemplate简单封装的RedisHelper,这不是重点

  • 实现Lock接口,完成加锁解锁的redis操作
  • 1、tryLock:加锁操作(注意设置有效时间防止死锁,和操作原子性)
  • 2、Lock:加锁等待,实现可重入锁。用带失效监听的中间件自然更优雅
  • 3、很多教程解锁用的lua脚本。我感觉一句读操作一句写操作不会产生不一致的问题,就直接解锁了。

    @Component
    @Scope("prototype")
    public class RedisLock implements Lock {

      /**
       * 锁的key
       */
      @Setter
      private String key;
      private String value;
      @Setter
      private long seconds = 10;
    
      @Resource
      private RedisHelper redisHelper;
    
      /**
       * 阻塞锁
       */
      @Override
      public void lock() {
          while (!tryLock()) {
              try {
                  Thread.sleep(5);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
    
      @Override
      public boolean tryLock() {
          String value = UUID.randomUUID().toString();
          Boolean b = redisHelper.setNx(this.key, value, this.seconds, TimeUnit.SECONDS);
          if (b) {
              this.value = value;
          }
          return b;
      }
    
      @Override
      @SneakyThrows
      public boolean tryLock(long time, @NonNull TimeUnit unit) {
          String value = UUID.randomUUID().toString();
          Boolean b = redisHelper.setNx(this.key, value, time, unit);
          if (b) {
              this.value = value;
          }
          return b;
      }
    
      @Override
      public void unlock() {
          String value = redisHelper.get(key);
          if (value.equals(this.value)) {
              redisHelper.del(key);
          }
      }
    
      @Override
      @NonNull
      public Condition newCondition() {
          return null;
      }

    }

  • 使用:

    @Resource
    private RedisLock redisLock;

    /**
    • 测试并发扣除库存,调用前先手动在redis中设置库存数量
    • set store 100
      */
      @GetMapping("/getStore")
      public R getStore() {

      int i;
      // 阻塞加锁
      redisLock.setKey("store-lock");
      redisLock.lock();
      try {
      String store = redisHelper.get("store");
      if ("0".equals(store)) {
      return R.error("库存不足");
      }
      i = Integer.parseInt(store) - 1;
      log.info("剩余库存数量:{}", i);
      redisHelper.set("store", String.valueOf(i));
      } finally {
      // 释放锁
      redisLock.unlock();
      }
      return R.ok(i);
      }

2、Redisson实现

自动续约,不用自己开线程检查

  • 依赖


    org.redisson
    redisson
    3.11.0

  • 配置

    /**
    • redisson配置
      */
      @Bean
      public Redisson redisson() {
      Config config = new Config();
      config.useSingleServer().setAddress("redis://127.0.0.1:6379")
      .setPassword("654321");
      return (Redisson) Redisson.create(config);
      }
  • 使用

    @Resource
    private Redisson redisson;

    @GetMapping("/getStore1")
    public R getStore1() {
    String lockKey = "redisLockKey";
    RLock lock = redisson.getLock(lockKey);
    lock.lock();
    int i;
    // 阻塞加锁
    try {
    String store = redisHelper.get("store1");
    if ("0".equals(store)) {
    return R.error("库存不足");
    }
    i = Integer.parseInt(store) - 1;
    log.info("剩余库存数量:{}", i);
    redisHelper.set("store1", String.valueOf(i));
    } finally {
    // 释放锁
    lock.unlock();
    }
    return R.ok(i);
    }

3、封装功能

  • 定义注解(可重入锁注解,其他类型略)

    /**
    • @description 可重入锁注解
      */
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface TryLock {

      /**
      • 锁的key,必须指定
        */
        String key();
      /**
      • 自动释放锁的超时时间:默认为10
        */
        long expire() default 10;
      /**
      • 时间单位:默认为秒
        */
        TimeUnit timeUnit() default TimeUnit.SECONDS;

    }

  • 定义注解处理器

    @Slf4j
    @Aspect
    @Component
    @AllArgsConstructor
    public class LockHandler {

      private Redisson redisson;
    
      /**
       * 可重入锁处理
       */
      @SneakyThrows
      @Around("@annotation(tryLock)")
      public Object lockAroundAction(ProceedingJoinPoint proceeding, TryLock tryLock) {
          String key = tryLock.key();
          long expire = tryLock.expire();
          TimeUnit timeUnit = tryLock.timeUnit();
          RLock lock = redisson.getLock(key);
          lock.lock(expire, timeUnit);
          try {
              return proceeding.proceed();
          } finally {
              lock.unlock();
          }
      }

    }

  • 使用

    // 注解并指定key
    @TryLock(key = "store2_lock")
    @GetMapping("/getStore2")
    public R getStore2() {
    int i;
    // 阻塞加锁
    String store = redisHelper.get("store2");
    if ("0".equals(store)) {
    String msg = "库存不足";
    log.info(msg);
    return R.error(msg);
    }
    i = Integer.parseInt(store) - 1;
    log.info("剩余库存数量:{}", i);
    redisHelper.set("store2", String.valueOf(i));
    return R.ok(i);
    }

基于Redis的分布式锁

标签:resource   简单   runtime   原子性   trace   中间   定义   etl   asp   

原文地址:https://www.cnblogs.com/zuier/p/11029424.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!