Rails运行多个delayed_job - 锁定表

10

嘿,我使用delayed_job进行后台处理。我的服务器有8个CPU,使用MySQL,并启动了7个delayed_job进程。

RAILS_ENV=production script/delayed_job -n 7 start 

Q1: 我想知道是否可能有两个或更多的delayed_job进程开始处理相同的进程(数据库中的相同记录行)。我查看了delayed_job插件的代码,但无法找到锁定指令(没有锁表或SELECT...FOR UPDATE)。

我认为每个进程在执行对locked_by列的UPDATE之前都应该锁定数据库表。它们通过更新locked_by字段(UPDATE delayed_jobs SET locked_by...)来简单地锁定记录。这样就足够了吗?不需要锁定吗?为什么?我知道UPDATE的优先级高于SELECT,但我认为在这种情况下没有这种影响。

我对多线程情况的理解是:

Process1: Get waiting job X. [OK]
Process2: Get waiting jobs X. [OK]
Process1: Update locked_by field. [OK]
Process2: Update locked_by field. [OK]
Process1: Get waiting job X. [Already processed]
Process2: Get waiting jobs X. [Already processed]

我认为在某些情况下,更多的作业可以获得相同的信息并开始处理相同的过程。

Q2: 对于一个有8个CPU的服务器而言,7个延迟作业是一个好的数量吗?为什么是/不是?

谢谢 10倍!

1个回答

11

我认为你问题的答案在 'lib/delayed_job/job.rb' 的第168行:

self.class.update_all(["locked_at = ?, locked_by = ?", now, worker], ["id = ? and (locked_at is null or locked_at < ?)", id, (now - max_run_time.to_i)])

如果没有其他工作程序已经锁定了该任务,那么仅在更新表时执行该行的更新。如果表被更新,则会检查是否有其他工作程序已经锁定该任务。不需要表锁或类似的机制(这样做将大大降低应用程序的性能),因为您的DBMS确保单个查询的执行与其他查询的影响是隔离的。在您的示例中,Process2不能获取作业X的锁,因为它只在未被锁定之前才更新作业表。

关于您的第二个问题:取决于情况。在一个专门为此工作而设计的8 CPU服务器上,8个工作程序是一个很好的起点,因为工作程序是单线程的,所以应该为每个核心运行一个。更多或更少的工作程序取决于您的设置。这严重依赖于您的作业。利用了多个内核吗?还是您的工作大部分时间都在等待外部资源?您需要尝试不同的设置并查看所有相关资源。


那么你的意思是每个进程都是原子式进程并且是安全的? - xpepermint
我认为这里缺少的是SELECT ... FOR UPDATE。 - xpepermint
查询是原子的。因此,如果您执行查询UPDATE jobs SET locked_at = '..', locked_by = 1 WHERE id = 12 and (locked_at is null or locked_at < '..'),则仅在没有其他有效锁时才会更新locked_at和locked_by。DBMS首先检查where条件,然后执行更新并确保行在中间未更改。因此,您无法覆盖现有锁定。 - gregor
当然,您可以使用 SELECT ... FOR UPDATE,但这并非必要且难以实现,因为所有锁定机制都不同于各种 DBMS。 - gregor
2
顺便提一下,现在锁定代码(lock_exclusively!)存储在可插拔的后端中。例如,对于ActiveRecord,它位于https://github.com/collectiveidea/delayed_job_active_record/blob/master/lib/delayed/backend/active_record.rb - rboyd
不确定使用锁定是否更好,但是通过复制我得到了以下警告:[Warning] Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. The statement is unsafe because it uses a LIMIT clause. This is unsafe because the set of rows included cannot be predicted. Statement: UPDATE \delayed_jobs` SET `delayed_jobs`.`locked_at` = '2014-10-25 02:00:25', `delayed_jobs`.`locked_by` = 'delayed_job host:www1 pid:15229' WHERE ((run_at <= '2014-10-25 02:00:25' AND (locked_at IS NULL OR locked_at < '2014-10-24 22:00:25') OR [..]` - 2called-chaos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接