2012-07-16 49 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 

顯然,它並不完美,但它的作品對我來說,這很簡單!