8

我很惊讶我无法找到更多关于此的信息,但唉,我仍然找不到答案。我们最近转换为 AWS,将我们简单的网站迁移到更强大、更可靠的系统。目前让我感到困惑的是在分布式系统上管理 cron 作业,当该 cron 作业被推送到环境中的每个实例时。

这是用例:

背景

设置

我们正在运行一个传统的 LAMP 堆栈。可能是第一个问题,但这就是我们遇到的问题。

数据库表

table1

 - id int(11)
 - start date
 - interval int(11) (number of seconds)

table2

 - id int(11)
 - table1_id int(11)
 - sent datetime

目标

目标是脚本每天运行一次并检查以下内容:

  1. 当前日期已过table1.start
  2. table1.start< 当前日期
  3. table1.interval> 0
  4. 今天正好是一个完整的间隔(如果间隔是 7 天 [以秒为单位] 并且它是第 6 天,则会失败)
  5. 今天没有条目,table2并且table2.senttable2.table1_id之前的检查相匹配。

如果所有这些检查都通过,我们在 table2 中为每个具有间隔的 table1 插入一个条目。这也意味着我们根据表 2 中的数据发送电子邮件。

问题

本质上,我们有两个查询,由上述块表示。问题在于,在分布式系统上,每个实例将同时运行 cron(或彼此相隔几毫秒)。table2没有“事务”的概念,因此如果一个实例在其他实例运行第一个查询之前没有机会插入,每个实例都会发送一封电子邮件。

解决方案???

我已经对此进行了大量研究,但我想出的唯一可能的解决方案如下:

Cron 实例

设置一个负责运行 cron 作业的独立实例。虽然这肯定会(据我所知)工作,但对于一项成本不高且最多每天只需要运行一次的工作来说,这是非常昂贵的。

PHP 调度程序

将 cron 设置为定期运行充当调度程序的 PHP 脚本。这是我们在研究表明这对于我们有限的时间和金钱来说是最简单的路线之后要走的路线。我遇到的问题是,这似乎只是将并发问题从消耗作业转移到了调度作业。您何时安排作业,以便不会从运行 cron 的每个实例同时安排多个作业?

这种方法似乎也很“笨拙”(借用我朋友最喜欢的一句话),我不得不同意。

交易

尽管我对此进行了相当多的研究,但始终通过数据库上的原子事务来解决并发问题,但据我所知,这在 LAMP 中并不容易实现。但也许我错了,我很高兴被证明是这样。

最后

因此,如果有人可以帮助我解决这个问题,我将不胜感激。也许我的谷歌搜索技能已经生疏了,但我无法想象我是唯一一个遭受这项(可能很简单)任务的人。

4

3 回答 3

4

我有一个类似的问题。而且我还有每分钟都必须运行的 cron 作业,但只能在单个主机上运行

我用这个 hack 解决了这个问题,它运行 amazon 自动缩放工具来确定它运行的盒子是否是这个自动缩放组中最后一个实例化的盒子。这显然假设您使用自动缩放,并且主机名包含实例 ID。

#!/usr/bin/env ruby

AWS_AUTO_SCALING_HOME='/opt/AutoScaling'
AWS_AUTO_SCALING_URL='https://autoscaling.eu-west-1.amazonaws.com'
MY_GROUP = 'Production'

@cmd_out = `bash -c 'AWS_AUTO_SCALING_HOME=#{ AWS_AUTO_SCALING_HOME }\
  AWS_AUTO_SCALING_URL=#{ AWS_AUTO_SCALING_URL }\
  #{ AWS_AUTO_SCALING_HOME }/bin/as-describe-auto-scaling-instances'`

raise "Output empty, should not happen!" if @cmd_out.empty?
@lines = @cmd_out.split(/\r?\n/)
@last = @lines.select {|l| l.match MY_GROUP }.reverse.
  detect { |l| l =~ /^INSTANCE\s+\S+\s+\S+\s+\S+\s+InService\s+HEALTHY/ }
raise "No suitable host in autoscaling group!" unless @last
@last_host = @last.match(/^INSTANCE\s+(\S+)/)[1]
@hostname = `hostname`
if @hostname.index(@last_host)
  puts "It's me!"
  exit(0)
else
  puts "Someone else will do it!"
  exit(1)
end

将其保存为 /usr/bin/lastonly,然后在 cron 作业中执行:

lastonly && do_my_stuff

显然它并不完美,但它对我有用,而且很简单!

于 2012-09-27T14:31:12.430 回答
3

看看 Gearman 项目http://www.gearman.org。基本架构是您将拥有一台机器作为作业服务器,而所有其他机器将成为服务器的客户端。

您可以在作业服务器上设置 crontab,以向通过 Gearman 连接的所有客户端发送要执行的命令。然后,您可以使用 PHP 对您的 cron 作业进行切片和切块,并尽可能深入地了解 Map/Reduce。

这是一个关于概念及其工作原理的很好的教程:http ://www.lornajane.net/posts/2011/Using-Gearman-from-PHP

不要对立即使用像 Gearman 这样的东西感到灰心。分布式 cron 系统可能很复杂,但一旦你了解它,你就会没事的。

FWIW,我们每分钟在 Amazon EC2 上的 Gearman 工作场中处理数千个 cron 脚本。我们非常喜欢它。

于 2012-07-21T00:08:40.630 回答
0

您可以使用队列使任务只运行一次。

于 2021-02-23T03:43:54.023 回答