天天看點

ASPX頁Web服務調用性能優化

摘要:本文介紹了如何通過異步方法消除使用microsoftasp.net的web服務調用的性能問題和線程池資源的消耗問題。

情況:從asp.net頁面調用web服務時的性能破壞

我們在本文中讨論web服務時,期望在各種情況下都可以享用web服務。一個主要的情況是從中間層環境(如asp.netweb頁面)通路web服務。為mappoint.netweb服務的使用者提供支援的人員經常收到這樣的問題,即使用者在使用其web服務時,對mappoint.net的調用可能需要相當長的時間。這本身并不是什麼問題,但某些其他因素可以使之成為比表面上要嚴重得多的大問題。

http雙連接配接限制

http規範表明,一個http用戶端與任一伺服器最多可以同時建立兩個tcp連接配接。這可以防止單個浏覽器在浏覽某個頁面(例如,具有120個嵌入的縮略圖)時,由于連接配接請求過多而使伺服器負載過重。此時,浏覽器将僅建立2個連接配接,然後通過這兩個管道開始發送120個http請求,而不是建立120個tcp連接配接并通過每個連接配接來發送http請求。對于中間層,此方法的問題在于,中間層可能會有50個同時請求連接配接的使用者。如果不得不為每個使用者進行一次mappoint.netweb服務調用,将會有48個使用者等待兩個管道中的一個空閑下來。

線程池限制

asp.net處理傳入的請求的方式是通過一個稱為程序線程池的一組線程為其提供服務。正常情況下,請求傳入後,池中某個空閑的線程将為其提供服務。這裡的問題在于,程序線程池不會建立無數個線程來處理大量的請求。具有最大線程數限制是一件好事,因為如果我們無限地建立線程,計算機上的全部資源将隻能用來管理這些線程了。通過限制所能建立的線程數,我們可以把線程管理的系統開銷保持在一個可控的水準。如果某個請求傳入時線程池中的所有線程都被占用,則該請求将排隊等候,在忙線程完成任務後,空閑出來的線程才能處理新請求。此方法實際上比切換到某個新線程更有效,因為不需要在請求之間進行線程切換。但存在的問題是,如果線程的使用效率不高(尤其是在非常忙的web伺服器上),則等候的請求隊列會變得很大。

考慮一下從asp.net頁面進行web服務調用的情況。如果進行同步調用,則正在運作的線程将被阻塞,直到web服務調用完成為止。在調用期間,線程無法進行任何其他活動。它無法處理其他請求,隻能等待。如果某個單處理器計算機上具有預設的工作線程數20,則隻需20個同時進行的請求即可用完全部線程,以後的請求必須排隊等候。

該問題不僅限于web服務

不僅調用web服務的使用者會遇到從web頁面進行調用時的擁堵且耗時較長的問題。進行任意數量的較長的調用都會遇到同樣的問題,例如:sqlserver?請求、長檔案的讀取或寫入、各種web請求或通路某個并發資源(其中鎖定會造成嚴重的延遲)。實際上,有許多使用web服務的情況,其服務調用比較迅速,并不是什麼問題。但您或許會了解,如果您想通過代理伺服器調用mappoint.netweb服務,所使用的連接配接具有一定的延遲,同時相應的服務可能又要花費一些時間來處理請求,則您可能在各處位置都看到延遲的情況,并且如果站點很忙,便可能出現問題。

改善問題

 

該問題的某些方面可以通過對環境進行某些配置設定來改善。我們看一下可用于改善該問題的某些配置設定。

maxconnections

連接配接到web資源的預設雙連接配接限制可以通過一個名為connectionmanagement的配置元素來控制。connectionmanagement設定允許您添加要讓其采用非預設連接配接限制的站點的名稱。可以将以下内容添加到典型的web.config檔案中,将您連接配接的所有伺服器的連接配接限制預設值增加到40。

<configuration>

<system.net>

<connectionmanagement>

<addaddress="*"maxconnection="40"/>

</connectionmanagement>

</system.net>

<system.web>

...

應當注意的是,對本地計算機的連接配接數量從來都沒有限制,是以,如果是連接配接到本地主機,則此設定無效。

maxworkerthreads和minfreethreads

如果收到http503錯誤(“服務暫時過載”),則表明線程池中的線程已全部占用,并且請求隊列也已超出最大值(apprequestqueuelimit的預設設定為100)。對于iis5.0安裝,可以簡單地增加線程池的大小。而對于iis6.0安裝(與iis5.0不相容),這些設定将無效。

maxworkerthreads和maxiothreads分别控制工作線程數以及處理新送出的asp.net請求的線程數。這些設定需要在您的machine.config中進行配置,它們将影響您計算機上運作的所有web應用程式。maxworkerthreads是machine.config中的processmodel元素的一部分,并且您在檢視後會發現,該設定的預設值為每個處理器20個線程。

minfreethreads設定可以在machine.config中進行配置,或者在您的應用程式的web.config檔案中的httpruntime元素下進行配置。該設定的作用是,當空閑的線程數低于所設定的限制時,将禁止使用線程池中的線程來處理傳入的http請求。如果您需要某個程序線程池線程完成挂起的請求,這會很有用。如果所有的線程都被用來處理傳入的http請求,并且這些請求在等待另一個線程完成其處理,那麼就會進入死鎖狀态。例如,如果您正在從asp.net應用程式進行對某個web服務的異步web服務調用,并且在等待回調函數完成該請求,就會出現這種情況。因為回調必須在程序線程池中的空閑線程上進行。如果檢視一下您的machine.config,将會注意到minfreethreads設定的預設值為8,如果工作線程池的限制為20,則該預設值還可以滿足需要,但是,如果線程池的大小增加到100,該預設值就太小了。

應當注意的是,如果您的asp.net應用程式對本地計算機進行web服務調用,則線程池限制的問題将被激化。例如,我為此專欄建立的測試應用程式調用與aspx頁面同處一台計算機上的web服務。因而,對于阻塞的調用,一個線程被同時用于aspx頁面和asmxweb服務請求。這有效地使web伺服器處理的同時請求數增加了一倍。在同時進行兩個web服務請求(使用異步web服務調用)的情況下,我們最終使同時進行的請求數增加了兩倍。為避免在回調本地計算機時出現此類問題,您應當考慮您的應用程式的體系結構,使其簡單地直接從aspx代碼來執行web方法中的代碼。

windowsxp限制

我們必須要注意,如果您在一個windows?xp計算機上進行某項測試,則所面臨的另一個限制是xpweb伺服器對所允許的同時連接配接數的人為限制。因為windowsxp不是伺服器平台,其同時連接配接數被限制為10。這對于開發環境中的測試通常沒問題,但是如果試圖進行任何複雜的測試,該限制問題就會比較嚴重。本地計算機的連接配接不受此限制影響。

真正的解決方案:異步請求處理

調整配置設定是一種改善問題的方法,而在實際設計web應用程式時通過某種方式徹底解決問題則是另一回事。等待阻塞的調用完成的線程永遠也不會有更好的調整餘地,是以,解決的辦法是完全避免阻塞問題。異步處理請求就是一個适當的解決方案。這表現在兩個方面:進行異步web服務調用,以及在asp.netweb應用程式中異步處理請求。

異步web服務調用

在以前的專欄中,我寫了有關異步調用web服務的問題。能夠使線程不用等待web服務調用完成是建立釋放線程以便處理更多請求的異步頁面處理模型的關鍵部分。此外,異步調用web服務也比較簡單。

請考慮以下aspx頁面的visualbasic.net代碼:

'錯用同步web服務調用所造成的性能極差的

'頁面!

publicclasssyncpage

inheritssystem.web.ui.page

protectedwitheventslabel1assystem.web.ui.webcontrols.label

protectedwitheventslabel2assystem.web.ui.webcontrols.label

privatesubpage_load(byvalsenderassystem.object,_

byvaleassystem.eventargs)handlesmybase.load

'調用web服務

dimproxyasnewlocalhost.service1

label1.text=proxy.method1(500)

label2.text=proxy.method1(200)

endsub

endclass

此代碼非常易懂。頁面加載時将建立一個web服務代理執行個體,然後用該執行個體兩次調用一個名為method1的web方法。method1隻傳回包含傳遞給該方法的輸入參數的字元串。為了向該系統添加一定程度的延遲,method1在傳回字元串之前還休眠了3秒鐘。從調用傳回到method1的字元串被放在aspx頁面上的兩個标簽的文本中。該頁面提供的性能極差,并且像一塊海綿一樣從程序線程池中吸取線程。由于在method1web方法中有3秒鐘的延遲,對該頁面的一個調用至少要6秒鐘才能完成。

以下代碼片段顯示了一個類似web頁面的代碼,隻不過現在進行的是異步web服務調用。

publicclassasyncpage

dimresasiasyncresult

=proxy.beginmethod1(500,nothing,nothing)

dimres2asiasyncresult

=proxy.beginmethod1(200,nothing,nothing)

label1.text=proxy.endmethod1(res)

label2.text=proxy.endmethod1(res2)

同樣,該頁面将建立一個web服務代理,然後兩次調用method1web方法。不同的是,現在調用的是beginmethod1,而不是直接調用method1。beginmethod1調用将立即傳回,這樣我們就可以開始第二次調用該方法。與第一個示例中等待第一個web服務調用完成不同,現在我們可以同時開始這兩個調用。對endmethod1的調用隻是在特定的調用完成前會造成阻塞。

值得注意的是,當我們從aspx頁面傳回後,響應将發送給用戶端。是以,在獲得所需的資料之前,我們無法從page_load方法傳回。這就是我們要阻塞web服務調用直至其完成的原因。好的方面是兩個調用可以同時執行,是以先前6秒鐘的延遲現在将降到3秒鐘左右。這雖然好一些,但仍然建立了阻塞的線程。我們真正需要的是在完成web服務調用的同時,能夠釋放線程以便其處理http請求。問題在于,aspx頁面的處理模型沒有一個異步執行模式。不過,asp.net确實提供了一個解決此問題的方法。

異步prerequesthandler執行

asp.net支援稱為httphandlers的類。httphandlers是實作ihttphandler接口的類,用于為帶有特定擴充名的檔案的http請求提供服務。例如,如果檢視一下machine.config檔案,您将注意到,有許多httphandlers服務于帶有擴充名(如.asmx、.aspx、.ashx甚至.config)的檔案的請求。對于帶有特定擴充名的檔案的請求,asp.net将檢視其配置資訊,然後調用與其相關聯的httphandler為該請求提供服務。

asp.net還支援寫事件處理程式,在處理http請求過程中的各個時候都可以發生這類事件。其中一個事件是prerequesthandlerexecute事件,它恰好發生在某個特定請求的httphandler被調用之前。還有一個對prerequesthandlerexecute通知的異步支援,可以注冊這些通知以使用httpapplication類的addonprerequesthandlerexecuteasync方法。httpapplication類源自基于global.asax檔案建立的事件處理程式。我們将使用異步prerequesthandler選項為web服務調用提供異步執行模式。

在調用addonprerequesthandlerexecuteasync之前要做的第一件事是建立一個begineventhandler和一個endeventhandler函數。請求傳入後,将調用begineventhandler函數。我們将在此時開始異步web服務調用。begineventhandler必須傳回一個iasyncresult接口。如果您正在進行一個web服務調用,則可以隻傳回由web服務begin函數傳回的iasyncresult接口(在我們的示例中,将由beginmethod1方法傳回一個iasyncresult接口)。在我建立的示例中,我想執行與前面的web頁面示例(其中揭示了同步和異步web服務調用)相同的操作。這就意味着我必須建立自己的iasyncresult接口。我的begineventhandler代碼如下所示:

publicfunctionbeginprerequesthandlerexecute(

byvalsenderasobject,_

byvaleaseventargs,_

byvalcbasasynccallback,_

byvalextradataasobject)asiasyncresult

ifrequest.url.absolutepath_

="/webapp/prerequesthandlerpage.aspx"then

dimproxyasmyproxy=newmyproxy

proxy.res=newmyasyncresult

proxy.res.result1

=proxy.beginmethod1(_

500,_

newasynccallback(addressofmycallback),_

proxy)

proxy.res.result2

300,_

proxy.res.callback=cb

proxy.res.state=extradata

proxy.res.proxy=proxy

returnproxy.res

endif

returnnewmyasyncresult

endfunction

關于此代碼還有許多有趣的事情值得注意。首先,針對此虛拟目錄處理的每個http請求都将調用此代碼。是以,我做的第一件事就是檢查請求的實際路徑,檢視它是否是我要為其提供服務的頁面的路徑。

我的函數使用了一些有趣的輸入參數來調用。cb參數是asp.net傳遞給我的回調函數。asp.net希望在我的異步工作完成後,可以調用由它提供給我的回調函數。它們就是通過這種方式知道何時調用我的endeventhandler。同樣,如果我隻進行一個web服務調用,則隻需将回調傳遞給beginmethod1調用,然後web服務調用将負責調用函數。但在本例中,我進行了兩個單獨的調用。是以,我建立了一個傳遞給兩個beginmethod1調用的中間回調函數,并且在回調代碼中檢查兩個調用是否都已完成。如果沒完成,我将傳回;如果已完成,我将調用原始的回調。另一個有趣的參數是extradata參數,它在asp.net調用我時為asp.net儲存了狀态。我在調用由cb參數指定的回調函數時必須傳回該狀态資訊,是以,我将其存儲在所建立的iasyncresult類中。我的回調代碼如下所示:

publicsubmycallback(byvalarasiasyncresult)

dimproxyasmyproxy=ar.asyncstate

ifproxy.res.iscompletedthen

proxy.res.callback.invoke(proxy.res)

還應當提到的一點是,我建立的實作iasyncresult的類(稱為myasyncresult)将在查詢iscompleted屬性時檢查兩個挂起web服務調用的完成情況。

在endeventhandler中,我隻是從web服務調用擷取結果,然後将其存儲在目前的請求上下文中。該上下文與要傳遞給httphandler的上下文相同。在本例中,它是.aspx請求的處理程式,這樣它便可以用于我的标準代碼。我的endeventhandler代碼如下所示:

publicsubendprerequesthandlerexecute(byvalarasiasyncresult)

dimresasmyasyncresult=ar

dimproxyasmyproxy=res.proxy

dimretstringasstring

retstring=proxy.endmethod1(proxy.res.result1)

context.items.add("webserviceresult1",retstring)

retstring=proxy.endmethod1(proxy.res.result2)

context.items.add("webserviceresult2",retstring)

由于已經接收了.aspx頁面的資料,是以實際的頁面處理也就非常簡單了。

publicclassprerequesthandlerpage

label1.text=context.items("webserviceresult1")

label2.text=context.items("webserviceresult2")

這不僅僅是理論--它确實起作用!

如果不考慮我沒有阻塞了所有線程,至少也使得浪費的資源更少了,因而這還是有意義的。但實際的結果确實會有所不同嗎?答案是肯定的“是”!我把此專欄中介紹的三種測試情況放在了一起:從web頁面代碼進行2個阻塞的調用,從web頁面代碼進行2個異步調用,以及從prerequesthandler代碼進行2個異步調用。我使用microsoftapplicationcentertest對這三種情況進行了測試,在60秒鐘内從100個虛拟用戶端連續發送請求。下圖顯示的結果表明了在60秒鐘内完成的請求數。

圖1:100個同時進行請求的用戶端在60秒鐘内完成的請求

異步prerequesthandler方法處理的請求數大約是排在第二位的方法處理的請求數的8倍。是以,該方法使您可以處理更多請求,但是對于單個請求,實際要多長時間才能完成呢?下圖顯示了這三種方法的平均響應時間。

圖2:100個同時進行請求的用戶端的平均完成響應時間

使用prerequesthandler方法的平均請求響應時間僅為3.2秒。假設每個web服務調用的内置延遲為3秒鐘,則該方法是一種非常有效的解決辦法。

我必須指出,這些并非科學的數字是在我的并非科學的辦公室中運作的并非科學的計算機上獲得的。當然,如果将空閑的線程釋放出來,讓它們做一些實際的工作确實會改善性能,因而這也很有意義。希望這些結果能夠表明性能的改善其實是非常顯著的。

prerequesthandler方法是很必要的,因為.aspx請求的處理程式中沒有内置異步請求處理機制。但并非所有asp.nethttp處理程式都是這樣。prerequesthandler方法适用于所有asp.net請求類型,但使用将異步支援置于.asmx處理程式内的程式設計方式要比使用prerequesthandler程式設計方式更容易一些。

<b>小結</b>

無論何時遇到任何類型的程序耗時較長的性能問題,異步執行模型都是一個很好的方法。在從.aspx頁面調用web服務的情況下,我們認為可以将異步web服務調用與asp.net提供的異步執行模式結合起來。這解決了在處理.aspx請求的過程中缺乏異步支援的問題。使用此異步方法可以消除性能問題以及線程池資源的消耗問題。