之前研究過確保網站永遠處於執行狀態的 IIS 設定方式,最近遇上網站停用但網站背景排程照跑的靈異事件,發現事情跟我想的不一樣,自己對 IIS 站台 Process 模式及 AlwaysRunning 行為有些誤解,寫篇筆記備忘。

我們都知道 IIS 管理員站台有組控制鈕,可以重新啟動、啟動或停止站台。

大家想像按下去的動作是什麼?重啟或切換 AppPool 狀態?並不是! 這也是這次我誤解的主要來源。

.NET 在一個 Process 裡可建立多個記憶體空間和資源獨立的 AppDomain,達到比另建 Process 省資源又兼顧安全及穩定性隔離的效果。(參考:VITO の 學習筆記:什麼是應用程式定義域 ( application domain ))。IIS 7+ 的做法是在第一次收到 ASP.NET 網頁要求時,由 IIS 受控引擎模組在記憶體建立 AppDomain,由 AppDomain 處理 aspx 頁面要求。(詳情可參考 MS Learn 文件)


圖片來源

為了實地驗證,我做了一個簡單的 ASP.NET Web Site Project 網站,並設好 AlwaysRunning、Preload Enabled

Global.asax 會啟動一個 Task 跑背景排程,每一秒寫入一筆 Log,另外,在站啟動及結束時也會寫 Log。Log 訊息前方則會加上 Process ID # AppDomain ID 方便觀察: (延伸閱讀:新時代 .NET ThreadPool 程式寫法以及為什麼你該用力 async/await)

<%@ Application Language="C#" %>
<%@ Import Namespace="System.Threading" %>
<%@ Import Namespace="System.Threading.Tasks" %>
<%@ Import Namespace="System.Diagnostics" %>
<script RunAt="server">
    CancellationTokenSource cts;
    void Log(string msg)
    {
        System.IO.File.AppendAllText("D:\\Logs\\trace.txt", 
            Process.GetCurrentProcess().Id + 
            "#" + AppDomain.CurrentDomain.Id + 
            " - " + msg + "\n");
    }
    void Application_Start(object sender, EventArgs e)
    {
        Log("Application Start");
        cts = new CancellationTokenSource();
        Task.Run(async () =>
        {
            while (!cts.Token.IsCancellationRequested)
            {
                Log(DateTime.Now.ToString("HH:mm:ss"));
                await Task.Delay(1000, cts.Token);
            }
        });
    }
    void Application_End(object sender, EventArgs e)
    {
        cts.Cancel();
        Log("Application End");
    }
</script>

有了這個小實驗,我的疑惑很快獲得解答:

  1. 啟動停止中的站台時會自動啟動網站嗎?
    答案是不會, AlwaysRunning、Preload Enabled 自動啟動網站只適用電腦重開或 AppPool 重啟時。如下面展示,按下 Start 網站沒開始寫 Log,得等呼叫 http://localhost:8000/ 後才開始:

    如前面所提,站台需等到第一個請求進來時才啟動 AppDoamin。而由 Process ID / AppDomain ID 可證明站台重啟後仍是同一個 Process,只有換 AppDomain。
    同理,更新 bin、web.config 也會造成 AppDomain 停止,之後需發出 HTTP 請求才能啟動網站。(註:web.config 的 initializationPage 設定是在網站啟動後自動呼叫,無法用來觸發停止中的站台)
    以下展示在 bin 寫入檔案讓網站停止,停止後用 PowerShell 發送 HTTP 請求後才被喚醒另建 AppDomain 執行:(AppDomain Id 由 4 變 5)
  2. 那 IISEREST 或重啟 W3WC 服務後網站會自動啟動嗎?
    AppPool 重啟時,不需 HTTP 請求便可啟動 AlwaysRunning + Preload 網站,實驗可觀察到重啟後 ProcessID 不同。
  3. 「站台已停用,網站背景程序卻還在執行」的靈異現象是怎麼發生的?
    如第 2 點所說,設定 AlwaysRunning 的站台會在機器重啟或 IISRESET 時自動啟動,即便站台已被停用。簡單解釋是停用站台是停掉目前執行中的 AppDoamin 並拒絕接收 HTTP 請求,但 AlwaysRunning + Preload Enabled 會在 AppPool 啟動時建立 AppDomain 執行,造成這次的靈異現象。

歸納結論:

  1. IIS 站台是以 AppDomain 方式執行,一個 AppPool Process 可有多個 AppDomain,IIS 管理的站台啟動、停止、重啟功能控制的是 AppDomain,bin 或 web.config 異動也會造成 AppDomain 結束
  2. AlwaysRunning + Preload Enabled 適用重新開機、AppPool 啟動、IISREST 後自動啟動網站,IIS 管理員重啟站台後需對網站發動請求才會建立 AppDomain
  3. 站台停用狀態下,AlwaysRunning + Preload Enabled 網站會在 AppPool 啟動時自動啟動。若不想程式被誤執行,可將 AppPool 也停用 (註:IIS 管理員設定停止的 AppPool 及站台,重新開機後仍會維持停止狀態)

Explanation of AppDomain operation design in IIS sites and how AlwaysRunning and Preload Enabled settings work, demonstrated by experiments.


Comments

Be the first to post a comment

Post a comment