@elibinary
2017-11-12T06:17:04.000000Z
字数 2536
阅读 860
DB
基于 Redis 的 lock 正是基于其单进程单线程及其原子操作来实现的。对于 Redis 来说,同一时刻只可能有一个命令正在操作,也就是说在 Redis 的层面上,请求是串行进行的。
SETNX 是 Redis 的一个命令,完整形式是这样的:
SETNX key value
它是 ‘set if not exists’ 的简写,正如其描述一样 SETNX 的作用是给 key 赋值,并且仅当目标 key 不存在时才能成功并返回 1
locking 实现主要就是基于 Redis 的这个命令
其过程是这样的:
看起来很完美,但其实这中间存在着很多细节上的问题,大致分析一下:
下面来看下 redis-objects 这个 gem 是如何实现这个 locking 的
看源码实现很简单,一共也就几十行代码
# Get the lock and execute the code block. Any other code that needs the lock
# (on any server) will spin waiting for the lock up to the :timeout
# that was specified when the lock was defined.
def lock(&block)
expiration = nil
try_until_timeout do
expiration = generate_expiration
# Use the expiration as the value of the lock.
break if redis.setnx(key, expiration)
# Lock is being held. Now check to see if it's expired (if we're using
# lock expiration).
# See "Handling Deadlocks" section on http://redis.io/commands/setnx
if !@options[:expiration].nil?
old_expiration = redis.get(key).to_f
if old_expiration < Time.now.to_f
# If it's expired, use GETSET to update it.
expiration = generate_expiration
old_expiration = redis.getset(key, expiration).to_f
# Since GETSET returns the old value of the lock, if the old expiration
# is still in the past, we know no one else has expired the locked
# and we now have it.
break if old_expiration < Time.now.to_f
end
end
end
begin
yield
ensure
# We need to be careful when cleaning up the lock key. If we took a really long
# time for some reason, and the lock expired, someone else may have it, and
# it's not safe for us to remove it. Check how much time has passed since we
# wrote the lock key and only delete it if it hasn't expired (or we're not using
# lock expiration)
if @options[:expiration].nil? || expiration > Time.now.to_f
redis.del(key)
end
end
end
先来看它获取锁的过程,首先会在超时时间内不断地循环尝试获取锁
def try_until_timeout
if @options[:timeout] == 0
yield
else
start = Time.now
while Time.now - start < @options[:timeout]
yield
sleep 0.1
end
end
raise LockTimeout, "Timeout on lock #{key} exceeded #{@options[:timeout]} sec"
end
可以看到,在超时时间内每隔 0.1 秒尝试一次
再回到 lock 方法,尝试获取锁的过程如下: