redis分布式锁今生

 对于前世redis分布式锁各种解决方案,无论是成熟单机方案 1.SETNX+LUA 2.SET unique_value nx px milliseconds ,都局限性很大
  所以Martin发布了一种算法redlock来进行集群(完全互相独立,不存在主从复制或者其他集群协调机制)操作分布式锁
  算法如下:
     1.获取当前Unix时间,以毫秒为单位。
     2.依次尝试从5个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。并且设置一个超时时间(一般是5-50毫秒,远小于失效时间)
     3.客户端当前时间减去开始获取锁时间(第一个redis实例开始)作为获取锁消耗总时间。当且仅当redis集群中有一多半锁获取到(n/2+1),并且获取锁总时间小于锁设置的失效时间,才任务该线程获取到分布式锁。
     4.获取到锁以后,锁的有效时间更改为,最起初设置的锁失效时间-获取锁总消耗时间
     5.如果获取锁失败,应该在redis集群中进行解锁
     
  Reddisson框架有效的实现了对redlock的封装。
  
  1)项目中引入
     <!--分布式锁-->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.11.1</version>
    </dependency>  
  2)项目中使用
    /**
     * redisson配置
     */
    @Configuration
    public class RedissonConfig {

        @Value("${spring.redis.host}")
        private String host;

        @Value("${spring.redis.port}")
        private String port;

        @Bean
        public RedissonClient getRedisson(){

            Config config = new Config();
            config.useSingleServer().setAddress("redis://" + host + ":" + port);
            return Redisson.create(config);
        }

    }
    
   3)代码中引用
       public VersionT getOneVersion() {

        VersionT versionT = (VersionT) redisCacheTemplate.opsForValue().get("bzversion");
        RLock rLock = redissonClient.getLock("redissonLock:" + Thread.currentThread().getName()); //分布式锁,避免大量请求一瞬间请求到数据库,造成缓存击穿
        try {
            rLock.tryLock(500,10000, TimeUnit.SECONDS); // 锁失效时间设置10秒,锁响应时间设置50毫秒
            if (versionT == null) {
                VersionT version = versionDao.getOneVersion();
                redisCacheTemplate.opsForValue().set("bzversion", version);
                return version;
            }
        } catch (Exception e) {
            System.out.println("缓存版本号失败" + e.getMessage());
        } finally {
            rLock.unlock();
        }
        return versionT;
    }

   4)源码中分析(redission获取锁源码解析)
        public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime); // 单实例获取锁响应时间
        long current = System.currentTimeMillis(); 
        long threadId = Thread.currentThread().getId();
        Long ttl = this.tryAcquire(leaseTime, unit, threadId); // 获取分布式锁
        if (ttl == null) {
            return true;
        } else {
            time -= System.currentTimeMillis() - current; // 超过定义响应时间,返回获取锁失败
            if (time <= 0L) {
                this.acquireFailed(threadId);
                return false;
            } else {
                current = System.currentTimeMillis();
                RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
                if (!this.await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
                    if (!subscribeFuture.cancel(false)) {
                        subscribeFuture.onComplete((res, e) -> {
                            if (e == null) {
                                this.unsubscribe(subscribeFuture, threadId);
                            }

                        });
                    }

                    this.acquireFailed(threadId);
                    return false;
                } else {
                    try {
                        time -= System.currentTimeMillis() - current;
                        if (time <= 0L) {
                            this.acquireFailed(threadId);
                            boolean var20 = false;
                            return var20;
                        } else {
                            boolean var16;
                            do {
                                long currentTime = System.currentTimeMillis();
                                ttl = this.tryAcquire(leaseTime, unit, threadId);
                                if (ttl == null) {
                                    var16 = true;
                                    return var16;
                                }

                                time -= System.currentTimeMillis() - currentTime;
                                if (time <= 0L) {
                                    this.acquireFailed(threadId);
                                    var16 = false;
                                    return var16;
                                }

                                currentTime = System.currentTimeMillis();
                                if (ttl.longValue() >= 0L && ttl.longValue() < time) {
                                    this.getEntry(threadId).getLatch().tryAcquire(ttl.longValue(), TimeUnit.MILLISECONDS);
                                } else {
                                    this.getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                                }

                                time -= System.currentTimeMillis() - currentTime;
                            } while(time > 0L);

                            this.acquireFailed(threadId);
                            var16 = false;
                            return var16;
                        }
                    } finally {
                        this.unsubscribe(subscribeFuture, threadId);
                    }
                }
            }
        }
    }
    5) <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        this.internalLockLeaseTime = unit.toMillis(leaseTime);
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
    }
    跟踪到这里,就会发现,通过LUA脚本实现了锁判断,锁重入等操作。
      if (redis.call('exists', KEYS[1]) == 0) 
        then redis.call('hset', KEYS[1], ARGV[2], 1); // 获取锁
        redis.call('pexpire', KEYS[1], ARGV[1]); // 设置key失效时间
        return nil; 
      end;  
      if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); // redis重入锁
        redis.call('pexpire', KEYS[1], ARGV[1]);  // 设置key失效时间
        return nil; 
      end; 
      return redis.call('pttl', KEYS[1]); // 以毫秒为单位返回 key 的剩余过期时间
      
     redlock已经属于现在较为稳定的reids分布式锁,但是redlock的作者Martin以及antirez就这个算法不足进行了激烈的讨论,以及引发分布式阵营的对垒。
     他们主要纠结的问题点在于:
     1.时钟发生跳跃
     2.长时间的GC pause或者长时间的网络延迟
     其实对于时钟跳跃情况1.服务器更新时间插件 2.运维同学手动更改服务器时间  这两种情况虽然很极端,但是确实会造成redlock的失效。
     对于第二种情况,无论是长时间的GC pause还是长时间的网络延迟,其实在redlock算法第四步做了校验,那就是最起初设置的失效时间如果小于集群环境下获取redis锁消耗的总时间,则会进行获取锁失败操作。
     

参考文献 https://mp.weixin.qq.com/s?__biz=MzA4NTg1MjM0Mg==&mid=2657261514&idx=1&sn=47b1a63f065347943341910dddbb785d&chksm=84479e13b3301705ea29c86f457ad74010eba8a8a5c12a7f54bcf264a4a8c9d6adecbe32ad0b&scene=21#wechat_redirect
https://yq.aliyun.com/articles/674394
https://www.cnblogs.com/demingblog/p/9542124.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值