閱讀511 返回首頁    go 技術社區[雲棲]


ASP.NET HTTP運行時組成詳解

簡介

不管使用哪種底層平台,可靠性和性能都是對所有Web應用程序的主要要求,盡管從某種意義上講,這兩個要求是相互矛盾的。例如,要構建更可靠、更健壯的應用程序,可能需要將Web服務器與具體的應用程序分離,使應用程序在進程外工作。但是,如果在不同於Web服務器進程的內存環境中工作,應用程序將變慢。因此,需要采取合理的措施,以確保進程外代碼盡可能快地運行。

在構建Microsoft?ASP.NET運行時環境時,依據的設計原則即:充分考慮可靠性和性能。得到的ASP.NET進程模型包含了兩個係統元素-一個存在於Web服務器進程中的進程內連接器,一個外部的輔助進程。另外,ASP.NET運行時結構的可伸縮能力很強,可以自動使用多處理器硬件中任意選定的處理器。這種模式被稱為“WebGarden”,它可以使多個輔助進程同時運行,而且各個進程均在獨立的處理器中。

高度概括起來,ASP.NET運行時具有三大屬性:

應用程序和ASP.NET輔助進程之間完全分離。提供服務的輔助進程的壽命決不會影響應用程序的壽命。換句話說,當應用程序啟動並處於運行狀態時,輔助進程可以隨時終止。
盡管ASP.NET應用程序從不在Web服務器內采用進程內的方式運行,但大多數情況下,其總體性能仍接近於進程內應用程序的性能。

為WebGarden體係結構提供了內置的和可配置的支持。隻要簡單檢查一下配置文件中的設置,輔助進程就可以克隆自己,以利用所有與進程密切相關的CPU。因此,在大多數情況下,您在具備多處理器的計算機中獲得的可縮放性將呈線性增長的趨勢。(本文後麵將詳細介紹此內容。)

本文將介紹ASP.NET運行時環境的組成元素,然後一步一步地講述從URL請求變為純HTML文本的“漫長而曲折”的過程。

除非另有說明,否則以下介紹中均指ASP.NET的默認進程模型,即Microsoft?InternetInformationServices(IIS)5.x中唯一的模型。

ASP.NET結構的組件

執行ASP.NET應用程序需要宿主Web服務器的支持。在Microsoft?Windows?的Server平台中,Web服務器由名為inetinfo.exe的IIS可執行文件表示。Windows2000及以上版本的操作係統本身均提供了Web服務器。但需要注意,在Microsoft?WindowsServer™2003中,並未默認安裝IIS和ASP.NET,必須通過單擊“控製麵板”中的“添加或刪除程序”小程序將其添加到係統中。

IIS是一個未托管的可執行程序,它提供了一個基於ISAPI擴展模塊和篩選器模塊的可擴展模型。通過編寫此類模塊,開發人員可以直接管理對特定資源類型的請求,並在各個預定義的步驟中接收當前請求。擴展和篩選器是一些DLL,可以導出一些具有已知名稱和簽名的函數。這些插件組件是在IIS配置數據庫中注冊並配置的。
 
隻有少數幾種被客戶端請求的資源類型由IIS直接處理。例如,對HTML頁麵、文本文件、JPEG和GIF圖像的傳入請求由IIS處理。對ActiveServerPage(*.asp)文件的請求通過調用名為asp.dll的ASP專用擴展模塊進行解析。同樣,對ASP.NET資源(例如,*.aspx、*.asmx、*.ashx)的請求將傳遞到ASP.NETISAPI擴展。該係統組件是一個名為aspnet_isapi.dll的Win32DLL。ASP.NET擴展可以處理多種資源類型,包括Web服務和HTTP處理程序調用。

ASP.NETISAPI擴展是一個Win32DLL,未集成托管代碼。它是接收和分派對各種ASP.NET資源的請求的控製中心。按照設計,該模塊存在於IIS進程中,在具有管理員權限的SYSTEM帳戶下運行。開發人員和係統管理員不能修改此帳戶。ASP.NETISAPI擴展負責調用ASP.NET輔助進程(aspnet_wp.exe),而該進程又負責控製請求的執行。除了對請求進行安排以外,ASP.NETISAPI還監視輔助進程的運行情況,並在性能降低到一定程度時將進程取消。

輔助進程是一小段Win32shell代碼,集成了公共語言運行庫(CLR)並運行托管代碼。它負責處理對ASPX、ASMX和ASHX資源的請求。一般來說,此進程在一台給定的計算機中隻有一個實例。所有當前激活的ASP.NET應用程序均在其中運行,每個應用程序都位於一個獨立的AppDomain中。但是,如前所述,輔助進程支持WebGarden模式,即進程的相同副本都運行在與進程密切相關的CPU中。(更多內容,請參閱本文後麵的“WebGarden模型”部分。)

ISAPI和輔助進程之間的通訊是使用一組命名管道進行的。命名管道是一種Win32機製,用於跨進程邊界傳輸數據。顧名思義,命名管道的工作方式與管道相似:在一端輸入數據,在另一端輸出相同的數據。建立的管道既可以連接本地進程,也可以連接遠程計算機上運行的進程。對於本地進程間通訊,管道是Windows中的最有效、最靈活的工具。

為確保獲得最優性能,aspnet_isapi使用異步命名管道來將請求轉發給輔助進程並獲得響應。另一方麵,輔助進程在需要查詢有關IIS環境的信息(即服務器變量)時又使用同步管道。aspnet_isapi模塊創建固定數量的命名管道,並使用重疊的操作以通過小的線程池處理同一時間進行的連接。當通過管道進行的數據交換操作結束後,完成例程將斷開客戶端,並重新使用管道實例為新的客戶端服務。線程池和重疊操作均可以保證使ASP.NETISAPI的性能達到令人滿意的水平。但是,aspnet_isapi擴展決不會處理HTTP請求。

ASP.NET請求的處理邏輯可以概括為以下步驟:

當請求到達時,IIS檢查資源類型並調用ASP.NETISAPI擴展。如果啟用了默認的進程模型,aspnet_isapi會將請求排隊,並將請求分配給輔助進程。所有的請求數據都通過異步I/O發送。如果啟用了IIS6進程模型,請求將自動在輔助進程(w3wp.exe)中排隊,此輔助進程用於處理應用程序所屬的IIS應用程序池。IIS6輔助進程不了解ASP.NET和托管代碼的任何情況,它隻是處理*.aspx擴展並加載aspnet_isapi模塊。當ASP.NETISAPI在IIS6進程模型中運行時,它的工作方式有所不同,僅在w3wp.exe輔助進程的上下文中加載CLR。

收到請求後,ASP.NET輔助進程將通知ASP.NETISAPI,它將為請求服務。通知通過同步I/O實現。之所以使用同步模型,是因為請求隻有在ISAPI內部請求表中被標記為“executing”,輔助進程才能開始處理它。如果請求已經由特殊的輔助進程進行處理,則不能再將它指定到其他進程,除非原始進程已取消。
在輔助進程的上下文中執行請求。有時,輔助進程可能需要回調ISAPI以完成請求,也就是需要說枚舉服務器變量。這種情況下,輔助進程將使用同步管道,因為這樣可以保持請求處理邏輯的順序。

完成後,響應被發送到打開了異步管道的aspnet_isapi。現在,請求的狀態變為“Done”,之後將從請求表中被刪除。如果輔助進程崩潰,正在處理的所有請求仍將保持“executing”狀態並持續一段時間。如果aspnet_isapi檢測到輔助進程已取消,它將自動終止請求並釋放所有相關的IIS資源。

以上說明是指默認的ASP.NET進程模型,即在IIS5.x中運行的工作模型。IIS6(WindowsServer2003提供)的默認工作方式對ASP.NET進程模型也有影響。當集成在IIS6.0中時,ASP.NET1.1會自動調整自己的工作方式以適應宿主環境。這時,不再需要使用aspnet_wp輔助進程,machine.config文件中定義的某些配置參數也被忽略。從ASP.NET的角度來看,IIS6的最大改變是有關請求的一切都在aspnet_isapi的控製之下,且都處在w3wp.exe輔助進程的上下文中。輔助進程的帳戶是為Web應用程序所屬的應用程序池設置的帳戶。默認情況下,該帳戶是NETWORKSERVICE&#151,它是一個內置的弱帳戶,在功能上與ASPNET等價。

輔助進程受一個名為進程回收(Recycling)的功能的控製。進程回收具有aspnet_isapi功能,當現有進程消耗的內存太多、響應太慢或掛起時可以自動啟動新進程。出現這種情況時,新請求將由新實例處理,新實例從而變成新的活動進程。但是,指定給舊進程的所有請求仍保持掛起狀態。如果舊進程結束了掛起的請求並進入空閑狀態,該進程即終止。如果輔助進程崩潰,或者由於其他原因停止處理請求,則所有掛起的請求將被重新指定給新進程。

盡管ASP.NETISAPI和輔助進程是ASP.NET運行時結構的主要組成部分,但還有其他一些可執行文件也發揮著作用。下表列出了所有這些組件。

表1:構成ASP.NET運行時環境的可執行文件

  名稱   類型   帳戶   aspnet_isapi.dll   Win32DLL(ISAPI擴展)   LOCALSYSTEM   aspnet_wp.exe   Win32   EXEASPNET   aspnet_filter.dll   Win32DLL(ISAPI篩選器)   LOCALSYSTEM   aspnet_state.exe   Win32NT   ServiceASPNET

aspnet_filter.dll組件是一個小的Win32ISAPI篩選器,用來備份ASP.NET應用程序的無Cookie會話狀態。在WindowsServer2003中,當啟用IIS6進程模型時,aspnet_filter.dll還將篩選出Bin目錄中對非可執行資源的請求。

aspnet_state.exe的作用對Web應用程序更為重要,因為它用於管理會話狀態。該項服務是可選的,可以用來在Web應用程序內存空間之外保存會話狀態數據。該可執行文件是一種NT服務,既可以在本地運行,也可以遠程運行。當該服務被激活後,可以將ASP.NET應用程序配置為將所有會話信息保存在此進程的內存中。一種類似的方案是提供更為可靠的數據存儲方式,不受進程回收和ASP.NET應用程序故障的影響。該服務在ASPNET本地帳戶下運行,但可以使用服務控製管理器(ServiceControlManager)接口來配置它。

另一個應該介紹的可執行文件是aspnet_regiis.exe,盡管嚴格來講,它並不屬於ASP.NET運行時結構。該實用程序可以用來配置環境,以在一台計算機上並行執行不同版本的ASP.NET,還可用於維修IIS和ASP.NET損壞的配置。該實用程序的工作方式是更新存儲在IIS配置數據庫的根目錄和子目錄中的腳本映射。腳本映射是資源類型和ASP.NET模塊之間的一種關聯關係。最後,還可以使用該工具來顯示已安裝的ASP.NET版本的狀態,執行其他配置操作,如授予對特定文件夾的NTFS權限、創建客戶腳本目錄。

WebGarden模型

WebGarden模型可以通過machine.config文件中的<processModel>部分進行配置。請注意,<processModel>部分是唯一不能放在應用程序特定的web.config文件中的配置部分。這就是說,WebGarden模式可以應用到計算機中運行的所有應用程序。但通過使用machine.config源文件中的<location>節點,可以針對各個應用程序調節計算機的設置。

<processModel>部分有兩個屬性可以影響WebGarden模型,它們是webGarden和cpuMask。webGarden屬性接受布爾值,表示是否使用了多個輔助進程(一個相關的CPU對應一個進程)。默認情況下,該屬性的值為false。cpuMask屬性保存一個DWORD值,該值的二進製表示為能夠運行ASP.NET輔助進程的CPU提供了位屏蔽。其默認值為-1(0xFFFFFF),表示可以使用所有可用的CPU。如果webGarden屬性為false,則cpuMask屬性的內容將被忽略。cpuMask屬性還為正在運行的aspnet_wp.exe的副本數設置了上限。

常言道“閃光的不都是金子”,用在這裏很合適。WebGarden模式使得多個輔助進程可以同時運行。但是,需要注意的是所有進程都會有自己的應用程序狀態、進程內會話狀態、ASP.NET緩存、靜態數據以及運行應用程序所需的其他內容。啟用WebGarden模式之後,ASP.NETISAPI將根據CPU的數量盡可能多地啟動輔助進程,每個輔助進程都是下一進程的完整克隆(每一進程都與相應的CPU密切相關)。為平衡工作負荷,傳入的請求以單循環的方式在運行的進程之間進行劃分。輔助進程就象在單處理器中一樣被回收。請注意,ASP.NET繼承了操作係統中所有的CPU使用限製,並且不包括實現限製的自定義語義。

總之,WebGarden模型並不適用於所有應用程序。應用程序的狀態越多,其的性能損失也越多。工作數據存儲在共享內存的塊中,以便一個進程輸入的變化可以立即被其他進程得知。但是,處理請求時,工作數據被複製到進程的上下文中。因此,各個輔助進程將處理自己的工作數據,而應用程序的狀態越多,性能損失就越大。鑒於此,仔細、明智的應用程序基準測試是絕對必要的。

隻有重啟IIS後,對配置文件中<processModel>部分所做的更改才會生效。在IIS6中,WebGarden模式的參數保存在IIS配置數據庫中,webGarden和cpuMask屬性被忽略。

HTTP管道

ASP.NETISAPI擴展啟動輔助進程後,它將傳遞部分命令行參數。輔助進程使用這些參數來執行加載CLR前需要執行的任務。傳遞的值包括:COM和DCOM安全性所要求的身份驗證等級、可以使用的命名管道的數量和IIS進程標識。命名管道的名稱是使用IIS進程標識和允許的管道數隨機生成的。輔助進程不接收可用管道的名稱,但可以接收識別管道名稱所需的信息。

COM和DCOM安全性與Microsoft?.NETFramework有何關係?實際上,CLR是作為COM對象提供的。更準確地說,CLR本身不是由COM代碼構成的,但是指向CLR的接口卻是一個COM對象。因此,輔助進程加載CLR的方式與加載COM對象的方式相同。

當ASPX請求遇到IIS時,Web服務器將根據選擇的身份驗證模型(匿名、Windows、Basic或Digest)來分配一個令牌。當輔助進程收到要處理的請求時,令牌被傳遞到輔助進程。請求由輔助進程中的線程獲取。該線程從最初獲取傳入請求的IIS線程繼承身份令牌。在aspnet_wp.exe中,負責處理請求的實際帳戶取決於在特殊的ASP.NET應用程序中是如何配置模擬的。如果模擬被禁用(默認設置),則線程將在輔助進程的帳戶下運行。默認情況下,該帳戶在ASP.NET進程模型中為ASPNET,在IIS6進程模型中為NETWORKSERVICE。這兩個帳戶都是“弱”帳戶,提供的功能比較有限,可以有效抵擋回複性攻擊(Revert-to-selfAttack)。(回複性攻擊是指將模擬的客戶端的安全性令牌回複到父進程令牌。為輔助進程分配弱帳戶可以挫敗此類攻擊。)

高度概括起來,ASP.NET輔助進程完成的一項主要任務就是將請求交給一係列稱為的HTTP管道的托管對象。要激活HTTP管道,可以創建一個HttpRuntime類的新實例,然後調用其ProcessRequest方法。如前所述,ASP.NET中始終隻運行一個輔助進程(除非啟用了WebGarden模型),該進程在獨立的AppDomain中管理所有的Web應用程序。每個AppDomain都有自己的HttpRuntime類實例,即管道中的輸入點。HttpRuntime對象初始化一係列有助於實現請求的內部對象。Helper對象包括緩存管理器(Cache對象)和內部文件係統監視器(用於檢測構成應用程序的源文件的更改)。HttpRuntime為請求創建上下文,並用與請求相關的HTTP信息填充上下文。上下文用HttpContext類的實例來表示。

另一個在HTTP運行時的設置初期創建的Helper對象是文本書寫器,用於包含瀏覽器的響應文本。文本書寫器是HttpWriter類的實例,此對象對頁麵代碼以編程方式發送的文本進行緩存。HTTP運行時被初始化後,它將查找實現請求的應用程序對象。應用程序對象是HttpApplication類的實例,該類就是global.asax文件背後的類。global.asax在編程時是可選的,但在構建結構時是必需的。因此,如果應用程序中沒有構建類,則必須使用默認對象。ASP.NET運行時包括幾個中間工廠類,可以用來查找並返回有效的Handler對象以處理請求。整個過程中用到的第一個工廠類是HttpApplicationFactory。它的主要任務是使用URL信息來查找URL虛擬目錄和匯集的HttpApplication對象之間的匹配關係。

應用程序工廠類的行為可以概括為以下幾點:

工廠類維護HttpApplication對象池,並使用它們來處理應用程序的請求。池的壽命與應用程序的壽命相同。

應用程序的第一個請求到達時,工廠類提取有關應用程序類型的信息(global.asax類)、設置用於監視更改的文件、創建應用程序狀態並觸發Application_OnStart事件。

工廠類從池中獲取一個HttpApplication實例,並將要處理的請求放入實例中。如果沒有可用的對象,則創建一個新的HttpApplication對象。要創建HttpApplication對象,需要先完成global.asax應用程序文件的編譯。

HttpApplication開始處理請求,並且隻能在完成這個請求後才能處理新的請求。如果收到來自同一資源的新請求,則由池中的其他對象來處理。

應用程序對象允許所有注冊的HTTP模塊對請求進行預處理,並找出最適合處理請求的處理程序類型。這通過查找請求的URL的擴展和配置文件中的信息來完成。

HTTP處理程序是一些實現IHttpHandler接口的類。.NETFramework為常見的資源類型提供了一些預定義的處理程序,包括ASPX頁麵和Web服務。machine.config文件中的<httpHandlers>部分定義了HttpApplication對象必須實例化才能處理特定類型資源的請求的類名。如果Helper類是一個處理程序工廠,GetHandler方法將確定要使用的處理程序類型。這時,將從一組類似的對象中獲取適當類型的處理程序,並對其進行配置以處理請求。

IHttpHandler接口提供了兩個方法:IsReusable和ProcessRequest。前者將返回一個布爾值,表示處理程序是否可以被匯集。(大多數預定義的處理程序都是匯集的,但是您可以自行定義每次都需要新實例的處理程序。)ProcessRequest方法包含處理特定類型資源所需的所有邏輯。例如,ASPX頁麵的處理程序基於以下偽代碼:


privatevoidProcessRequest()
{
//確定請求是否是回發(postback)
IsPostBack=DeterminePostBackMode();

//觸發ASPX源代碼的Page_Init事件
PageInit();

//加載ViewState,處理已發送的值。
if(IsPostBack){
LoadPageViewState();
ProcessPostData();
}

//觸發ASPX源代碼的Page_Load事件
PageLoad();

//1)再次處理已發送的值(當
//動態創建控件時)
//2)將屬性更改的服務器端事件提升為輸入驅動的
//控件(即複選框的狀態改變)
//3)執行與回發事件相關的所有代碼
if(IsPostBack){
ProcessPostDataSecondTry();
RaiseChangedEvents();
RaisePostBackEvent();
}

//觸發ASPX源代碼的Page_PreRender事件
PreRender();

//將控件的當前狀態保存到ViewState中
SavePageViewState();

//將頁麵內容呈現給HTML
RenderControl(CreateHtmlTextWriter(Response.Output));
}

無論調用的資源類型如何,基於HTTP處理程序的模型是相同的。唯一隨資源類型變化而變化的元素是處理程序。HttpApplication對象負責查找應該使用哪種處理程序來處理請求。HttpApplication對象還負責檢測對動態創建的、表示資源的程序集(如.aspx頁麵或.asmxWeb服務)所進行的更改。如果檢測到更改,應用程序對象將確保編譯並加載所請求的資源的最新來源。

臨時文件和頁麵程序集

要全麵了解ASP.NETHTTP運行時,讓我們來分析一下當請求ASP.NET頁麵時,文件係統層所發生的變化。接下來,您將了解由HTTP管道的對象管理和監視的一組動態創建的臨時文件。

雖然可以將頁麵的核心代碼隔離在代碼背後的C#或Microsoft?VisualBasic?.NET類中,但可以將Web頁麵編寫和部署為.aspx文本文件。對於要顯示為URL的頁麵來說,.aspx文件在應用程序的Web空間中必須始終可用。.aspx文件的實際內容將確定應用程序對象要加載的程序集(或多個程序集)。

按照設計,HttpApplication對象將查找一個根據請求的ASPX文件命名的類。如果頁麵命名為sample.aspx,則要加載的相應的類名為ASP.sample_aspx。應用程序對象在Web應用程序的所有程序集文件夾中查找這樣的類,這些文件夾包括全局程序集緩存(GAC)、Bin子文件夾和TemporaryASP.NETFiles文件夾。如果未找到這樣的類,HTTP結構將分析.aspx文件的源代碼,創建一個C#或VisualBasic.NET類(具體創建哪種類,取決於.aspx頁麵上設置的語言),同時對其進行編譯。新創建的程序集的名稱是隨機生成的,位於特定於應用程序的子文件夾中,路徑如下所示:C:/WINDOWS/Microsoft.NET/Framework/v1.1.4322/TemporaryASP.NETFiles。

子文件夾v1.1.4322特定於ASP.NET1.1。如果您使用的是ASP.NET1.0,子文件夾的版本號會有所不同,即子文件夾名為v1.0.3705。再次訪問頁麵時,程序集就已存在,不需要重新創建。但是,HttpApplication對象是如何確定特定於頁麵的程序集是否存在呢?它每次都要掃描大量文件夾嗎?不,並不是這樣。

應用程序對象隻查看TemporaryASP.NETFiles文件夾中某個特殊文件夾的內容。具體路徑(特定於應用程序的路徑)由HttpRuntime.CodegenDir屬性返回。如果是第一次訪問.aspx文件(即還未創建頁麵程序集),則該文件夾中就不存在以ASPX頁麵名稱開頭的XML文件。例如,具有動態程序集的sample.aspx頁麵應有如下的條目:

sample.aspx.XXXXX.xml

XXXXX占位符是一種散列代碼。通過讀取該XML文件的內容,應用程序對象就可以了解要加載的程序集的名稱以及要在其中獲取的類。以下代碼片段是這種Helper文件的典型內容。包含ASP.sample_aspx類的程序集的名稱是mvxvx8xr。


<preserveassem="mvxvx8xr"type="ASP.sample_aspx">
<filedepname="c:/inetpub/wwwroot/vdir/sample.aspx"/>
</preserve>

當然,隻有在分析filedep文件的源代碼以生成動態程序集時才創建該文件。對filedep文件所做的任何更改都會使程序集無效,在下一次請求時必須重新編譯。需要注意的是,在ASP.NET架構的未來版本中,該實現過程可能會有較大改變。不論什麼原因,隻要您決定在當前應用程序中使用它,都必須十分小心。

由於更新而要為頁麵創建新的程序集時,ASP.NET將驗證是否可以刪除舊的程序集。如果舊的程序集隻包含修改後的頁麵的類,ASP.NET將試圖刪除並替換該程序集,否則將在保留舊程序集的情況下創建一個新程序集。

在刪除過程中,ASP.NET可能會發現程序集文件已被加載並鎖定。這種情況下,可以為舊程序集添加一個“.DELETE”擴展名,以將其重新命名。(注意,所有Windows文件都可以在使用過程中重新命名。)隻要應用程序重新啟動(例如,由於對某個應用程序文件如global.asax和web.config進行了更改),這些臨時的.DELETE文件就將被刪除。但在處理下一個請求時,ASP.NET運行時不會刪除這些文件。

請注意,默認情況下,在整個應用程序重新啟動之前,每個ASP.NET應用程序最多可以重新編譯15個頁麵,同時會損失一些會話和應用程序數據。當最近的編譯次數超過了<httpRuntime>部分的numRecompilesBeforeAppRestart屬性中設置的閾值時,將卸載AppDomain,並重新啟動應用程序。還要注意,在.NETFramework中,您無法卸載單個程序集。AppDomain是可以從CLR卸載的最小的代碼塊。

小結

ASP.NET應用程序有兩大特征:進程模型和頁麵對象模型。ASP.NET提前使用了IIS6.0的一些功能,而IIS6.0則是WindowsServer2003中提供的全新的、開創性的MicrosoftWeb信息服務。尤其值得一提的是,在獨立的輔助進程中運行的ASP.NET應用程序,其行為與IIS6中的所有應用程序相同。而且,盡管會出現運行時異常、內存泄露或程序錯誤,ASP.NET運行時仍能自動回收輔助進程以保證實現卓越的性能。這種功能已成為IIS6.0的係統功能。

在本文中,我概括介紹了默認的ASP.NET進程模型的基礎知識,以及IIS級代碼(ASP.NETISAPI擴展)和輔助進程之間的交互。同時,還介紹了與IIS6進程模型之間的最新區別。

最後更新:2017-04-02 00:06:33

  上一篇:go ASP.NET中編程殺死進程
  下一篇:go Asp.net+Xml實現無數據庫論壇一點即通