ASPX頁Web服務(wù)調(diào)用優(yōu)化性能
發(fā)表時間:2024-02-20 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]摘要:本文介紹了如何通過異步方法消除使用 Microsoft ASP.NET 的 Web 服務(wù)調(diào)用的性能問題和線程池資源的消耗問題! ∏闆r:從 ASP.NET 頁面調(diào)用 Web 服務(wù)時的性能破壞 我們在本文中討論 Web 服務(wù)時,期望在各種情況下都可以享用 Web 服務(wù)。一個主要的情況是從中...
摘要:本文介紹了如何通過異步方法消除使用 Microsoft ASP.NET 的 Web 服務(wù)調(diào)用的性能問題和線程池資源的消耗問題。
情況:從 ASP.NET 頁面調(diào)用 Web 服務(wù)時的性能破壞
我們在本文中討論 Web 服務(wù)時,期望在各種情況下都可以享用 Web 服務(wù)。一個主要的情況是從中間層環(huán)境(如 ASP.NET Web 頁面)訪問 Web 服務(wù)。為 MapPoint .NET Web 服務(wù)的用戶提供支持的人員經(jīng)常收到這樣的問題,即用戶在使用其 Web 服務(wù)時,對 MapPoint .NET 的調(diào)用可能需要相當長的時間。這本身并不是什么問題,但某些其他因素可以使之成為比表面上要嚴重得多的大問題。
HTTP 雙連接限制
HTTP 規(guī)范表明,一個 HTTP 客戶端與任一服務(wù)器最多可以同時建立兩個 TCP 連接。這可以防止單個瀏覽器在瀏覽某個頁面(例如,具有 120 個嵌入的縮略圖)時,由于連接請求過多而使服務(wù)器負載過重。此時,瀏覽器將僅創(chuàng)建 2 個連接,然后通過這兩個管道開始發(fā)送 120 個 HTTP 請求,而不是創(chuàng)建 120 個 TCP 連接并通過每個連接來發(fā)送 HTTP 請求。對于中間層,此方法的問題在于,中間層可能會有 50 個同時請求連接的用戶。如果不得不為每個用戶進行一次 MapPoint .NET Web 服務(wù)調(diào)用,將會有 48 個用戶等待兩個管道中的一個空閑下來。
線程池限制
ASP.NET 處理傳入的請求的方式是通過一個稱為進程線程池的一組線程為其提供服務(wù)。正常情況下,請求傳入后,池中某個空閑的線程將為其提供服務(wù)。這里的問題在于,進程線程池不會創(chuàng)建無數(shù)個線程來處理大量的請求。具有最大線程數(shù)限制是一件好事,因為如果我們無限地創(chuàng)建線程,計算機上的全部資源將只能用來管理這些線程了。通過限制所能創(chuàng)建的線程數(shù),我們可以把線程管理的系統(tǒng)開銷保持在一個可控的水平。如果某個請求傳入時線程池中的所有線程都被占用,則該請求將排隊等候,在忙線程完成任務(wù)后,空閑出來的線程才能處理新請求。此方法實際上比切換到某個新線程更有效,因為不需要在請求之間進行線程切換。但存在的問題是,如果線程的使用效率不高(尤其是在非常忙的 Web 服務(wù)器上),則等候的請求隊列會變得很大。
考慮一下從 ASP.NET 頁面進行 Web 服務(wù)調(diào)用的情況。如果進行同步調(diào)用,則正在運行的線程將被阻塞,直到 Web 服務(wù)調(diào)用完成為止。在調(diào)用期間,線程無法進行任何其他活動。它無法處理其他請求,只能等待。如果某個單處理器計算機上具有默認的工作線程數(shù) 20,則只需 20 個同時進行的請求即可用完全部線程,以后的請求必須排隊等候。
該問題不僅限于 Web 服務(wù)
不僅調(diào)用 Web 服務(wù)的用戶會遇到從 Web 頁面進行調(diào)用時的擁堵且耗時較長的問題。進行任意數(shù)量的較長的調(diào)用都會遇到同樣的問題,例如:SQL Server? 請求、長文件的讀取或?qū)懭、各種 Web 請求或訪問某個并發(fā)資源(其中鎖定會造成嚴重的延遲)。實際上,有許多使用 Web 服務(wù)的情況,其服務(wù)調(diào)用比較迅速,并不是什么問題。但您或許會理解,如果您想通過代理服務(wù)器調(diào)用 MapPoint .NET Web 服務(wù),所使用的連接具有一定的延遲,同時相應(yīng)的服務(wù)可能又要花費一些時間來處理請求,則您可能在各處位置都看到延遲的情況,并且如果站點很忙,便可能出現(xiàn)問題。
改善問題
該問題的某些方面可以通過對環(huán)境進行某些配置設(shè)置來改善。我們看一下可用于改善該問題的某些配置設(shè)置。
maxconnections
連接到 Web 資源的默認雙連接限制可以通過一個名為 connectionManagement 的配置元素來控制。connectionManagement 設(shè)置允許您添加要讓其采用非默認連接限制的站點的名稱?梢詫⒁韵聝(nèi)容添加到典型的 Web.config 文件中,將您連接的所有服務(wù)器的連接限制默認值增加到 40。
<configuration>
<system.net>
<connectionManagement>
<add address="*" maxconnection="40" />
</connectionManagement>
</system.net>
<system.web>
...
應(yīng)當注意的是,對本地計算機的連接數(shù)量從來都沒有限制,因此,如果是連接到本地主機,則此設(shè)置無效。
maxWorkerThreads 和 minFreeThreads
如果收到 HTTP 503 錯誤(“服務(wù)暫時過載”),則表明線程池中的線程已全部占用,并且請求隊列也已超出最大值(appRequestQueueLimit 的默認設(shè)置為 100)。對于 IIS 5.0 安裝,可以簡單地增加線程池的大小。而對于 IIS 6.0 安裝(與 IIS 5.0 不兼容),這些設(shè)置將無效。
maxWorkerThreads 和 maxIoThreads 分別控制工作線程數(shù)以及處理新提交的 ASP.NET 請求的線程數(shù)。這些設(shè)置需要在您的 Machine.config 中進行配置,它們將影響您計算機上運行的所有 Web 應(yīng)用程序。maxWorkerThreads 是 Machine.config 中的 processModel 元素的一部分,并且您在查看后會發(fā)現(xiàn),該設(shè)置的默認值為每個處理器 20 個線程。
minFreeThreads 設(shè)置可以在 Machine.config 中進行配置,或者在您的應(yīng)用程序的 Web.config 文件中的 httpRuntime 元素下進行配置。該設(shè)置的作用是,當空閑的線程數(shù)低于所設(shè)置的限制時,將禁止使用線程池中的線程來處理傳入的 HTTP 請求。如果您需要某個進程線程池線程完成掛起的請求,這會很有用。如果所有的線程都被用來處理傳入的 HTTP 請求,并且這些請求在等待另一個線程完成其處理,那么就會進入死鎖狀態(tài)。例如,如果您正在從 ASP.NET 應(yīng)用程序進行對某個 Web 服務(wù)的異步 Web 服務(wù)調(diào)用,并且在等待回調(diào)函數(shù)完成該請求,就會出現(xiàn)這種情況。因為回調(diào)必須在進程線程池中的空閑線程上進行。如果查看一下您的 Machine.config,將會注意到 minFreeThreads 設(shè)置的默認值為 8,如果工作線程池的限制為 20,則該默認值還可以滿足需要,但是,如果線程池的大小增加到 100,該默認值就太小了。
應(yīng)當注意的是,如果您的 ASP.NET 應(yīng)用程序?qū)Ρ镜赜嬎銠C進行 Web 服務(wù)調(diào)用,則線程池限制的問題將被激化。例如,我為此專欄創(chuàng)建的測試應(yīng)用程序調(diào)用與 ASPX 頁面同處一臺計算機上的 Web 服務(wù)。因而,對于阻塞的調(diào)用,一個線程被同時用于 ASPX 頁面和 ASMX Web 服務(wù)請求。這有效地使 Web 服務(wù)器處理的同時請求數(shù)增加了一倍。在同時進行兩個 Web 服務(wù)請求(使用異步 Web 服務(wù)調(diào)用)的情況下,我們最終使同時進行的請求數(shù)增加了兩倍。為避免在回調(diào)本地計算機時出現(xiàn)此類問題,您應(yīng)當考慮您的應(yīng)用程序的體系結(jié)構(gòu),使其簡單地直接從 ASPX 代碼來執(zhí)行 Web 方法中的代碼。
Windows XP 限制
我們必須要注意,如果您在一個 Windows? XP 計算機上進行某項測試,則所面臨的另一個限制是 XP Web 服務(wù)器對所允許的同時連接數(shù)的人為限制。因為 Windows XP 不是服務(wù)器平臺,其同時連接數(shù)被限制為 10。這對于開發(fā)環(huán)境中的測試通常沒問題,但是如果試圖進行任何復(fù)雜的測試,該限制問題就會比較嚴重。本地計算機的連接不受此限制影響。
真正的解決方案:異步請求處理
調(diào)整配置設(shè)置是一種改善問題的方法,而在實際設(shè)計 Web 應(yīng)用程序時通過某種方式徹底解決問題則是另一回事。等待阻塞的調(diào)用完成的線程永遠也不會有更好的調(diào)整余地,因此,解決的辦法是完全避免阻塞問題。異步處理請求就是一個適當?shù)慕鉀Q方案。這表現(xiàn)在兩個方面:進行異步 Web 服務(wù)調(diào)用,以及在 ASP.NET Web 應(yīng)用程序中異步處理請求。
異步 Web 服務(wù)調(diào)用
在以前的專欄中,我寫了有關(guān)異步調(diào)用 Web 服務(wù)的問題。能夠使線程不用等待 Web 服務(wù)調(diào)用完成是創(chuàng)建釋放線程以便處理更多請求的異步頁面處理模型的關(guān)鍵部分。此外,異步調(diào)用 Web 服務(wù)也比較簡單。
請考慮以下 ASPX 頁面的 Visual Basic.NET 代碼:
' 錯用同步 Web 服務(wù)調(diào)用所造成的性能極差的
' 頁面!
Public Class SyncPage
Inherits System.Web.UI.Page
Protected WithEvents Label1 As System.Web.UI.WebControls.Label
Protected WithEvents Label2 As System.Web.UI.WebControls.Label
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'調(diào)用 Web 服務(wù)
Dim proxy As New localhost.Service1
Label1.Text = proxy.Method1(500)
Label2.Text = proxy.Method1(200)
End Sub
End Class
此代碼非常易懂。頁面加載時將創(chuàng)建一個 Web 服務(wù)代理實例,然后用該實例兩次調(diào)用一個名為 Method1 的 Web 方法。Method1 只返回包含傳遞給該方法的輸入?yún)?shù)的字符串。為了向該系統(tǒng)添加一定程度的延遲,Method1 在返回字符串之前還休眠了 3 秒鐘。從調(diào)用返回到 Method1 的字符串被放在 ASPX 頁面上的兩個標簽的文本中。該頁面提供的性能極差,并且像一塊海綿一樣從進程線程池中吸取線程。由于在 Method1 Web 方法中有 3 秒鐘的延遲,對該頁面的一個調(diào)用至少要 6 秒鐘才能完成。
以下代碼片段顯示了一個類似 Web 頁面的代碼,只不過現(xiàn)在進行的是異步 Web 服務(wù)調(diào)用。
Public Class AsyncPage
Inherits System.Web.UI.Page
Protected WithEvents Label1 As System.Web.UI.WebControls.Label
Protected WithEvents Label2 As System.Web.UI.WebControls.Label
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'調(diào)用 Web 服務(wù)
Dim proxy As New localhost.Service1
Dim res As IAsyncResult
= proxy.BeginMethod1(500, Nothing, Nothing)
Dim res2 As IAsyncResult
= proxy.BeginMethod1(200, Nothing, Nothing)
Label1.Text = proxy.EndMethod1(res)
Label2.Text = proxy.EndMethod1(res2)
End Sub
End Class
同樣,該頁面將創(chuàng)建一個 Web 服務(wù)代理,然后兩次調(diào)用 Method1 Web 方法。不同的是,現(xiàn)在調(diào)用的是 BeginMethod1,而不是直接調(diào)用 Method1。BeginMethod1 調(diào)用將立即返回,這樣我們就可以開始第二次調(diào)用該方法。與第一個示例中等待第一個 Web 服務(wù)調(diào)用完成不同,現(xiàn)在我們可以同時開始這兩個調(diào)用。對 EndMethod1 的調(diào)用只是在特定的調(diào)用完成前會造成阻塞。
值得注意的是,當我們從 ASPX 頁面返回后,響應(yīng)將發(fā)送給客戶端。因此,在獲得所需的數(shù)據(jù)之前,我們無法從 Page_Load 方法返回。這就是我們要阻塞 Web 服務(wù)調(diào)用直至其完成的原因。好的方面是兩個調(diào)用可以同時執(zhí)行,因此先前 6 秒鐘的延遲現(xiàn)在將降到 3 秒鐘左右。這雖然好一些,但仍然創(chuàng)建了阻塞的線程。我們真正需要的是在完成 Web 服務(wù)調(diào)用的同時,能夠釋放線程以便其處理 HTTP 請求。問題在于,ASPX 頁面的處理模型沒有一個異步執(zhí)行模式。不過,ASP.NET 確實提供了一個解決此問題的方法。
異步 PreRequestHandler 執(zhí)行
ASP.NET 支持稱為 HttpHandlers 的類。HttpHandlers 是實現(xiàn) IHttpHandler 接口的類,用于為帶有特定擴展名的文件的 HTTP 請求提供服務(wù)。例如,如果查看一下 Machine.config 文件,您將注意到,有許多 HttpHandlers 服務(wù)于帶有擴展名(如 .asmx、.aspx、.ashx 甚至 .config)的文件的請求。對于帶有特定擴展名的文件的請求,ASP.NET 將查看其配置信息,然后調(diào)用與其相關(guān)聯(lián)的 HttpHandler 為該請求提供服務(wù)。
ASP.NET 還支持寫事件處理程序,在處理 Http 請求過程中的各個時候都可以發(fā)生這類事件。其中一個事件是 PreRequestHandlerExecute 事件,它恰好發(fā)生在某個特定請求的 HttpHandler 被調(diào)用之前。還有一個對 PreRequestHandlerExecute 通知的異步支持,可以注冊這些通知以使用 HttpApplication 類的 AddOnPreRequestHandlerExecuteAsync 方法。HttpApplication 類源自基于 Global.asax 文件創(chuàng)建的事件處理程序。我們將使用異步 PreRequestHandler 選項為 Web 服務(wù)調(diào)用提供異步執(zhí)行模式。
在調(diào)用 AddOnPreRequestHandlerExecuteAsync 之前要做的第一件事是創(chuàng)建一個 BeginEventHandler 和一個 EndEventHandler 函數(shù)。請求傳入后,將調(diào)用 BeginEventHandler 函數(shù)。我們將在此時開始異步 Web 服務(wù)調(diào)用。BeginEventHandler 必須返回一個 IAsyncResult 接口。如果您正在進行一個 Web 服務(wù)調(diào)用,則可以只返回由 Web 服務(wù) begin 函數(shù)返回的 IAsyncResult 接口(在我們的示例中,將由 BeginMethod1 方法返回一個 IAsyncResult 接口)。在我創(chuàng)建的示例中,我想執(zhí)行與前面的 Web 頁面示例(其中揭示了同步和異步 Web 服務(wù)調(diào)用)相同的操作。這就意味著我必須創(chuàng)建自己的 IAsyncResult 接口。我的 BeginEventHandler 代碼如下所示:
Public Function BeginPreRequestHandlerExecute(
ByVal sender As Object, _
ByVal e As EventArgs, _
ByVal cb As AsyncCallback, _
ByVal extraData As Object) As IAsyncResult
If Request.Url.AbsolutePath _
= "/WebApp/PreRequestHandlerPage.aspx" Then
Dim proxy As MyProxy = New MyProxy
proxy.Res = New MyAsyncResult
proxy.Res.result1
= proxy.BeginMethod1( _
500, _
New AsyncCallback(AddressOf MyCallback), _
proxy)
proxy.Res.result2
= proxy.BeginMethod1( _
300, _
New AsyncCallback(AddressOf MyCallback), _
proxy)
proxy.Res.Callback = cb
proxy.Res.State = extraData
proxy.Res.Proxy = proxy
Return proxy.Res
End If
Return New MyAsyncResult
End Function
關(guān)于此代碼還有許多有趣的事情值得注意。首先,針對此虛擬目錄處理的每個 HTTP 請求都將調(diào)用此代碼。因此,我做的第一件事就是檢查請求的實際路徑,查看它是否是我要為其提供服務(wù)的頁面的路徑。
我的函數(shù)使用了一些有趣的輸入?yún)?shù)來調(diào)用。cb 參數(shù)是 ASP.NET 傳遞給我的回調(diào)函數(shù)。ASP.NET 希望在我的異步工作完成后,可以調(diào)用由它提供給我的回調(diào)函數(shù)。它們就是通過這種方式知道何時調(diào)用我的 EndEventHandler。同樣,如果我只進行一個 Web 服務(wù)調(diào)用,則只需將回調(diào)傳遞給 BeginMethod1 調(diào)用,然后 Web 服務(wù)調(diào)用將負責調(diào)用函數(shù)。但在本例中,我進行了兩個單獨的調(diào)用。因此,我創(chuàng)建了一個傳遞給兩個 BeginMethod1 調(diào)用的中間回調(diào)函數(shù),并且在回調(diào)代碼中檢查兩個調(diào)用是否都已完成。如果沒完成,我將返回;如果已完成,我將調(diào)用原始的回調(diào)。另一個有趣的參數(shù)是 extraData 參數(shù),它在 ASP.NET 調(diào)用我時為 ASP.NET 保存了狀態(tài)。我在調(diào)用由 cb 參數(shù)指定的回調(diào)函數(shù)時必須返回該狀態(tài)信息,因此,我將其存儲在所創(chuàng)建的 IAsyncResult 類中。我的回調(diào)代碼如下所示:
Public Sub MyCallback(ByVal ar As IAsyncResult)
Dim proxy As MyProxy = ar.AsyncState
If proxy.Res.IsCompleted Then
proxy.Res.Callback.Invoke(proxy.Res)
End If
End Sub
還應(yīng)當提到的一點是,我創(chuàng)建的實現(xiàn) IAsyncResult 的類(稱為 MyAsyncResult)將在查詢 IsCompleted 屬性時檢查兩個掛起 Web 服務(wù)調(diào)用的完成情況。
在 EndEventHandler 中,我只是從 Web 服務(wù)調(diào)用獲取結(jié)果,然后將其存儲在當前的請求上下文中。該上下文與要傳遞給 HttpHandler 的上下文相同。在本例中,它是 .aspx 請求的處理程序,這樣它便可以用于我的標準代碼。我的 EndEventHandler 代碼如下所示:
Public Sub EndPreRequestHandlerExecute(ByVal ar As IAsyncResult)
If Request.Url.AbsolutePath _
= "/WebApp/PreRequestHandlerPage.aspx" Then
Dim res As MyAsyncResult = ar
Dim proxy As MyProxy = res.Proxy
Dim retString As String
retString = proxy.EndMethod1(proxy.Res.result1)
Context.Items.Add("WebServiceResult1", retString)
retString = proxy.EndMethod1(proxy.Res.result2)
Context.Items.Add("WebServiceResult2", retString)
End If
End Sub
由于已經(jīng)接收了 .aspx 頁面的數(shù)據(jù),因此實際的頁面處理也就非常簡單了。
Public Class PreRequestHandlerPage
Inherits System.Web.UI.Page
Protected WithEvents Label1 As System.Web.UI.WebControls.Label
Protected WithEvents Label2 As System.Web.UI.WebControls.Label
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Label1.Text = Context.Items("WebServiceResult1")
Label2.Text = Context.Items("WebServiceResult2")
End Sub
End Class
這不僅僅是理論 -- 它確實起作用!
如果不考慮我沒有阻塞了所有線程,至少也使得浪費的資源更少了,因而這還是有意義的。但實際的結(jié)果確實會有所不同嗎?答案是肯定的“是”!我把此專欄中介紹的三種測試情況放在了一起:從 Web 頁面代碼進行 2 個阻塞的調(diào)用,從 Web 頁面代碼進行 2 個異步調(diào)用,以及從 PreRequestHandler 代碼進行 2 個異步調(diào)用。我使用 Microsoft Application Center Test 對這三種情況進行了測試,在 60 秒鐘內(nèi)從 100 個虛擬客戶端連續(xù)發(fā)送請求。下圖顯示的結(jié)果表明了在 60 秒鐘內(nèi)完成的請求數(shù)。
圖 1:100 個同時進行請求的客戶端在 60 秒鐘內(nèi)完成的請求
異步 PreRequestHandler 方法處理的請求數(shù)大約是排在第二位的方法處理的請求數(shù)的 8 倍。因此,該方法使您可以處理更多請求,但是對于單個請求,實際要多長時間才能完成呢?下圖顯示了這三種方法的平均響應(yīng)時間。
圖 2:100 個同時進行請求的客戶端的平均完成響應(yīng)時間
使用 PreRequestHandler 方法的平均請求響應(yīng)時間僅為 3.2 秒。假設(shè)每個 Web 服務(wù)調(diào)用的內(nèi)置延遲為 3 秒鐘,則該方法是一種非常有效的解決辦法。
我必須指出,這些并非科學(xué)的數(shù)字是在我的并非科學(xué)的辦公室中運行的并非科學(xué)的計算機上獲得的。當然,如果將空閑的線程釋放出來,讓它們做一些實際的工作確實會改善性能,因而這也很有意義。希望這些結(jié)果能夠表明性能的改善其實是非常顯著的。
PreRequestHandler 方法是很必要的,因為 .aspx 請求的處理程序中沒有內(nèi)置異步請求處理機制。但并非所有 ASP.NET HTTP 處理程序都是這樣。PreRequestHandler 方法適用于所有 ASP.NET 請求類型,但使用將異步支持置于 .asmx 處理程序內(nèi)的編程方式要比使用 PreRequestHandler 編程方式更容易一些。
小結(jié)
無論何時遇到任何類型的進程耗時較長的性能問題,異步執(zhí)行模型都是一個很好的方法。在從 .aspx 頁面調(diào)用 Web 服務(wù)的情況下,我們認為可以將異步 Web 服務(wù)調(diào)用與 ASP.NET 提供的異步執(zhí)行模式結(jié)合起來。這解決了在處理 .aspx 請求的過程中缺乏異步支持的問題。使用此異步方法可以消除性能問題以及線程池資源的消耗問題。
下載本文相關(guān)源代碼