redis分布式锁核心实现-创新互联
- 分布式锁的由来
- 特征
- 核心实现
- 单实例
- Redlock实现
- redlock由来
- 实现步骤
- 源码剖析
在我们的日常开发中,一个进程中当多线程的去竞争某一资源的时候,我们通常会用一把锁来保证只有一个线程获取到资源。如加上synchronize关键字或ReentrantLock锁等操作。
那么,如果是多个进程相互竞争一个资源,如何保证资源只会被一个操作者持有呢?
server1、server2、server3 这三个服务都要修改amount这个数据,每个服务更新的值不同,为了保证数据的正确性,三个服务都向lock server服务申请修改权限,最终server2拿到了修改权限,即server2将amount更新为2,其他服务由于没有获取到修改权限则返回更新失败。
特征核心实现 单实例我们这里直接使用Lua脚本来保证原子性,其中
NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。
EX seconds :设定key的过期时间,时间单位是秒。
PX milliseconds: 设定key的过期时间,单位为毫秒
加锁
使用 Setnx 命令保证互斥性。
需要设置锁的过期时间,避免死锁。
Setnx 和设置过期时间需要保持原子性,避免在设置 Setnx 成功之后在设置过期时间客户端崩溃导致死锁。
加锁的 Value 值为一个唯一标示。由于分布式任务随机数都不可靠,可以(MAC地址+JVM进程ID) 作为唯一标示。加锁成功后需要把唯一标示返回给 Client 来用进行解锁操作。
脚本如下:
redis.call('set',key,value,'NX','PX',30000)
解锁
需要拿加锁成功的唯一标示要进行解锁,从而保证加锁和解锁的是同一个客户端。
解锁操作需要比较唯一标识是否相等,相等再执行删除操作。这 2 个操作可以采用 Lua 脚本方式使 2 个命令的原子性
脚本如下:
redis.call('get',key == value then return tostring(redis.call('del', key)==1) else return 'false' end
Redlock实现
redlock由来上面琐大的缺点就是它加锁时只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:
例如,在Redis的master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点,此时master故障,发生故障转移,slave节点升级为master节点,导致锁丢失。
正因为如此,Redis作者antirez基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock。
其核心思想为:
实现步骤搞多个Redis master部署,以保证它们不会同时宕掉。并且这些master节点是完全相互独立的,相互之间不存在数据同步。同时,需要确保在这多个master实例上,是与在Redis单实例,使用相同方法来获取和释放锁。
- 获取当前时间,以毫秒为单位。
- 按顺序向5个master节点请求加锁。客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。(假设锁自动失效时间为10秒,则超时时间一般在5-50毫秒之间,我们就假设超时时间是50ms吧)。如果超时,跳过该master节点,尽快去尝试下一个master节点。
- 客户端使用当前时间减去开始获取锁时间(即步骤1记录的时间),得到获取锁使用的时间。当且仅当超过一半(N/2+1,这里是5/2+1=3个节点)的Redis master节点都获得锁,并且使用的时间小于锁失效时间时,锁才算获取成功。(如上图,10s>30ms+40ms+50ms+4m0s+50ms)
- 如果取到了锁,key的真正有效时间就变啦,需要减去获取锁所使用的时间。
- 如果获取锁失败(没有在至少N/2+1个master实例取到锁,有或者获取锁时间已经超过了有效时间),客户端要在所有的master节点上解锁(即便有些master节点根本就没有加锁成功,也需要解锁,以防止有些漏网之鱼)。
参考链接: 一文搞懂 Redis 分布式锁
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
名称栏目:redis分布式锁核心实现-创新互联
文章分享:http://scyanting.com/article/gegoi.html