我正在使用这种相当简单的方法来匹配两个现场球员:
class Seek
def self.create(uuid)
if opponent = REDIS.spop("seeks")
Game.start(uuid, opponent)
else
REDIS.sadd("seeks", uuid)
end
end
def self.remove(uuid)
REDIS.srem("seeks", uuid)
end
end
然后当我的游戏开始时,我只需Seek.create(uuid)
。
我得到的利基问题很少,有时两个人寻求同时。我猜两个玩家Redis.spop("seeks")
都会返回nil
,转换然后将它们都添加到REDIS.sadd("seeks", uuid)
。然后他们都无限期地等待(除非当然有其他玩家出现)。
我的情况似乎是一种相当罕见的情况,但我很好奇我的seek.rb
文件是否可以用更好的方式来防止这种情况发生。
答案 0 :(得分:1)
您的问题是SPOP
和SADD
之间存在竞争条件。您应该在事务中运行这两个命令。使用Redis,您可以使用Lua scripting实现此目的,这可确保整个脚本在服务器端以原子方式运行。
-- transaction.lua
redis.replicate_commands() -- see https://redis.io/commands/eval#replicating-commands-instead-of-scripts for details
local uuid = ARGV[1] -- pass uuid by script's arguments
local member = redis.call('SPOP', 'seeks')
if (member) then
return member -- get an exist uuid
else
redis.call('SADD', 'seeks', uuid) -- add it to the set
end -- the whole script runs in a transaction
答案 1 :(得分:1)
我希望您监控日志。除了使用事务之外,您还可以使用自旋锁来处理redis中的竞争条件。您可以参考此文章以获取更多详细信息:http://hoyvinglavin.com/2012/04/29/redis-as-a-mutex-service/。但通常情况下,您可以使用模型化代码来解决手头的问题。
class Seek
def self.create(uuid)
if opponent = REDIS.spop("seeks")
Game.start(uuid, opponent)
else
#Check if key(lock) exists in redis. If not then proceed ahead
#Set key(acquire lock). Raise exception in case redis set key does not return true
REDIS.sadd("seeks", uuid) #Perform your operation
#Delete key(release lock)
end
end
def self.remove(uuid)
REDIS.srem("seeks", uuid)
end
end