如何在ASP.NET Core程序啟動時運行異步任務(3)


原文:Running async tasks on app startup in ASP.NET Core (Part 3)
作者:Andrew Lock
譯者:Lamond Lu

之前我寫了兩篇有關在ASP.NET Core中運行異步任務的博文,本篇博文是對之前兩篇博文中演示示例和實現方法的簡短跟進。

你可以通過以下鏈接查看之前的博文。

啟動任務的例子

在之前博客中,我收到的最常見的反饋是關於我在描述問題時使用的例子。在我最初的博客中,我列舉了3種可能場景,在這3種場景中,你希望在ASP.NET Core應用啟動時運行一些異步任務。

  • 檢查強類型配置是否合法
  • 使用數據庫或者API填充緩存
  • 運行數據庫遷移

對於前兩種場景,沒有任何問題,但是對於數據庫遷移,一些博友提出了一些疑問。其實在兩篇博文中我一直都反復說明,數據庫遷移作為啟動任務不是一個很好的方案,這里我只是想用它作為一個說明如何在ASP.NET Core程序啟動時運行異步任務的例子。現在來看,當時使用這個例子是非常失敗的。

數據庫遷移是一個糟糕的選擇

那么為什么在ASP.NET Core應用啟動時,運行數據庫遷移任務會是一個問題呢?畢竟,在應用程序開始處理請求之前,你肯定要完成數據庫遷移!

其實這里其實有3個問題:

  1. 數據庫遷移是應該是單線程的
  2. 遷移數據庫需要更多的權限
  3. 開發人員不太喜歡直接運行數據庫遷移

下面我們依次說明一下。

數據庫遷移應該是單線程的

擴展一個Web應用最常用的方式之一是進行橫向擴展,啟動多個運行實例並使用負載均衡分發請求

這種Web集群擴展的方案是非常有效的,特別是當當前應用是無狀態的(請求被分發到各個應用程序中,如果一個應用程序崩潰,其他的Web應用實例依然可以處理請求)。

但是不幸的是,如果嘗試將數據庫遷移作為應用啟動任務,你很可能就會遇到問題。如果有超過1個以上的實例同時啟動,多個數據庫遷移任務將同時運行。

雖然並不能保證你一定會遇到麻煩,但除非你非常小心地確保冪等更新和錯誤處理,否則你很可能會陷入困境。

你肯定不希望使用這種方法,因為它可能產生的數據庫完整性問題。 這里一個更好的選擇是先啟動單個實例完成遷移操作。 這樣數據庫遷移任務變成一個單線程任務,自然也就避開最嚴重的危險。

這種方法比將數據庫遷移作為啟動任務運行更有意義,但它更安全,更容易實現。

當然,這不是唯一的選擇。 如果你對啟動任務遷移的想法有所了解,那么你可以使用分布式鎖來確保只有一個應用程序實例來運行遷移腳本。 然而,這並沒有解決第二個問題......

遷移通常需要更多的權限

通常來說,最佳實踐是你必須限制你的應用程序,以便他們只有權訪問和修改所需的資源。 如果報表應用只需要讀取銷售數據,那么它應該無法修改它們,或者更改數據庫表結構! 為指定的連接字符串配置可操作的權限,可以防止在的的應用出現安全問題時產生大量影響。

如果你正在使用Web應用程序本身來運行數據庫遷移,那么該Web應用程序自然需要數據庫權限才能執行高風險活動,例如修改數據庫表結構,更改權限或更新/刪除數據。 你真的希望您的Web應用程序能夠刪除你的學生表嗎?

同樣,你可以使用一些特定的實現,例如,與正常的數據庫訪問相比,使用不同的連接字符串進行遷移。 但是,如果使用外部遷移過程,你根本無法鎖定Web應用程序進程。

開發人員不習慣直接運行EF Core遷移

這是一個不那么明顯的觀點,但是很多人表示在生產環境中使用EF Core遷移工具可能不是一個好主意。

就個人而言,我已經有1年多沒有在生產環境中使用EF Core遷移了,到目前為止遷移工具肯定已經有所改善。 話雖如此,我仍然看到一些問題:

就我自己而言,我經常使用DbUp和FluentMigrator,而不會使用EF Core遷移。我發現它們都運行良好。

因此,如果數據庫遷移任務不適合應用啟動任務示例,那么哪些任務才是比較適合的呢?

比較適合作為啟動任務的一些例子

雖然在之前的博文中我已經反復提到了一些例子,但我還是將在下面再次描述它們。這里其他博友還給我一些有趣的補充。

  • 檢查強類型配置是否有效。ASP.NET Core 2.2引入了配置驗證,但它只在首次訪問IOptions 類時執行此操作。 正如我在之前文章中所描述的那樣,你可能希望在應用啟動時進行驗證,以確保你的環境和配置有效。
  • 填充緩存。 你的應用程序可能需要來自文件系統或遠程服務的數據,它只需要加載一次,但加載需要耗費相當多的資源,所以在應用程序啟動之前加載此數據可減少請求延遲。
  • 預連接到數據庫和/或外部服務。 以類似的方式,你可以通過連接到數據庫或其他外部服務來填充數據庫連接池。 這些通常是相對昂貴的操作,因此是很好的用例。
  • 預編譯加載應用中所有的單例服務。我認為這個一個非常有趣的想法,通過預加載依賴注入容器中注冊的單例服務,你可以減少請求的響應時間。

使用健康檢查來完成啟動任務

我完全同意有關數據庫遷移的反饋。 當這不是一個好主意時,將它們用作啟動任務的示例有點誤導,特別是因為我個人不使用我所描述的方法!

然而,很多人都同意我所描述的另外一種方法 - 在啟動Kestrel服務器和處理請求之前運行啟動任務。

這里Damian Hickey針對這個方案提出了一個問題,他建議盡快啟動Kestrel服務器。 他建議在所有啟動任務完成后,使用運行健康檢查向負載均衡器發出信號,表明應用程序已准備好開始接收請求。 與此同時,所有非健康檢查流量(如果負載均衡器正在執行此任務,則不應該有任何流量)將收到503服務不可用。

這種方法的主要好處是它可以避免網絡超時。 一般來說,應用程序最好能盡快的針對請求返回錯誤代碼,而不是根本不響應請求,並導致客戶端超時。 這減少了客戶端所需的資源數量。 通過較早啟動Kestrel服務器,應用程序可以更早地開始響應請求,即使響應是“未就緒”響應。

這實際上與我在第一篇文章中描述的方法非常相似,但是我沒有選用它了,因為它太復雜了,而且沒有達到我設定的目標。 從技術上來說,在那篇文章中,我只是關注在Kestrel服務器啟動之前運行任務的方法,健康檢查方法不能完成這個功能。

然而,Damian提出的問題讓我再次思考。 在我下一篇博客中,我將描述如何使用ASP.NET Core 2.2的健康檢查功能向負載均衡器發送信號,表明應用程序已經完成了所有啟動任務。

總結

在這篇文章中,我分享了我之前關於在ASP.NET Core應用程序啟動時運行異步任務的一些反饋。 這里最大的問題是我選擇使用EF Core數據庫遷移作為啟動任務的示例。 數據庫遷移不適合在應用程序啟動時運行,因為它們通常需要由單個進程運行,並且需要比更多的數據庫權限。

我提供了一些比較適合作為的啟動任務的場景,並且描述了Damian給出的建議 - 盡快啟動Kestrel服務器,並使用運行狀況檢查來指示任務何時完成。 我將在下一篇文章中描述如何實現這一功能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM