第一章 J2EEWeb服務客戶端質量報告
概要
本文實現了記錄J2EE(Java2平台企業版)Web服務的客戶端響應次數的一個通用的結構。記錄的響應次數是真實的客戶端響應次數,所以它們實際上反映了用戶對服務質量的看法。實驗的樣品是使用SunONE(開放式網絡環境)應用服務器和IDE建立起來的,但是這個方法很普通,很容易推廣到其它J2EE實現上。
Web服務正迅速的成為實現客戶端-服務器係統的首選結構。它的優點是:企業可以正式的定義一組服務,然後生成通訊用的完整的客戶端和服務器的代碼庫,從而簡化新的客戶端對合法的Web資源的訪問。
但是,Web服務在簡化客戶端-服務器係統的建立的同時,監控服務質量就變得很重要。假設有一個在用戶的立場上提交處理的客戶端應用程序。而企業事務通常要調用好幾個Web服務:初始調用遞交工作內容,接下來的調用檢查實現,最終調用得出結果。一個調用就是一個特殊的HTTP/SOAP(簡單對象訪問協議)交換。假設你是IT部門專門負責監控服務器裝載和預測未來需要的工作人員,你必須得回答這個基本問題:"我現在管理我的客戶端怎樣呢?對於將來的管理,我還需要什麼東西?"
如果你隻有HTTP日誌的話,就很難回答上述問題了。客戶端隻關心事務的處理,但是因為每個事務包括幾個HTTP請求,對於評估服務質量,你最多隻能開發自定義數據采集軟件,該軟件可通過HTTP日誌做出指示並建立用戶事務處理的模型。就算是這樣,你所擁有的信息仍然有限,因為它不能反映網絡傳送或者客戶端應用程序的內務操作。
本文的中心思想是:事務服務質量用客戶端評估最好。這兒采用的方法就是允許客戶端記錄實際的事務響應次數。客戶端應用程序通過將響應時間報告添加到下一個彈出的事務處理請求上,從而上傳響應時間報告給服務器。服務器取出這些附件並將他們排隊儲存和在線分析。
結構
基於客戶端的頻率記錄結構的目標就是:記錄用的下部構造必須是輕型的,它不但有利於實現運行的內務開銷還可以減輕添加它到一個現有的實現的負擔。我們也希望該結構對提供的服務沒有限製,我們很想可以將服務添加到一個現有的、可以盡可能容易地使用Web服務的客戶端-服務器係統上。
我們的結構的另一個目標是:企業應用程序自身的可靠性不要太差。我們將引入一些新的、容易做到的步驟到應用程序的處理工作流程中。而且我們可以保證這些新步驟中的任何故障都可以得到處理,我們不會因為不能將頻率用於程序就讓企業事務的處理失敗
下圖顯示了一個典型的J2EE(Java2平台企業版)Web服務的客戶端-服務器應用程序。典型的組件用粗線標明;我們添加的新組件用於收集頻率,它采用紅線標明。
J2EEWebservices:Metrics-gatheringarchitecture
"J2EEApplicationServer"區域表示現有的服務器資源。他們是用來處理客戶端請求的企業JavaBeans(EJB)組件。工具可自動的生成Web服務軟件包。EJB組件和相關的Web服務模塊被當作J2EE應用程序配置到J2EE服務器上。當應用程序配置時,客戶端可以通過調用程序WSDL(Web服務描述語言)服務來判斷一個服務。WSDL服務對程序提供的服務給出正式的定義。
"ApplicationClient"區域由程序組件和Web服務軟件包組成。程序組件實現商業邏輯和用戶界麵。Web服務軟件包可自動地通過WSDL編譯器和J2EE服務器程序提供的WSDL服務創建出來。
從理論上來說,整個客戶端-服務器係統有兩層。應用程序層在服務器端擁有EJB組件,在客戶端擁有一個應用程序。Web服務層有一個服務器實現和一個客戶端實現,兩者都可以自動產生。
典型地,用戶的商業事務處理包括許多個服務期調用。第一個調用初始化事務,返回一個"handle"給客戶端。後來的調用查詢事務的完成--客戶端使用句柄調用服務來檢查事務是否得到處理。通常最後一個調用可得到完成的事務的狀態。因此,一個商業模型,可在客戶端程序內實現的商業模型,總是使得事務與低級別的服務器調用聯係起來。
我們可以將收集頻率的組件添加到我們的標準J2EEWeb服務結構上。上圖中的Payload軟件包在服務器和客戶端都有配置。稍後我會詳細的討論這個軟件包,但是從結構的意義上來講,這個軟件包提供了幾個服務。例如:客戶端程序可以使用beginTransaction()和commitTransaction()來定界事務和記錄次數。客戶端Web服務軟件包使用Payload軟件包來連載次數報告給SOAP信息附件。服務器端的Web服務軟件包使用Payload軟件包將SOAP附件從引入的請求中剝離出來,並將它列隊登記和報告。
這個實現中的係統操作很少因為客戶端和服務器不交換任何新的通信--完成的事務的頻率報告與下一個客戶端請求一起運送。引入的唯一的新的處理就是客戶端上的一些連載和服務器端排隊等待的附件。整個實現很輕便,因為隻要添加一行代碼到每個程序Web方法上,並且代碼還是一樣的--如果Web方法的標記不變的話他也不會發生變化。
引入的最後一個組件就是信息驅動的EJB組件,它可讀取連載的頻率附件。典型的,這些附件將會記錄到一個數據庫中所以企業可以保存事務服務質量的曆史紀錄。企業可以使用這個數據庫將真實的事務響應次數與服務器資源的使用聯係起來,從而可以鑒定性的判斷出哪個服務器組件才是關鍵的服務瓶頸。因為附件是排隊等待的,所以頻率讀取器EJB組件應該在不同的J2EE服務器上運行,我們不希望使用商業EJB組件紀錄的頻率附件競爭資源。
實現
在這個部分,我將會告訴你如何將頻率代碼集成到簡單的J2EE客戶端-服務程序上。所有的代碼都可從Resources處下載;下麵部分將告訴你如何使用SunONE(開放網絡環境)應用程序框架來建立和運行頻率代碼。
應用服務原型
在本例中,服務器應用包括一個單一的會話期bean。這無損於應用程序的一般性因為內部的服務EJB設計不影響記錄頻率的結構。即使服務器中有許多不同的EJB組件也可以使用同樣的頻率方法。
XactBeanEJB暴露了三個商業方法:submitWork()、checkWork()、和getResult()。每個方法都各有不同。客戶端應用使用這三個方法來模擬一個客戶端,該客戶端可提交多個Web請求以執行用戶的事務。
代表服務器應用的會話期bean顯示如下:
packageTransactionProcessor;
importjavax.ejb.*;
importjava.rmi.server.*;
importjava.util.*;
/**
*CreatedMay19,200310:07:39PM
*CodegeneratedbytheSunONEStudioEJBBuilder
*/
publicclassXactBeanimplementsjavax.ejb.SessionBean{
privatejavax.ejb.SessionContextcontext;
privateintmRandom;
/**
*@seejavax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext)
*/
publicvoidsetSessionContext(javax.ejb.SessionContextaContext){
context=aContext;
}
/**
*@seejavax.ejb.SessionBean#ejbActivate()
*/
publicvoidejbActivate(){
}
/**
*@seejavax.ejb.SessionBean#ejbPassivate()
*/
publicvoidejbPassivate(){
}
/**
*@seejavax.ejb.SessionBean#ejbRemove()
*/
publicvoidejbRemove(){
}
/**
*Seesection7.10.3oftheEJB2.0specification
*/
publicvoidejbCreate(){
Randomr=newRandom();
mRandom=r.nextInt(10000);
}
publicjava.lang.StringSubmitWork(java.lang.StringWork){
returnnewInteger(mRandom).toString();
}
publicbooleanCheckWork(java.lang.StringXact){
returntrue;
}
publicjava.lang.StringGetResult(java.lang.StringXact){
returnnewInteger(mRandom).toString();
}
}
這三個方法可模擬客戶端事務處理。在submitWork()中,我們產生了一個作為任意號碼使用的句柄--實際上它是唯一的事務標記符。checkWork()總是返回"真"。在實際的係統中,客戶端傳遞事務標記符,所以checkWork()使用一個回溯的事務管理器檢查事務是否已經完成。類似的,在實際的係統中,getResult()返回一個複雜的事務完成紀錄。
服務器Web服務軟件包
服務器Web服務軟件包可自動生成。在SunONEStudio中,Web模塊的創建隻要選擇一組EJBJava方法即可,並且Web服務軟件包的類可由Web模塊創建。
該軟件包包含許多類和接口。這裏最關鍵的一個就是<ServiceName>ServantInterface_Tie類,在這個類中服務名就是<ServiceName>。類Tie是Web服務模塊最上麵的堆棧;它將引入的服務調用綁定到創建它的EJB組件上。我們隻需修改類Tie就可以添加次數紀錄。
Tie包括許多方法,但是我們隻需修改與EJB商業方法invoke_<X>關聯的那一個方法。在方法invoke_<X>中,<X>表示EJB商業方法的名稱。我們添加一個importPayload.*;到類Tie上,並對每個商業方法作了一個小小的修改。讓我們看看下麵的方法invoke_SubmitWork():
/*
*Thismethoddoestheactualmethodinvocationforoperation:SubmitWork
*/
privatevoidinvoke_SubmitWork(StreamingHandlerStatestate)throwsException{
TransactionService.XactServiceGenServer.
XactServiceServantInterface_SubmitWork_RequestStruct
myXactServiceServantInterface_SubmitWork_RequestStruct=null;
ObjectmyXactServiceServantInterface_SubmitWork_RequestStructObj=
state.getRequest().getBody().getValue();
/*Lineaddedtogeneratedmethod:*/
Serializer.queueFirstAttachmentText(state.getMessageContext());
if(myXactServiceServantInterface_SubmitWork_RequestStructObj
instanceofSOAPDeserializationState){
myXactServiceServantInterface_SubmitWork_RequestStruct=
(TransactionService.XactServiceGenServer.
XactServiceServantInterface_SubmitWork_RequestStruct)
((SOAPDeserializationState)
myXactServiceServantInterface_SubmitWork_RequestStructObj)
.getInstance();
}else{
myXactServiceServantInterface_SubmitWork_RequestStruct=
(TransactionService.XactServiceGenServer.
XactServiceServantInterface_SubmitWork_RequestStruct)
myXactServiceServantInterface_SubmitWork_RequestStructObj;
}
java.lang.Stringresult=
((TransactionService.XactServiceGenServer.XactServiceServantInterface)
getTarget()).SubmitWork
(myXactServiceServantInterface_SubmitWork_RequestStruct.getString_1());
TransactionService.XactServiceGenServer.
XactServiceServantInterface_SubmitWork_ResponseStruct
myXactServiceServantInterface_SubmitWork_ResponseStruct=
newTransactionService.XactServiceGenServer
.XactServiceServantInterface_SubmitWork_ResponseStruct();
SOAPHeaderBlockInfoheaderInfo;
myXactServiceServantInterface_SubmitWork_ResponseStruct.setResult(result);
SOAPBlockInfobodyBlock=newSOAPBlockInfo
(ns1_SubmitWork_SubmitWorkResponse_QNAME);
bodyBlock.setValue(myXactServiceServantInterface_SubmitWork_ResponseStruct);
bodyBlock.setSerializer
(myXactServiceServantInterface_SubmitWork_ResponseStruct_SOAPSerializer);
state.getResponse().setBody(bodyBlock);
}
我們添加了一個單行到invoke_SubmitWork()上:
Serializer.queueFirstAttachmentText(state.getMessageContext());
getMessageContext()返回實現接口javax.xml.rpc.handler.soap.SOAPMessageContext的對象。該對象提供對當前SOAP信息的訪問。我們傳遞實現接口SOAPMessageContext的對象到Payload.Serializer中的一個靜態方法上。該靜態方法從第一個信息附件中獲取XML字符串並將它排隊等待次數處理器EJB組件的調用。
我們對每個invoke_<X>方法作了同樣的修改。
Payload軟件包
Payload軟件包可用於客戶端,也可用於服務器。它包含三個類:ClientReport、CurrentReport、和Serializer。
ClientReport表示一個客戶端次數報告:
packagePayload;
importjava.io.*;
importjava.util.*;
/**
*
*/
publicclassClientReportimplementsSerializable{
publicDateclientStartDateTime;
publicDateserverStartDateTime;
publiclongclientElapsedMS;
publicStringtype;
publicStringstatus;
publicStringtransactionID;
publicStringclientID;
//DefaultpublicconstructorforWSDL
publicClientReport(){
}
/*
...Get,setpropertymethodsarenotshown
*/
在上述代碼中,clientStartDateTime記錄客戶端初始化事務的時間。serverStartDateTime當前沒有使用;它的用途是保存事務的服務器開始時間以便事務次數可與服務器資源使用的隨時間的變化關聯起來。
ClientElapsedMS是我們記錄的主要工具:從客戶端開始記錄新事務到它收到最後一個Web服務調用的結果為止這段時間的毫秒數。
Type允許客戶端使用類型特征化事務。通常,事物係統提供許多種類型的事務。我們期望某些類型對於服務器來說相對容易一些,某些類型相對難一些,這樣當我們分析響應次數和測量服務器資源時我們能夠將他們辨別出來。
Status記錄事務完成時的完成狀態。
ClientID是客戶端標記符。當分析服務品質時我們可以使用它來區別同一個客戶端完成的事務。
客戶端使用第二個類CurrentReport來定界應用事務:
packagePayload;
importjava.util.*;
importjava.rmi.server.*;
/**
*
*/
publicclassCurrentReport{
publicstaticUIDClientIdentifier=newUID();
/**HoldsvalueofpropertycurrentReport*/
publicstaticClientReportReport;
publicstaticClientReportLastReport;
/**CreatesanewinstanceofCurrentReport*/
publicCurrentReport(){
}
publicvoidBeginTransaction(){
Report=newClientReport();