精华内容
下载资源
问答
  • RedLock分布式锁

    2019-04-03 15:13:22
    分布式锁一般有三者实现方式: 数据库的乐观锁 基于Redis的分布式锁 基于ZooKeeper的分布式锁 可靠性 锁的实现必须确保以下四个条件: 互斥性:在任意时刻,只有一个客户端能持有锁 不会发生死锁,即使...

    知乎的解释
    简书的解释
    Java技术驿站

    前言

    分布式锁一般有三者实现方式:

    • 数据库的乐观锁
    • 基于Redis的分布式锁
    • 基于ZooKeeper的分布式锁

    可靠性

    锁的实现必须确保以下四个条件:

    • 互斥性:在任意时刻,只有一个客户端能持有锁
    • 不会发生死锁,即使有一个客户端在持有锁的期间崩溃没有主动解锁,也能保证后续其他客户端能加锁
    • 加锁和解锁必须是同一个客户端

    Redis分布式锁

    Redis实现分布式锁的原理就是设置一个唯一的key,但客户端想要执行操作时,先去申请设置这个key,如果这个key不存在则进行set操作,并添加超时时间;此时另一个客户端也想要执行操作,申请设置key时发现key已经存在,则证明已经有客户端占用此key在操作,所以无法获得执行权;当第一个客户端执行完操作之后,删除key,代表第一个客户端执行完毕,释放锁,其他客户端可以获得执行权

    单点分布式锁代码实现

    加锁

    public class RedisTool {
     
        private static final String LOCK_SUCCESS = "OK";
        private static final String SET_IF_NOT_EXIST = "NX";
        private static final String SET_WITH_EXPIRE_TIME = "PX";
     
        /**
         * 尝试获取分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @param expireTime 超期时间
         * @return 是否获取成功
         */
        public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
     
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
     
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
     
        }
     
    }
    
    • 第一个为key,我们使用key来当锁,因为key是唯一的
    • 第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
    • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作
    • 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定
    • 第五个为time,与第四个参数相呼应,代表key的过期时间

    总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

    解锁

    public class RedisTool {
     
        private static final Long RELEASE_SUCCESS = 1L;
     
        /**
         * 释放分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @return 是否释放成功
         */
        public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
     
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
     
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
     
        }
     
    }
    

    可以看到,我们解锁只需要两行代码就搞定了!第一行代码,我们写了一个简单的Lua脚本代码,第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。

    那么这段Lua代码的功能是什么呢?其实很简单,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。那么为什么要使用Lua语言来实现呢?因为要确保上述操作是原子性的


    怎么在单节点上实现分布式锁

    SET resource_name my_random_value NX PX 30000

    主要依靠上述命令,该命令仅当Key不存在时(NX保证)set值,并且设置过期时间3000ms(PX保证),值my_random_value必须是所有client和所有锁请求发生唯一的,释放锁的逻辑为:

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

    述实现可以避免释放另一个client创建的锁,如果只有 del 命令的话,那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间,此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2,那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了,但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string,方法很多随意选择一种就行了
    在这里插入图片描述

    单点实现分布式锁的缺点

    这类锁的最大缺点就是加锁时只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:

    1. 在Redis的master节点上拿到了锁
    2. 但是这个加锁的key还没有同步到slave节点
    3. master故障,发生故障转移,slave节点升级为master节点
    4. 导致锁丢失

    什么是RedLock

    Redis 提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:

    • 安全特性:互斥访问,即永远只有一个 client 能拿到锁
    • 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
    • 容错性:只要大部分 Redis 节点存活就可以正常提供服务

    RedLock算法

    1. 获取当前Unix时间,以毫秒为单位
    2. 依次尝试从5个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁
    3. 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(N/2+1,这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功
    4. 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)
    5. 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)

    失败重试

    如果一个 client 申请锁失败了,那么它需要稍等一会在重试避免多个 client 同时申请锁的情况,最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作,便于其他 client 获得这把锁,避免这些锁过期造成的时间浪费,当然如果这时候网络分区使得 client 无法联系上这些 master,那么这种浪费就是不得不付出的代价了

    放锁

    放锁操作很简单,就是依次释放所有节点上的锁就行了

    性能、崩溃恢复和 fsync

    如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁! 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。

    简单理解就是当一个Client在5个master中的三个申请到了锁,但是其中一个master挂了,重启之后恢复到没有个Client锁的状态,这时又有一个Client过来申请锁,刚好又申请到了三个master,此时出现两个Client获得锁,违反互斥性

    Reddison对RedLock的实现

    …未完待续

    展开全文
  • Redlock 分布式锁

    2020-08-15 18:02:52
    一款基于redis实现的分布式锁的nodejs包 Github地址 基本实现方式 初始化 // redlock支持node redis, ioredis 或者 其他的redis包 var client1 = require('redis').createClient(6379, 'redis1.example.com'); var ...

    Redlock

    一款基于redis实现的分布式锁的nodejs包

    Github地址

    基本实现方式

    初始化

    // redlock支持node redis, ioredis 或者 其他的redis包
    var client1 = require('redis').createClient(6379, 'redis1.example.com');
    var client2 = require('redis').createClient(6379, 'redis2.example.com');
    var client3 = require('redis').createClient(6379, 'redis3.example.com');
    var Redlock = require('redlock');
    
    var redlock = new Redlock(
    	// 支持单机或集群
    	[client1, client2, client3],
    	{
    		// 预期的偏移时间
    		// see http://redis.io/topics/distlock
    		driftFactor: 0.01, // time in ms
    
    		// 尝试获取锁的次数,之后报错
    		retryCount:  10,
    
    		// 尝试获取锁的时间间隔,单位ms
    		retryDelay:  200,
    
    		retryJitter:  200 // time in ms
    	}
    );
    

    获取锁和释放锁

    // 锁id
    var resource = 'locks:account:322456';
    
    // 持有锁的最大时长,也可以使用extend函数延时
    var ttl = 1000;
    // 获取锁
    redlock.lock(resource, ttl).then(function(lock) {
    
    	// 业务代码
    
    	// 释放锁
    	return lock.unlock()
    	.catch(function(err) {
    		// 当连接不到redis时,持有的锁会过期,并且报错
    		console.error(err);
    	});
    });
    

    延时处理

    redlock.lock('locks:account:322456', 1000).then(function(lock) {
    
    	// 业务代码
    
    	// 如果还需要处理其他业务逻辑,可以使用extend函数延长锁的持有时间,并且可以一直循环延时
    	return lock.extend(1000).then(function(lock){
    
    		// 业务代码
    
    		// 释放锁
    		return lock.unlock()
    		.catch(function(err) {
    			// 当连接不到redis时,持有的锁会过期,并且报错
    			console.error(err);
    		});
    	});
    });
    
    展开全文
  • Redlock分布式锁

    2019-11-03 21:23:14
    Redis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性: 安全特性:互斥访问,即永远只有一个 client 能拿到锁 避免死锁:...

    这篇文章主要是对 Redis 官方网站刊登的 Distributed locks with Redis 部分内容的总结和翻译。

    什么是 RedLock

    Redis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:

    1. 安全特性:互斥访问,即永远只有一个 client 能拿到锁
    2. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
    3. 容错性:只要大部分 Redis 节点存活就可以正常提供服务

    怎么在单节点上实现分布式锁

    SET resource_name my_random_value NX PX 30000

    主要依靠上述命令,该命令仅当 Key 不存在时(NX保证)set 值,并且设置过期时间 3000ms (PX保证),值 my_random_value 必须是所有 client 和所有锁请求发生期间唯一的,释放锁的逻辑是:

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

    上述实现可以避免释放另一个client创建的锁,如果只有 del 命令的话,那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间,此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2,那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了,但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string,方法很多随意选择一种就行了。

    Redlock 算法

    算法很易懂,起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作:

    1. 得到当前的时间,微秒单位
    2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间
    3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。
    4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间
    5. 如果 client 申请锁失败了,那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作,重置状态

    失败重试

    如果一个 client 申请锁失败了,那么它需要稍等一会在重试避免多个 client 同时申请锁的情况,最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作,便于其他 client 获得这把锁,避免这些锁过期造成的时间浪费,当然如果这时候网络分区使得 client 无法联系上这些 master,那么这种浪费就是不得不付出的代价了。

    放锁

    放锁操作很简单,就是依次释放所有节点上的锁就行了

    性能、崩溃恢复和 fsync

    如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁! 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。

    展开全文
  • RedLock分布式锁原理

    2021-02-09 16:05:18
    Redlock分布式锁 传统的Redis分布式锁缺陷 使用传统的Redis分布式锁: SET key_name my_random_value NX PX 30000 NX 表示if not exist 就设置并返回True,否则不设置并返回False PX 表示过期时间用毫秒级, 30000 ...

    Redlock分布式锁

    传统的Redis分布式锁缺陷

    使用传统的Redis分布式锁:
    SET key_name my_random_value NX PX 30000
    NX 表示if not exist 就设置并返回True,否则不设置并返回False
    PX 表示过期时间用毫秒级, 30000 表示这些毫秒时间后此key过期
    传统的Redis分布式锁有缺点:
    只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况.
    比如说在Redis的master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点,master故障,发生故障转移,slave节点升级为master节点.但是这个新的Master并没有锁信息,此时就导致锁丢失.
    就会出现严重的问题.因为锁丢失了,此时新的线程来尝试获取锁,就能获取到了.这样的话程序就有了严重的bug.
    由此 redis官方推荐 redlock 来解决这个问题

    RedLock原理简述

    假设有5个redis节点,这些节点之间既没有主从,也没有集群关系。客户端用相同的key和随机值在5个节点上请求锁,请求锁的超时时间应小于锁自动释放时间。当在3个(超过半数)redis上请求到锁的时候,才算是真正获取到了锁。如果没有获取到锁,则把部分已锁的redis释放掉。

    RedLock详细解答

    什么是 RedLock

    Redis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:

    1. 安全特性:互斥访问,即永远只有一个 client 能拿到锁
    2. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
    3. 容错性:只要大部分 Redis 节点存活就可以正常提供服务

    怎么在单节点上实现分布式锁

    SET resource_name my_random_value NX PX 30000

    主要依靠上述命令,该命令仅当 Key 不存在时(NX保证)set 值,并且设置过期时间 3000ms (PX保证),值 my_random_value 必须是所有 client 和所有锁请求发生期间唯一的,释放锁的逻辑是:

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

    上述实现可以避免释放另一个client创建的锁,如果只有 del 命令的话,那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间,此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2,那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了,但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string,方法很多随意选择一种就行了。

    Redlock 算法

    算法很易懂,起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作:

    1. 得到当前的时间,微妙单位
    2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间
    3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。
    4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间
    5. 如果 client 申请锁失败了,那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作,重置状态

    失败重试

    如果一个 client 申请锁失败了,那么它需要稍等一会在重试避免多个 client 同时申请锁的情况,最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作,便于其他 client 获得这把锁,避免这些锁过期造成的时间浪费,当然如果这时候网络分区使得 client 无法联系上这些 master,那么这种浪费就是不得不付出的代价了。

    放锁

    放锁操作很简单,就是依次释放所有节点上的锁就行了

    性能、崩溃恢复和 fsync

    如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁! 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。

    展开全文
  • 分布式锁简介1)为何需要分布式锁2)Java中实现的常见方式3)Redis 分布式锁的问题①、锁超时②、单点/多点问题二、Redis 分布式锁的实现1)代码实现二、Redlock分布式锁1.什么是 RedLock2.怎么在单节点上实现分布式锁3....
  • 什么是 RedLockRedis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:安全特性:互斥访问,即永远只有一个 client 能拿到锁...
  • 分布式锁深入探究 一、分布式锁简介 锁 是一种用来解决多个执行线程 访问共享资源 错误或数据不一致问题的工具。 为何需要分布式锁 一般情况下,我们使用分布式锁主要有两个场景: 1.避免不同节点重复相同的工作:...
  • 还有一种方式可以实现分布式锁:zookeeper,也有主从架构。 CAP理论:C表示一致性、A表示可用性、P表示分区容错性。 Redis集群满足:AP zookeeper集群满足:CP Redis中只要在主节点中加锁成功,马上返回给客户端...
  • Redlock分布式锁(43)

    2019-07-30 15:49:08
    Redis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性: 安全特性:互斥访问,即永远只有一个 client 能拿到锁 避免死锁:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 730
精华内容 292
关键字:

redlock分布式锁