redis非集群版分布式锁

@Component
public class RedisLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static Logger logger = LogManager.getLogger(RedisLock.class);
    private static final String LUA_SCRIPT = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                                             "    return redis.call(\"del\",KEYS[1])\n" +
                                             "else\n" +
                                             "    return 0\n" +
                                             "end";

    /**
     * @param supplier 需要执行的代码
     * @param lockName 锁名称,redis key 名称
     * @param maxLockTime 最大锁时间,maxLockTime后自动解锁,防止死锁,单位是s,传null为10s
     * @param maxRetryNum 最大重试次数,到达最大重试次数后,抛出异常,可为空,为null就为5次
     * @date 2021年04月07日 20:29
     *
     * 尝试加锁,加锁成功后执行方法,若不成功,等待500ms后递归尝试,直到maxRetryNum抛出异常
     */
    public <R> R lockAndExecute(Supplier<R> supplier, String lockName, Integer maxLockTime, Integer maxRetryNum) throws BaokuRedisException {
        if (Objects.isNull(maxRetryNum)) {
            maxRetryNum = ObjectUtils.defaultIfNull(maxRetryNum , 5);
        }
        // lock的value , 脚本删除时校验 , 防止执行过程中锁过期误删
        String randomValue = IdUtil.fastSimpleUUID();
        // set nx ex 尝试获取锁 , 并设置过期时间 , 防止死锁
        Boolean tryLockResult = redisTemplate.opsForValue().setIfAbsent(lockName, randomValue, ObjectUtils.defaultIfNull(maxLockTime , 10), TimeUnit.SECONDS);
        if (Objects.nonNull(tryLockResult) && Boolean.TRUE.equals(tryLockResult)) {
            R result = null;
            try {
                // 执行逻辑代码
                result = supplier.get();
            } catch (Exception e) {
                logger.error("RedisLock fail", e);
            } finally {
                // lua脚本执行 get and del , 保证原子性
                redisTemplate.execute(new DefaultRedisScript<>(LUA_SCRIPT, Long.class), Collections.singletonList(lockName), randomValue);
            }
            return result;
        } else {
            // 如果尝试次数大于 0 , 递归执行 , 否则抛出异常
            if (maxRetryNum-- > 0) {
                // 500ms 后重试
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    logger.error("RedisLock fail", e);
                }
                return lockAndExecute(supplier, lockName, maxLockTime, maxRetryNum);
            } else {
                throw new BaokuRedisException("the maximum number of tentatives has been reached");
            }
        }
    }

    /**
     * @param lockName 锁名称,redis key 名称
     * @param maxLockTime 最大锁时间,maxLockTime后自动解锁,防止死锁,单位是s,传null为10s
     * @return java.lang.String
     *
     * 尝试加锁 , 成功返回uuid , 解锁带入此uuid
     */
    public TryLockResult lock(String lockName, Integer maxLockTime){
        TryLockResult result = new TryLockResult();
        String randomValue = UUID.fastUUID().toString();
        Boolean tryLockResult = redisTemplate.opsForValue().setIfAbsent(lockName, randomValue, ObjectUtils.defaultIfNull(maxLockTime , 10), TimeUnit.SECONDS);
        if (Objects.nonNull(tryLockResult) && Boolean.TRUE.equals(tryLockResult)) {
            result.setRandomValue(randomValue);
        }
        return result;
    }


    public static class TryLockResult{
        private String randomValue;

        public String getRandomValue() {
            return randomValue;
        }

        public void setRandomValue(String randomValue) {
            this.randomValue = randomValue;
        }

        public boolean isSucc(){
            return StringUtils.isNotBlank(randomValue);
        }
    }


    /**
     * @param lockName 锁名称,redis key 名称
     * @param randomValue lock方法返回值,用于防止误删,eg:线程A 加锁key1锁定10s,但是后续代码执行15s,线程B在第11s获取锁key1,后续代码执行10s,
     *                    线程A在第15s执行完成并删除锁key1,如果没有randomvalue作为各个线程的唯一值校验,那么就会删除线程B所持有的锁,导致锁被误删失效
     *
     * 解锁
     */
    public void unlock(String lockName,String randomValue){
        redisTemplate.execute(new DefaultRedisScript<>(LUA_SCRIPT, Long.class), Collections.singletonList(lockName), randomValue);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值