Google App Engine (07) - 任務佇列與排程任務

+1

No comments posted yet

Comments

Slide 2

在這個部分的課程中,我們可以了解下列事項: 任務佇列與排程任務的異同。 如何利用任務佇列執行任務。 如何自訂佇列。 如何利用參數控制任務佇列的任務。 排程任務的設定。

Slide 3

任務 (Task) 就是對於請求處理器的要求。 跟其他要求不一樣的是,這些要求是經由 (被動或主動) Google App Engine 所發出的。 執行任務就是呼叫應用程式的網址,這些網址又可以稱之為 web hooks。

Slide 4

任務佇列 (Task Queues) 可以將部分作業,透過任務佇列的方式加以執行。如此一來,呼叫任務的程式就可以馬上返回,以便快速地回應使用者需求,提供使用者更佳的使用經驗。 像是更新 datastore ,或進行其他較費時的工作時,就可以考慮使用任務佇列的方式,通常可以大幅減少使用者等待的時間。 任務佇列內的任務,如果執行失敗後會自動再次回到佇列,等候重新執行。任務執行成功與否的判斷主要是依據 HTTP 回應的狀態碼,只有 200 表示任務執行成功,其他數值皆視為執行失敗。 排程任務 (Scheduled Tasks, Cron Jobs) 在特定的時間自動執行特定的任務,跟系統的使用狀態沒有關係。 像是定期維護、寄送統計報表、與外部系統的同步更新等作業,都屬於排程任務的適用範圍。 排程任務並不會確保任務的成功執行。不過我們可以在排程任務的程式中使用任務佇列的方式來達成確保成功執行的需求。 在測試環境下,不管是任務佇列或是排程任務內的任務都不會被自動執行,必須連結至測試伺服器的管理介面中 (http://localhost:8080/_ah/admin/) 以手動的方式加以執行。

Slide 6

程式可以將任務放置於任務佇列中,而任務佇列會以定義好的速率 (rate) 執行存放在佇列內的任務。 每個應用程式最多可以擁有 10 個排程佇列,每個佇列都可以設定自己的執行速率。 任務佇列可以同時執行多個任務,所以可以用來同時執行大量的任務。 雖然 Google App Engine 會盡量依據任務置入佇列的順序執行任務,但是卻不保證一定會依據這樣的順序。此外,程式也無法掌握因為執行失敗而重新嚐試執行的任務之執行順序。因此我們在設計任務時,應該避免任務之間必須依據特定的順序才能正常地執行。 如果任務是在預設版本下被置入佇列,那麼此任務執行時也會用預設版本的程式加以執行,即使兩個時間點的預設版本並不是同一個版本編號。但是如果任務是在特定版本編號下被置入佇列,那麼執行時就會利用此一編號的版本。

Slide 7

代幣桶 (Token Bucket) 是用來控制任務佇列執行速率的機制。 代幣 (Token):使用代幣來控制任務的執行。每一個任務在執行前都必須取得代幣,任務執行完畢後則釋放所取得的代幣。 容量 (Bucket size):最多可以擁有的代幣數量,也就是最多可以同時執行幾個任務。 補充率 (Refill rate) : 使用過的代幣必須以補充率的速度放回代幣桶中,以便讓其他等候執行的任務能夠再次取得。

Slide 8

每個應用程式都擁有一個預設的任務佇列,這個佇列的名稱就是 “default”。它的容量是 5,而補充率則為 5個/秒。 雖然增加容量可以讓 Google App Engine 同時執行更多的任務,但是過多的任務可能引起系統資源的不足,反而降低了整體的運行效率。所以在設定時必須謹慎,或者是透過模擬的方式加以判斷合適的設定值。 佇列的設定檔 queue.xml 必須放置於 /WEB-INF 的目錄下。 <name></name> : 佇列名稱。 <rate></rate> : 補充率,格式為 “數量/單位”。單位包含 s (秒)、m (分)、h (小時)、d (天)。舉例來說,60/m 跟 1/s 一樣,都是一秒鐘補充一個代幣。 <bucket-size></bucket-size>:容量。

Slide 9

在預設的情形下,沒有任何佇列存在。 default 佇列在使用時會自動產生,不需要先行定義。

Slide 10

為了方便測試,我們寫了一隻程式,可以產生特定數量 (taskCount) 的任務,並將這些任務置放於特定的佇列 (queueName) 。 如果要使用的是預設佇列,也可以採用 QueueFactory 的靜態函式 getDefaultQueue()。

Slide 11

這是我們測試任務的請求處理器。 因為任務請求處理器的回應訊息會被忽略 (除了 HTTP 的狀態碼外),所以我們將訊息記錄在紀錄檔中。 任務佇列預設採用 POST 的方式呼叫請求處理器,因此我們必須實做 doPost。 為了測試方便,我們一併實作了 doGet。

Slide 12

雖然預設的情形下可以直接使用預設的佇列 (default),但是我們仍舊需要定義處理器的對應關係,讓 Google App Engine 知道應該要由哪個請求處理器負責。 因為請求處理器跟一般處理器一樣都是透過網址的方式加以呼叫,所以必須限制對其的存取。 可以透過前端處理器限制只有開發人員(管理者)才能連結,此設定不會影響任務佇列對其呼叫的合法性。

Slide 13

程式上傳後,我們可以直接連結請求處理器。 如果已經使用開發者的帳號進行過登入的程序,請求處理器將會正常執行。

Slide 14

在應用程式管理介面的紀錄上,我們可以看到請求處理器確實已被正常執行。

Slide 15

產生 10 個任務,並放置到預設的佇列 (default)。

Slide 16

我們看到預設佇列已經被自動產生,而且有 10 個任務等待執行。 事實上,任務放置於佇列中已經經過約 30 秒的時間,但是依舊尚未開始執行。 任務佇列雖然會盡其可能的”盡早”執行任務,但是卻無法保證時間的即時性。

Slide 17

佇列中的任務執行完畢。 正確執行完畢的任務將會被自動刪除。

Slide 18

處理器一共被呼叫了 10 次,也就表示任務一共執行了 10 次。 預設採用 POST 的方式呼叫請求處理器。

Slide 19

我們重新執行程式,這次產生 5 個任務並放置到 datastore 這個佇列中。 執行時產生例外的錯誤,原因在於 Google App Engine 不認識 datastore 這個佇列。

Slide 20

佇列的設定檔 query.xml 必須放置在 /WEB-INF 目錄下。 容量設定為 2 ,而補充率設定為 5 個/秒。

Slide 21

同樣必須在 web.xml 設定請求處理器的對應關係。

Slide 22

再次執行程式,這是我們順利將 5 個任務放置於 datastore 這個佇列中。

Slide 23

在管理介面看到 datastore 佇列中一共有 5 個等待執行的任務。 除此之外,我們也可以看到佇列 datastore 的相關設定。

Slide 24

任務執行完畢後,在紀錄檔內一共出現了 8 次請求處理器的呼叫,其中有 3 次是執行錯誤的情況 (HTTP 的狀態碼 500),所以依舊算是執行 5 次。 原因在於應用程式初始化時間過長,造成了呼叫的失敗。

Slide 25

將補充率改為 1個/秒 後重新執行,可以看到這次沒有出現任何執行錯誤。 在執行完前兩個任務後,之後任務之間的執行時間間隔約為一秒,原因在於補充率只有 1個/秒。 事實上,任務的間隔時間大於一秒,再次證明任務的執行時間是無法精確地加以預測。

Slide 26

佇列設定時容量不可以超過 20。

Slide 27

佇列設定時補充率不可以超過 20個/秒,注意這裡的限制是將所有佇列的補充率相加之後的結果。

Slide 28

可以透過工具更新佇列的設定檔,工具所在的位址為 %ECLIPSE_HOME%/plugins/com.google.appengine.eclipse.sdkbundle.versionnumber/appengine-java-sdk-versionnumber/bin 其中 %ECLIPSE_HOME% 是 Eclipse 的安裝目錄 指令為 appcfg update_queues path_to_war_archieve ,其中 path_to_war_archieve 是指到 war 檔或 war 的目錄結構。

Slide 30

每個任務都擁有一個具有唯一性的名稱。 此一名稱可以由 Google App Engine 自動產生或是程式自行定義。如果是由 Google App Engine 所產生,Google App Engine 會確保此一名稱的唯一性。 當 Google App Engine 發現任務名稱與現有任務,或是”某段”時間之前已執行過的任務名稱相同的話,將會產生例外的錯誤。 此一功能可以用來避免重複執行相同的任務。重複執行常見於使用者重複按下按鈕,或是進行網頁重整的動作。 “某段”時間之前已經執行過的任務,Google App Engine 稱之為墓碑 (tombstones)。這段時間現今的設定為 7 天。

Slide 31

可以透過 TaskOptions.Builder 指令取得 TaskOptions 物件。 TaskOptions 物件可以設定下列參數: url 可以用來指定處理器的網址,其預設值為 /_ah/queue/佇列名稱。 param 會轉成網頁呼叫的參數。 method 處理器呼叫方式,預設為 POST。 header/headers 可以用來指定 HTTP 的標頭。 countdownMills/etaMills 設定任務經過多久以後才開始執行。countdownMills 指的是距離置入佇列的時間差距。而 etaMills 則用來指定一個絕對的時間。

Slide 32

每個任務都有其獨一無二的名稱,此為 Google App Engine 自行賦予的任務名稱。

Slide 33

在此程式範例中,我們故意產生兩個名稱一樣的任務,並將這些任務放到佇列中。

Slide 34

執行結果顯示第一個任務正確的存放於佇列之中,但是第二個任務則存放失敗。

Slide 35

程式中建立一個任務,並設定此任務呼叫的網址、方式、以及標頭。另外加上一個名稱為 user 的變數,而執行的時間則指定在 60 秒之後。

Slide 36

正確把任務置放到佇列中。

Slide 37

可以看到被呼叫的處理器不是之前預設的處理器,而是在程式內指定網址所對應的處理器。 從程式執行到任務被執行中間差距大於 71 秒,顯然我們指定 60 秒的差距只是一個最小的建議值。任務佇列沒有辦法保證時間的精確性。 可以看到呼叫方式改為 GET ,而且多了一個 user 的參數。

Slide 39

Google App Engine 根據 HTTP 回應的狀態碼決定任務執行是否成功,只有 200 的狀態碼才算是成功的執行。 當任務執行失敗時,Google App Engine 會延遲一段時間才重新嚐試執行任務,以避免資源過度損耗。延遲時間會根據失敗次數增加而逐漸延長,但是最少一天會重新嚐試一次。 可以透過 HTTP 標頭知道是否為第一次執行,或是已經經過多少次的嚐試。 X-AppEngine-TaskRetryCount 在極為特殊的情況下,一個正常執行完畢的任務有可能會再次被執行。如果這樣的行為會產生嚴重的後遺症,在使用上就必須加以小心。

Slide 40

程式利用強制送出 HTTP 回應狀態碼 500 的方式模擬任務執行失敗,迫使佇列重新嚐試執行任務。 程式檢查 X-AppEngine-TaskRetryCount 的數值,在錯誤嚐試達到 5 次後就不再送出 HTTP 狀態碼 500。此時會送出預設的狀態碼 200,表示任務執行成功。 我們可以利用這個方法來避免任務一再失敗,進而影響系統本身的運作。

Slide 41

透過之前撰寫的程式呼叫任務。 別忘了在 web.xml 與 queue.xml 設定相關的參數。

Slide 42

在記錄檔中,可以看到任務一共被呼叫了 6 次,前 5 次的回應狀態碼都是 500 。最後一次的狀態碼則為 200,表示任務正常執行完畢,因此停止再次嚐試的動作。

Slide 44

排程任務也是透過呼叫請求處理器的方式執行任務。但是跟任務佇列不同的是,排程任務是採用 GET 的方式呼叫處理器,而且此呼叫方式是不能改變的。 排程任務的網址同樣必須加以保護,以避免一般使用者直接呼叫。 排程任務也不能傳遞參數或修改標頭檔。 如果是排程任務的呼叫,在標頭檔中將會有一個名為 X-AppEngine-Cron 的標頭,其內容為 true。 排程任務永遠會執行預設版本的程式,而無法指定執行特定版本編號的程式。

Slide 45

排程任務的設定檔 corn.xml 必須放置於 /WEB-INF 的目錄下。 <url></rul>:排程任務的呼叫網址。 <description</description>:描述。 <schedule></schedule>:執行時間的敘述。 every 1 minutes -> 每一分鐘 every 10 minutes -> 每十分鐘 every day 21:00 -> 每天下午九點 every tuesday 21:00 -> 每星期二下午九點 2nd sunday 21:00 -> 每個月第二個星期天下午九點 2nd sunday of may 21:00 -> 五月的第二個星期天下午九點 <timezone></timezone>:時區。

Slide 46

排程任務的設定檔 cron.xml 必須置放於 /WEB-INF 目錄下。 指定每分鐘執行一次,而且採用台北的時區。

Slide 47

可以透過 SDK 所提供的命令列工具檢查設定檔的內容有沒有錯誤,以及相關的排程結果。

Slide 48

也可以用命令列工具將排程任務的設定上傳到 Google App Engine。 透過 Eclipse 的介面也同樣可以上傳排程任務的設定檔,但是透過 Eclipse 介面會將所有檔案上傳,可能會造成不必要的錯誤。

Slide 49

排程設定已經生效,但是尚未開始執行。

Slide 50

排程任務已經順利執行,呼叫的任務將會產生 10 個任務並放到預設的佇列中。

Slide 51

排程任務執行後,一連產生 10 個任務佇列的任務。

Slide 1

Google App Engine Google 應用服務引擎

Slide 2

任務佇列 與 排程任務

Slide 3

we

Slide 4

任務佇列 非同步化的執行任務 重複嚐試直到成功 排程任務 在特定時間執行任務 不會重覆嚐試

Slide 5

任務佇列

Slide 6

透過網址呼叫 執行速率 可同時執行多個任務 不保證順序與時間點

Slide 7

容量 : 2 補充率 : 1個/1分鐘 開始 5秒鐘後 1分鐘後

Slide 8

預設佇列 default 容量不是越大越好 設定檔 - queue.xml 佇列名稱、補充率、容量

Slide 29

控制任務

Slide 30

必須具有唯一性 GAE 或程式自行指定 墓碑 避免重複執行

Slide 31

TaskOptions url name method header/headers countdownMills/etaMills

Slide 38

重新嚐試

Slide 39

從佇列中取出 任務加以執行 HTTP 狀態碼等於200? 增加延遲時間 放回佇列中 正常執行完畢 是 否

Slide 43

排程任務

Slide 44

採用GET的方式呼叫 不能傳遞參數 不能修改標頭檔 僅能執行於預設版本

Slide 45

cron.xml url description schedule timezone

Slide 52

謝謝您! cyril.hcwang@gmail.com

Summary: 在這個部分的課程中,我們可以了解下列事項: 1. 任務佇列與排程任務的異同。 2. 如何利用任務佇列執行任務。 3. 如何自訂佇列。 4. 如何利用參數控制任務佇列的任務。 5. 排程任務的設定。

Tags: cloud cloudcomputing google appengine

URL: