我有一个cron任务,每x秒在n台服务器上运行。它将“ SELECT FROM table WHERE time_scheduled
现在的问题是:如何避免让两个单独的服务器同时执行同一任务?
想法是在选择* time_scheduled *后以设定的间隔对其进行更新。但是,如果两个服务器碰巧同时运行查询,那将为时已晚,不是吗?
欢迎所有想法。它不一定是严格的MySQL解决方案。
谢谢!
最佳答案
我猜您只有一个MySQL实例,并且来自n个服务器的连接才能运行此处理作业。您正在此处实现作业队列。
您提到的table
需要使用InnoDB访问方法(或Percona或MariaDB提供的其他事务友好访问方法之一)。
您表格中的这些项目是否需要分批处理?也就是说,它们之间有某种联系吗?还是服务器进程可以一对一地处理它们?这是一个重要的问题,因为如果可以单独或小批量处理它们,则将在服务器进程之间获得更好的负载平衡。让我们假设小批量。
这个想法是为了防止任何其他服务器进程在表中的某一行上抓住该行。我不得不做很多这样的事情,这是我的建议。我知道这可行。
首先,将整数列添加到表中。称其为“有效的”或类似的东西。给它一个默认值零。
其次,为每个服务器分配一个永久ID号。服务器IP地址的最后一部分(例如,如果服务器的IP地址是10.1.0.123,标识号是123)是一个不错的选择,因为它在您的环境中可能是唯一的。
然后,当服务器进行抓取工作时,请使用这两个SQL查询。
UPDATE table
SET working = :this_server_id
WHERE working = 0
AND time_scheduled < CURRENT_TIME
ORDER BY time_scheduled
LIMIT 1
SELECT table_id, whatever, whatever
FROM table
WHERE working = :this_server_id
第一个查询将始终获取一批要处理的行。如果另一个服务器进程同时进入,它将永远不会抓取相同的行,因为除非
working = 0
,否则任何进程都无法抓取行。请注意,LIMIT 1将限制您的批量大小。您不必这样做,但是可以。我还使用ORDER BY
首先处理等待时间最长的行。那可能是做事的有用方法。第二个查询检索完成工作所需的信息。不要忘记为您正在处理的行检索主键值(我称它们为
table_id
)。然后,您的服务器进程将执行所需的任何操作。
完成后,它需要将该行重新放入队列以备后用。为此,服务器进程需要将
time_scheduled
设置为所需的值,然后再设置working = 0
。因此,例如,您可以针对要处理的每一行运行此查询。 UPDATE table
SET time_scheduled = CURRENT_TIME + INTERVAL 5 MINUTE,
working = 0
WHERE table_id = ?table_id_from_previous_query
而已。
除了一件事。在现实世界中,这些排队系统有时会出错。服务器进程崩溃。等等参见墨菲定律。您需要一个监视查询。在这个系统中这很容易。
该查询将提供所有逾期五分钟以上的所有作业的列表,以及应该在其上进行工作的服务器。
SELECT working, COUNT(*) stale_jobs
FROM table
WHERE time_scheduled < CURRENT_TIME - INTERVAL 5 MINUTE
GROUP BY WORKING
如果此查询为空,则一切正常。如果在
working
设置为零的情况下出现大量作业,则服务器将无法跟上。如果它的作业带有working
设置为某个服务器的ID号,则该服务器正在午休。您可以根据需要重置分配给该服务器的所有作业,这些作业已经用完了。
UPDATE table
SET working=0
WHERE working=?server_id_at_lunch
顺便说一句,
(working, time_scheduled)
上的复合索引可能会帮助它更好地执行。关于php - SELECT + UPDATE避免返回相同的结果,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21659627/