我在 n 服务器上每隔 x 秒运行一次cron任务。它将“SELECT FROM table WHERE time_scheduled< CURRENT_TIME”,然后对此结果集执行冗长的任务。
我现在的问题是:如何避免让两个单独的服务器同时执行相同的任务?
想法是在选择后用设定的间隔更新* time_scheduled *。但是如果两台服务器同时运行查询,那就太晚了,不是吗?
欢迎所有想法。它不一定是严格的MySQL解决方案。
谢谢!
答案 0 :(得分:0)
我猜你有一个MySQL实例,以及来自 n 服务器的连接来运行这个处理工作。你在这里实现一个工作队列。
您提到的table
需要使用InnoDB访问方法(或Percona或MariaDB提供的其他一种易于使用的事务访问方法)。
您的表中的这些项目是否需要批量处理?也就是说,他们是否有某种程度的相互关联?或者您的服务器进程是否可以逐个处理它们?这是一个重要的问题,因为如果您可以单独或小批量处理它们,您将在服务器进程之间获得更好的负载平衡。我们假设小批量。
这个想法是防止任何服务器进程抓住表中的一行,如果某个其他服务器进程有该行的话。我不得不做很多这样的事情,这是我的建议;我知道这很有效。
首先,在表中添加一个整数列。称之为“工作”或某种类似的东西。给它一个默认值零。
其次,为每个服务器分配一个永久ID号。服务器IP地址的最后一部分(例如,如果服务器的IP地址是10.1.0.123,ID号是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)
上的复合索引可能会有助于此表现良好。