2012-07-16 12 views
7

我很惊讶,我无法在这方面找到更多,但唉,我仍然无法找到答案。我们最近转换为AWS,将我们的简单网站转移到更强大和更可靠的系统。目前让我莫名其妙的是在分布式系统上管理cron作业,当cron作业被推送到环境中的每个实例时。AWS上的Cron(或一般的分布式系统)

这里的用例:

背景

设置

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

DB表

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. 今天3210
  4. table1.interval> 0
  5. 正是整体区间远
  6. 存在table2这样table2.sent没有进入今天(这样会失败,如果时间间隔为7天[以秒],这是第6天)和table2.table1_id匹配先前的检查。

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

的问题

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

解决方案???

我做的这个研究有相当数量,但我已经拿出了唯一可能的解决办法详述如下:

cron的实例

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

PHP调度

设置cron来定期运行充当调度的PHP脚本。这是我们在研究表明它对于我们有限的时间和金钱来说最简单的时候所走的路线。我碰到的问题是,这似乎将并发问题从消费性工作转移到了调度工作。您什么时候安排这些工作,使得多个工作不是从运行cron的每个实例同时安排的?

这种方法也似乎很“kludgy”(借用我朋友最喜欢的词),我不得不同意。

交易

虽然我已经研究这个颇有几分,并发总是与数据库原子事务解决,但就我所知,这是不容易实现与LAMP。但也许我错了,我很乐意证明如此。

最后

所以,如果有人能帮助我这一个,我将不胜感激。也许我的谷歌搜索技能正在生锈,但我无法想象自己是唯一一个遭受这种(可能很简单)任务的人。

+1

我没有足够的经验来把它变成一个非常有建设性的答案,但你有没有看过亚马逊的SWF?由于您已经在AWS上,可能是cron的可靠替代品。 – 2012-07-16 23:05:57

+0

这可能听起来过于夸张,但也许你可以看看[Zookeeper](http://zookeeper.apache.org/)。它使用起来很简单,轻便,强大,并且可以让您的任务尽可能简单地协调/同步分布式任务。 – Viccari 2012-07-16 23:25:37

+0

也许值得注意的是,我们正在使用Kohana。我想知道是否有某种程度的锁定我可以在数据库查询上进行确认,以确保交易是原子级的和串联的。 – Ryan 2012-07-17 01:19:37

回答

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,我们每分钟处理亚马逊EC2上的Gearman工作区中的数千个cron脚本。我们非常喜欢它。

4

我有一个类似的问题。我也有cron作业,必须每分钟运行,但只在一台主机上运行亚马逊自动缩放工具,以查明它运行的盒子是否是最后一台在此自动缩放组中实例化。这显然假定您使用自动调节,并且主机名包含实例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 

显然,它并不完美,但它的作品对我来说,这很简单!