Java实现分布式锁-基于redis(2)

441 阅读2分钟

前言

  • 获取锁的redis命令

  • SET resource_name my_random_value NX PX 30000

    • (1) resource_name: 资源名称,可以根据不同的业务区分不同的锁
    • (2) my_random_value:随机值,每个线程的随机值都不同,用于释放锁对应的锁
    • (3) NX: key不存在时设置成功,key存在则设置不成功
    • (4) PX: 自动失效的时间,出现异常情况,锁可以自己释放。
  • 为什么使用SETNX来实现:

    • 因为在GET和SET操作之间有延迟,我们没法知道从“发送命令”到“redis服务器返回结果”之间的这段时间内是否有其他线程也去建立锁。当然,这些都在几毫秒之内,发生的可能性相当低。但是如果在一个繁忙的环境中运行着大量的并发线程和命令,重叠的可能性并不是微不足道的。为了解决这个问题,应该用SETNX命令。SETNX消除了GET命令需要等待返回值的问题,SETNX只有在key不存在时才返回成功。这意味着只有一个线程可以成功运行SETNX命令,而其他线程会失败,然后不断重试,直到它们能建立锁。

代码部分

  • RedisLockController.java
@RestController
@Slf4j
public class RedisLockController {
    @Autowired
    private RedisTemplate redisTemplate;
    @RequestMapping("redisLock")
    public String redisLock(){
        log.info("我进入了方法!");
        try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){
            if (redisLock.getLock()) {
                log.info("我进入了锁!!");
                Thread.sleep(15000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法执行完成");
        return "方法执行完成";
    }
}
  • RedisLock.java
@Slf4j
public class RedisLock implements AutoCloseable {

    private RedisTemplate redisTemplate;
    private String key;
    private String value;
    //单位:秒
    private int expireTime;

    public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.expireTime=expireTime;
        this.value = UUID.randomUUID().toString();
    }

    /**
     * 获取分布式锁
     * @return
     */
    public boolean getLock(){
        RedisCallback<Boolean> redisCallback = connection -> {
            //设置NX
            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
            //设置过期时间
            Expiration expiration = Expiration.seconds(expireTime);
            //序列化key
            byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
            //序列化value
            byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
            //执行setnx操作
            Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
            return result;
        };

        //获取分布式锁
        Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
        return lock;
    }

    public boolean unLock() {
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);
        List<String> keys = Arrays.asList(key);

        Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);
        log.info("释放锁的结果:"+result);
        return result;
    }


    @Override
    public void close() throws Exception {
        unLock();
    }
}

  • 小插曲: 为什么实现AutoCloseable接口
    • 1.了解AutoCloseable:
      • AutoCloseable接口位于java.lang包下,从JDK1.7开始引入。

    • 2.对于实现AutoCloseable接口的类的实例,将其放到try后面(我们称之为:带资源的try语句),在try结束的时候,会自动将这些资源关闭(调用close方法)。