在工作上有些系統選擇將定期執行作業的邏輯寫在 ASP.NET 網站,再設定 SQL 排程 (SQLAgent) 或 Windows 排程器 (Task Scheduler) 呼叫它。這麼做的好處是邏輯完全集中在網站專案,不致分散於網站及主控台程式兩處(雖共用程式庫已可大幅減少重複,但仍比單一網站複雜,也減少換版同步更新的部署需求)。在這種情境下,我們需要一個 EXE 程式或一段 Script 呼叫 ASPX 以觸發批次作業,依過往經驗有幾種選擇:

  1. 自己寫 呼叫網頁的功能用 .NET WebClient 幾行就搞定,好處是可量身訂做較詳細的 Log 及自訂例外處理,加上錯誤通知等。
  2. 使用 Chrome Headless 模式 Headless Chrome 說明可參考這篇:使用 Headless Chrome 擷圖、轉存PDF、爬資料 用 Chrome 的好處是遇到問題可以開 Chrome 視覺化排除問題。不過在正式主機安裝軟體有時需外額外申請,若主機無法上網,則還不能用標準安裝程式,需另外下載離線安裝檔。參考:Google Chrome 完整的獨立離線安裝程式連結
  3. 使用現成 HTTP 客戶端工具 這部分選擇不少,curl 是我心中的首選。curl 在 Unix 界頗豐盛名,功能強大,有 Windows 版本可下載。 註:下載 curl for Windows 64 bit,解壓縮的 bin 目錄有 curl.exe、curl-ca-bundle.crt、libcurl-x64.dll 三支程式,我實測只需 curl.exe 就能跑,故部署時複製一個檔案就夠。

如果不想寫程式也不想裝軟體,curl 最省事。但是單用 curl 仍缺少幾項實務常見要求,例如:每次執行的 Log 記錄、出錯時留下明顯事件軌跡、發信通知等。

DOS 有 Pipeline 及導向機制,將輸出結果附加到 Log 檔不是難事,問題不大。至於錯誤事件,需分兩種狀況討論:

若使用 SQLAgent,批次檔只需在出錯時傳回非零 Exit Code (exit /b n, n > 1) 即被判定作業失敗:

Windows 工作排程器 (Task Scheduler) 比較討厭,即使批次檔回傳Exit Code 255,排程器記錄上依然是「動作已完成」, 需點開事件才看得到「傳回碼為255」,無法一眼看出有問題。

要克服這點,有個替代做法是產生 Windows 錯誤事件,如此透過事件檢視器可快速發現批次執行錯誤。關於 EventCreate 的使用請參考:在 DOS 批次檔寫入 Windows 事件

底下用一個完整範例進行演練。

我寫了一支 Test.aspx 模擬批次作業,依亂數決定回傳 "OK" 或是 "FAIL-錯誤訊息 秒數"

<%@Page Language="C#"%>
<script runat="server">
void Page_Load(object sender, EventArgs e) 
{
	Random rand = new Random();
	Response.Write(rand.Next(100) % 2 == 0 ? 
		"OK" : "FAIL-錯誤訊息 " + DateTime.Now.ToString("ssfff"));
}
</script>

跑排程用的批次檔有點小複雜,有些地方像咒語,但我已穿插註解,希望大家能看懂:

@echo off
cd e:\
set logfile=.\curl.log
rem 切換為UTF-8
chcp 65001
rem 記錄目前時間寫入Log檔
echo %date% %time% >> %logfile%
rem 呼叫網站將執行時間及傳回結果寫入輸出檔
curl -k -w "/ExecTime:%%{time_total}" http://localhost/asp/test.aspx 2>&1 > result.txt
rem 讀取輸出檔轉為環境變數result
set /p result=<result.txt
rem 將輸出結果寫入Log檔
echo %result% >> %logfile%
rem REF:https://stackoverflow.com/a/7006016/4335757
rem 檢查輸出結果是否有OK字樣,若有表執行正常,結束
if not "%result:OK=%"=="%result%" exit /b 0
rem 傳回結果未包含OK字樣,顯示錯誤訊息
echo %result%
rem 在事件簿加入錯誤事件
eventcreate /l Application /t Error /id 255 /so BatchJob /d "%result%"
exit /b 255

批次檔的流程概念如下:

  1. CHCP 65001 切換使用 UTF8 語系。當今網站幾乎都使用 UTF8,若不切換傳回結果會變亂碼。參考:在命令提示視窗(Command Prompt)顯示UTF-8內容
  2. 先用 DOS 內建指令取得日期與時間,寫入 Log 檔。(curl.log)
  3. 用 curl 呼叫排程網頁,將結果寫入 result.txt,加上 –w "/ExecTime: %" 一併記錄 ASPX 執行時間。(註:批次檔中 % 有特殊意義,故要寫成%%)
  4. 將result.txt 讀成 result 環境變數並寫入 Log。
  5. 利用字串置換技巧 (%varName:find=replace%) 檢查網站傳回結果是否包含 OK 字樣,若有,批次檔正常結束(Exit Code = 0) 參考:批次檔字串操作技巧
  6. 若傳回結果不包含 OK,印出結果,並用 EventCreate.exe 產生錯誤事件。

如此,我們完了 curl 呼叫網頁、記錄結果,並於錯誤時寫入 Windows 事件,至於寄送通知,可考慮在 DOS 批次檔寫入 Windows 事件 文末提到的附加寄信工作到指定事件的技巧(如果你完全不想寫程式的話,否則寫個通用通知信小工具或服務更能符合需求)。

Example of writing a DOS batch to call scheduled task webpage, including result check, logging and error event raising.


Comments

# by 韋銘

最近想用 SQL 排程呼叫 ASP.NET Web API,執行一些批次作業, 想問黑大,是否需要做什麼設定讓Web API不會Time out? 或是有什麼需要注意的部份? 謝謝

# by Jeffrey

to 韋銘, 呼叫WebAPI Timeout的發生點可能在IIS端,也可能在SQL Agent端,要先釐清問題來源,解決法也不同。

# by 阿岳

網址的部分如果想大量處理的話該怎麼做才好呢?

# by Jeffrey

to 阿岳,需要更明確定義大量處理,是要執行一連串呼叫還是在短時間內呼叫大量網址? 前者用迴圈即可,後者可靠多執行緒並行,但若目標集中在同一台主機,要留意不要超過主機負荷或被誤認為DoS攻擊。

# by 阿岳

謝謝黑大的回答~可以麻煩黑大幫我看一下嗎? 我是想要執行一連串呼叫 目前的做法如下 for /f %%a in (test.txt) do ( curl -k -w ExecTime%%{time_total} %%a >result.txt ) test.txt只放單純一個網址可以正常執行,多內容則失敗 檢查result.txt檔只會出現ExecTime0.266000 再麻煩黑大多提點提點了 謝謝!

# by Jeffrey

to 阿岳,我用同樣寫法測試是成功的 (參考: https://imgur.com/a/Z9MO6Of )。但有個地方你改一下再試看看,">result.txt"改成">>result.txt",將curl結果附加到result.txt而不是覆蓋前次結果,以觀察執行歷程。

Post a comment


53 + 22 =