使用Redis可以很方便地实现分布式锁。
实现分布式锁不难,难的是要考虑性能及优化加锁解锁机制。
提示:以下是本篇文章正文内容,下面案例可供参考
Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
基于setnx命令的特性,我们就可以实现一个最简单的分布式锁了。
key = 竞争资源
value =threadId+count
,线程ID+重入次数
客户端需要自行维护 [线程ID+重入次数]
redis> setnx lock1 threadId_count
(integer) 1
客户端自行实现自旋
redis> setnx lock1 threadId_count
(integer) 0
# 判断是否自己持有的锁
redis> get lock1
"threadId_count"
# 重入次数减1
redis> set lock1 threadId_count
OK
# 直接释放锁
redis> del lock1
(integer) 1
将客户端维护锁超时的工作,交给Redis来做。
Redis Setex 命令为指定的 key 设置值及其过期时间。
如果需要同时拥有Setex跟Setnx的特性,可以使用 set命令 + options
key = 竞争资源
value =threadId+count
,线程ID+重入次数
expire = 锁存活时间
客户端需要自行维护 [线程ID+重入次数]
redis> set lock1 threadId_count ex expire nx
(integer) 1
redis> set lock1 threadId_count ex expire nx
(integer) 0
# 判断是否自己持有的锁
redis> get lock1
"threadId_count"
# 重入次数减1
redis> setex lock1 threadId_count expire
OK
# 直接释放锁
redis> del lock1
(integer) 1
前面的Redis原生命令实现的方式,需要多次的网络请求,在锁竞争激烈的情况下,对应用和Redis都有不少的压力,对于客户端也需要自行维护原子性、一致性等并发安全问题。
这里可以使用Lua脚本减少对Redis网络请求,并保证一系列操作的原子性。
涉及的Redis命令:
local key = KEYS[1] -- 锁资源
local waitSet = key .. '_waitSet' -- 该锁的等待线程队列的Key
local timeout = ARGV[1] -- 持有锁的时间
local threadId = ARGV[2] -- 线程唯一标识
-- 判断资源是否在锁
if (redis.call('exists', key) == 0) then
-- 锁重入次数=1
redis.call('hincrby', key, threadId, 1)
redis.call('pexpire', key, timeout)
return 'ok'
-- 是否是自己的锁
elseif (redis.call('hexists', key, threadId) == 1) then
-- 锁重入次数+1
redis.call('hincrby', key, threadId, 1)
redis.call('pexpire', key, timeout)
return 'ok'
else
-- 未获取到锁,返回该锁剩余持有时间
return redis.call('pttl', key)
end
获取锁失败后,需要订阅waitSet
Channel,收到通知后再次尝试获取锁。
涉及Redis命令:
local key = KEYS[1] -- 锁资源
local waitSet = key .. '_waitSet' -- 该锁的等待线程队列的Key
local timeout = ARGV[1] -- 持有锁的时间
local threadId = ARGV[2] -- 线程唯一标识
-- 锁不存在or不是自己的锁
if (redis.call('hexists', key, threadId) == 0) then
return nil
end
-- 释放锁,重入次数减1
local counter = redis.call('hincrby', key, threadId, -1)
-- 释放锁后,本线程的其它业务代码仍持有锁
if (counter > 0) then
redis.call('pexpire', key, timeout)
return 0
-- 释放锁后,可以通知其它等待线程唤醒竞争锁
else
redis.call('del', key);
redis.call('publish', waitSet, 'UNLOCK')
return 1
end
能正常锁重入次数递减,能正常释放锁
释放锁后,等待队列能正常监听
以上就是今天要讲的内容,本文仅仅简单介绍了Redis分布式锁的使用,Lua脚本提供了原子性操作。而Redisson提供了大量能使我们快速便捷使用的分布式锁实现。