在Ruby和Redis中匹配实时播放器的最佳策略?

时间:2017-06-02 22:26:12

标签: ruby-on-rails ruby redis actioncable

我正在使用这种相当简单的方法来匹配两个现场球员:

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文件是否可以用更好的方式来防止这种情况发生。

2 个答案:

答案 0 :(得分:1)

您的问题是SPOPSADD之间存在竞争条件。您应该在事务中运行这两个命令。使用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