【问题标题】:How to lock Resque jobs to one server如何将 Resque 作业锁定到一台服务器
【发布时间】:2012-10-15 02:28:28
【问题描述】:

我的基础架构中有一个 Resque 服务器“集群”。它们都具有完全相同的作业优先级等。我会根据有多少待处理的作业以及服务器上用于处理所述作业的可用资源来自动上下缩放 Resque 服务器的数量。我总是至少有两个 Resque 服务器。

我的问题是,当我完成一项快速的、一次性的工作时,有时两个服务器都会处理该工作。这很糟糕。

我尝试使用以下内容为我的工作添加锁:

require 'resque-lock-timeout'

class ExampleJob
  extend Resque::Plugins::LockTimeout

  def self.perform
   # some code
  end
end

此插件适用于运行时间较长的作业。然而,对于这些超小的一次性工作,处理会立即发生。 Resque 服务器都看不到其姊妹服务器设置的锁,都设置了锁,处理作业,解锁,然后完成。

除了让一台专用服务器处理此类工作外,我不完全确定此时该做什么或有什么解决方案。配置和扩展这将是一个严重的痛苦。我真的希望两台服务器都能处理它,但是一旦其中一台从队列中抓取它,请确保另一台不会运行它。

谁能提出一些可行的解决方案?

【问题讨论】:

  • aahh 你的意思是你有两个 resque 的工作,而且他们都碰巧开始从事同一个工作,这很奇怪,因为我知道 resque 在内部使用 pop 在旧的 resqueBRPOPLPUSH在新的 resque 中,如果没有错,我猜上述任何一个命令的并发访问都将保证结果为 one 和 nil,如果列表为空,因此您提到的情况不太可能。如果我错了,请纠正我
  • 我不知道你在哪里。我了解 Resque 为处理工作所做的工作。但是,这些作业最终仍会同时得到处理。这就是为什么要建造诸如锁定宝石之类的东西的原因。不过,我没有运气。
  • 我不知道该告诉你什么我编造的观点是 redis is single threadLOPOPBLPOP resque 用来为队列消费消息的两个命令都是原子的这保证只有一个成功,你所说的永远不会发生我不确定locking gem 应该做什么,但你认为不成立,不要误会我的意思。可能是你的代码预览可以帮助纠正如果我错了
  • 更好地解释上下文是什么!您是多次排队同一工作还是您的意思是许多工人在做同一工作(仅排队一次)?
  • @randombits 你用的是什么版本的 Redis 和 resque?

标签: ruby-on-rails ruby redis resque


【解决方案1】:

两个工作人员应该不可能获得相同的“有效负载”,因为项目是使用BLPOP 出列的。 Redis 只会将排队的项目发送给第一个调用BLPOP 的客户端。听起来您不止一次将作业排入队列,因此两个工作人员能够使用相同的参数获取不同的有效负载。 'resque-lock-timeout' 的目的是确保具有相同方法和参数的有效负载不会同时运行;但是,如果第一个作业在第二个作业尝试获取锁之前释放锁,它不会阻止第二个有效负载的工作。

这仅发生在短期运行的作业上是有道理的。以下是可能发生的情况:

payload 1 is enqueued
payload 2 is enqueued
payload 1 is locked 
payload 1 is worked
payload 1 is unlocked
payload 2 is locked
payload 2 is worked
payload 2 is unlocked

在长时间运行的作业中可能会发生以下情况:

payload 1 is enqueued
payload 2 is enqueued
payload 1 is locked
payload 1 is worked 
payload 2 is fails to get lock
payload 1 is unlocked

尝试关闭 Resque 并将您的工作排入队列。在 Redis 列表中查看您的 Resque 队列(或使用 redis-cli monitor 监控 Redis)。查看 Resque 是否已将多个有效负载排队。如果您仍然只看到一个有效负载,请监视该列表以查看您的另一位 resque 工作人员是否在失败的作业上调用 recreate

如果您想让 'resque-lock-timeout' 持有锁的时间超过处理作业所需的持续时间,您可以覆盖 release_lock! 方法来设置锁的到期时间,而不仅仅是删除它。

module Resque
  module Plugins
    module LockTimeout  
      def release_lock!(*args)
        lock_redis.expire(redis_lock_key(*args), 60) # expire lock after 60 seconds
      end
    end
  end
end

https://github.com/lantins/resque-lock-timeout/blob/master/lib/resque/plugins/lock_timeout.rb#l153-155

【讨论】:

    【解决方案2】:

    编写您的锁解释器,等待T 毫秒,然后再查找唯一ID小于它所生成的锁值的锁

    这将决定谁赢得比赛,而失败者将自我终止。

    T 是给定队列池中所有N 服务器之间的并行延迟。您可以通过从 1000 毫秒缩小直到您再次发现重复发生的工作来确定此heuristically。为延迟变化提供填充。

    这称为互斥线程安全的 Busy-Wait 解决方案。考虑到必须解决互斥锁的各种场景(例如锁定等),它被认为是可接受的权衡之一

    我会在关闭移动设备时发布一些链接。互斥量的维基百科条目应该解释这一切。

    这对你不起作用,那么: 1. 使用调度程序来控制复制。 2. 将短期运行的作业分类到一个设计用来串行运行它们的队列中。

    TL;DR 没有完美的解决方案,只有根据您的条件进行良好的权衡。

    【讨论】:

    • 队列必须是线程安全的,所以不要抱着希望解决这个问题。做一个程序员而不是一个祈祷者;o)
    • @SirRufo 我很清楚。这是解决互斥锁的问题。我现在添加了更多细节,稍后将更彻底地考虑这种情况下的不确定性。不幸的是,我的解决方案可能是最好的答案
    • Resque 使用 Redis 的原子列表操作,这完全避免了锁定以避免重复作业处理的需要,因为您可以保证只有一个客户端能够从列表中弹出一个项目。这个答案与技术并不真正相关。
    • @ChrisHerald 当我听到这个问题时,我推测 OP 创建了一个更复杂的场景,它绕过了 Redis 类似互斥的能力。
    • @NewAlexandria 即使使用互斥锁,也只有一个可以保证赢得 ASAIK
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-02
    • 2013-03-30
    • 2012-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多