<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Sky's blog

    我和我追逐的夢

    常用鏈接

    統計

    其他鏈接

    友情鏈接

    最新評論

    使用AIO和SEDA模型來構建可伸縮的企業應用

    備注:翻譯自theserverside.com的一篇文章,原文地址請見http://www.theserverside.com/tt/articles/article.tss?l=IOandSEDAModel。英文能力一般,翻譯質量不是特別理想,大家將就點看吧。如有錯誤請幫忙指正。

    正文如下:

    討論
        這篇文章展示一個解決方案,用來解決企業應用中的可伸縮性問題,這些應用必須支持即要求快速響應而又長時間運行的業務程序,吞吐量或大或小。
       
        讓我們定義一個簡單的示例場景來模擬這種情況。我們有一個前端web應用程序,通過http接收請求,然后把請求發送給不同的web service后端。web service請求的后端平臺中有一個響應很慢。結果導致我們將獲得一個很低的吞吐量.因為這個響應很慢的服務使得web服務器的線程池中的一個工作線程始終保持繁忙,其他請求無法得到處理。
       
        這里有一個解決這種情況的方案,雖然現在還沒有標準化,但已經被幾乎所有servlet容器以這樣或者那樣的方法實現:Jetty, Apache Jakarta Tomcat, and Weblogic. 這就是異步IO(asynchronous IO,or AIO).
       
        上面提到的解決方案中使用到的關鍵架構組件如下:
            1. 在servlet容器中使用異步IO
            2. 階段化事件驅動架構模型(SEDA)
           
        在servlet容器中使用異步IO

        servlet容器正成為在java nio庫之上實現高可伸縮性應用的良好機會——nio類庫給予從每連接一線程轉變為每請求一線程的能力。

        當然這些還不夠,在實現Reverse Ajax-based的應用時會發生問題。目前沒有機制提供servlet API來容許異步發送數據給客戶端。目前Reverse Ajax有三種實現方式:

        * polling
        * piggy back
        * Comet

        當前基于Commet的實現是保持和客戶端的一個打開的通道,基于某些事件發回數據。但是這打破了每請求一線程模型,在服務器端至少需要分派一個工作線程。

        在servlet容器中目前有兩種實現方式:
        1.異步IO(Apache Tomcat, Bea Weblogic)——容許servlet異步處理數據
        2.continuations (延續?)(Jetty)——在Jetty6介紹的非常有趣的特性,容許掛起當前請求并釋放當前線程。

        所有這些實現都有優點和缺點,而最好的實現將是所有這些實現的組合。

        我的例子基于Apache Jakarta Tomcat的實現,稱為CometProcessor。這種實現將請求和應答從工作線程中解耦,從而容許工作線程稍后再完成應答。

        Staged event-driven architecture (SEDA) model
        SEDA模型是伯克利大學的Matt Welsh, David Culler和Eric Brewer推薦的一個架構設計。SEDA將應用分解為由動態資源控制器分離的不同階段,從而容許應用程序動態調整來改變負載。

        下面你將看到基于SEDA的HTTP服務器:
       
       

        圖片2: SEDA HTTP服務器: 基于SEDA的HTTP服務器的架構表述。應用由被隊列分離的多個階段的集合組成。箭頭表述了階段之間的事件流程。每個階段可以被獨立管理,并且階段可以按順序依次運行或并發運行,或者是兩者的組合。時間隊列的使用容許每個階段分別load-conditioned(負載調節?).例如,設置事件隊列的閥值。

        有關這個架構的更多內容可以在這個頁面找到:SEDA: An Architecture for Well-Conditioned, Scalable Internet Services.
       
        讓我們一起來看,我們的簡化場景是如何映射到這個SEDA架構的。


       
        基于SEDA的應用將由七個階段組成。當一個特定類型的請求到達時,它將被路由到正確的隊列中。對應的階段將處理這個消息,然后將應答放到應答隊列中。最后數據將被發送給客戶端。通過這種方法我們可以解決當請求被路由到應答緩慢的服務時阻塞其他請求處理而帶來的擴展性問題。
       
        讓我們一起來看看怎么用Mule來實現這種架構。
       
        Mule是一種開源Enterprise Message Bus (ESB),它的模型概念是基于SEDA模型。Mule也支持其他信息模型,但默認是SEDA模型。在這種模式下,Mule將每個組件當成一個階段,使用自己的線程和工作隊列。
       
        在SEDA模型中的關鍵組件——Incoming Event Queue(輸入事件隊列), Admission Controller(許可控制器), Dynamically sized Thread Pool(動態線程池), Event Handler(事件處理器)和Resource Controller(資源控制器)——被映射到Mule的服務組件。
       
        在Mule中,Incoming Event Queue(輸入事件隊列)是作為一個inbound(內部?)的路由器或者終端提供,而Event Handler(事件處理器)自身就是作為一個組件。Thus we're short of an Admission Controller, Resource Controller and a Dynamically sized Thread Pool. (be short of ?怎么翻譯,sorry)
       
        Admission Controller(許可控制器)作為SEDA階段和Incoming Event Queue(輸入事件隊列)連接,用Mule的術語說是組件。實現這種方式的最直接的方法是作為一個Inbound路由器,用于控制被注冊到通道上的組件接受的事件,哪些該被處理和該如何處理。

        我們場景的邏輯流程,將在下面的圖中展示如何被映射到Mule模型。圖中列舉的步驟如下:

        1. 客戶端通過http請求下一個訂單
        2. 請求被http服務器處理,在我們的案例中是Apache Jakarta Tomcat。基于http請求提供的參數,前端應用程序組合一個請求對象。在我們的場景中,我們有兩個對象類型,PriceOrderRequest和StockOrderRequest。每個請求會自動生成一個關聯id,并被映射到關聯這個請求的應答對象中。我們將在稍后看到這個關聯id將被如何用于匹配從Mule容器到原始客戶端請求的應答。從現在開始,請求對象將包含這個關聯id,并將在前端應用程序的所有層之間傳遞,當然也會穿透Mule的組件。這個請求訂單,不管是PriceOrderRequest還是StockOrderRequest,將被發送到access層。在access層將有一個準備好的JMS生產者用于將這個信息加入到請求隊列。現在請求訂單將被Mule組件處理。被web服務器分配用來服務于我們http請求的工作線程現在被釋放可以用于服務其他請求,它不需要等待我們的業務處理結束。



        3. 我們的請求訂單現在在jms的隊列中,地址是jms://requestQueue。現在處理被轉移到Mule中。

        4. 基于對象類型,訂單將被路由到不同的隊列。在我們的案例中,我們有一個PriceOrderRequest,所以信息被路由到jms://priceOrderQueue。

        5. 通過使用Apache CXF,一個SOAP請求被生成并發送到web service容器。應答將被發送到jms://responseQueue.

        6. 同樣的類似步驟4的場景發生在StockOrderRequest的案例中。
       
        7. 類似步驟5.

        8. JMS的消費者池監聽the jms://responseQueue. 這個隊列包含業務請求的應答信息。這個消息包含在步驟2中生成的關聯id元數據,這將容許我們識別請求的發起者。

        9. 一旦http應答對象被識別,我們可以發送應答給客戶端。

        上面流程的Mule配置信息展示如下:

    <jms:activemq-connector name="jmsConnector" brokerURL="tcp://localhost:61616"/>

    <model name="Sample">
        
    <service name="Order Service" >            
              
    <inbound>
                 
    <jms:inbound-endpoint queue="requestQueue"/>                   
              
    </inbound>               
              
    <component class="org.mule.example.Logger"/>
              
    <outbound>
               
    <filtering-router>
                   
    <jms:outbound-endpoint queue="priceOrderQueue" />
                       
    <payload-type-filter expectedType="org.mule.model.PriceOrderRequest"/>
                 
    </filtering-router>                
                 
    <filtering-router>     
                      
    <jms:outbound-endpoint queue="stockOrderQueue" />                         
                        
    <payload-type-filter expectedType="org.mule.model.StockOrderRequest" />
                    
    </filtering-router>
            
    </outbound>        
        
    </service>
            
        
    <service name="stockService">
            
    <inbound>
                
    <jms:inbound-endpoint queue="stockOrderQueue" transformer-refs="JMSToObject 
                        StockOrderRequestToServiceRequest"
     />
            
    </inbound>               
            
    <outbound>
                 
    <chaining-router>
                     
    <cxf:outbound-endpoint                             
                         
    address="http://localhost:8080/axis2/services/getStock"
                     clientClass
    ="org.axis2.service.stock.GetStock_Service"  
                      wsdlPort
    ="getStockHttpSoap12Endpoint" 
                      wsdlLocation
    ="classpath:/Stock.wsdl" 
                      operation
    ="getStock" />
                     
    <jms:outbound-endpoint queue="responseQueue" 
                             transformer-refs
    ="ServiceResponseToStockOrderResponse ObjectToJMS"/>
                
    </chaining-router>                
             
    </outbound>
            
    <default-service-exception-strategy>
               
    <jms:outbound-endpoint queue="responseQueue" 
                       transformer-refs
    ="ExceptionToResponse ObjectToJMS"/>
            
    </default-service-exception-strategy>
       
    </service>    
               
       
    <service name="priceService">
           
    <inbound>
               
    <jms:inbound-endpoint queue="priceOrderQueue" 
                       transformer-refs
    ="JMSToObject PriceOrderRequestToServiceRequest"/>
           
    </inbound>    
           
    <outbound>
               
    <chaining-router>
                   
    <cxf:outbound-endpoint                     
                       
    address="http://localhost:8080/axis2/services/getPrice"
                       clientClass
    ="org.axis2.service.price.GetPrice_Service"  
                       wsdlPort
    ="getPriceHttpSoap12Endpoint" 
                       wsdlLocation
    ="classpath:/Price.wsdl" 
                       operation
    ="getPrice" />
                   
    <jms:outbound-endpoint queue="responseQueue" 
                           transformer-refs
    ="ServiceResponseToPriceOrderResponse ObjectToJMS"/>
               
    </chaining-router>                
              
    </outbound>
              
    <default-service-exception-strategy>

        這個事件驅動的架構模型有一個挑戰性的問題,如何將應答和請求關聯?請求被生成,業務對象被創建,并被作為jsm對象信息的負載在Mule空間中通過多個jms隊列傳輸。這個信息被從一個隊列路由到另一個,通常被用來作為到web service請求的輸入。

        容許我們持續追蹤信息的關鍵信息是來自jms規范的關聯id。可以通過使用message.setJMSCorrelationID()來設置。然而如果你在jms隊列中發布設置了這個屬性的信息,Mule似乎會覆蓋這個信息并為消息創建一個將貫穿整個流程的新的關聯id。幸好還有一個內部的名為MULE_CORRELATION_ID的Mule消息屬性。如果Mule發現消息的這個屬性被設置,它將被用于穿越流程中所有的組件,另外如果關聯id沒有被設置,MULE_CORRELATION_ID屬性的值還將被作為關聯id的值使用。

    /* set the MULE_CORRELATION_ID property before sending the message to the queue*/
    conn
    =getConnection();
    session
    =conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
    producer
    = session.createProducer(getDestination(Constants.JMS_DESTINATION_REQUEST_QUEUE));
    jmsMessage
    =session.createObjectMessage();
    jmsMessage.setObject(request);
                jmsMessage.setStringProperty(Constants.PROPS_MULE_CORRELATION_ID, request.getCorrelationID());
    producer.send(jmsMessage);

        所以每個請求必須在對應的業務對象被發送到Mule入口(一個jms對象)前生成一個唯一的關聯id。

        一個可行的方法是生成一個UUID用做關聯id,同樣將UUID映射到CometProcessor接口中的事件方法提供的被包裹為CometEvent對象的HttpServletResponse對象。

    /* 
    * generate the UUID for the CORRELATION ID and map to the HttpServletResponse 
    */
    public class IdentityCreator extends MethodInterceptorAspect{
        
        
    protected void beforeInvoke(MethodInvocation method){        
            Object[] args
    =method.getArguments();
            HttpServletRequest httpRequest
    =((CometEvent)args[0]).getHttpServletRequest();
            String uuid
    =UuidFactory.getUuid();
            httpRequest.setAttribute(Constants.PROPS_MULE_CORRELATION_ID, uuid);
            HttpResponseManager.getInstance().saveResponse(uuid, ((CometEvent)args[
    0]).getHttpServletResponse());
            
        }
        
    protected void afterInvoke(MethodInvocation method){
            
    return;
        }
        @Override
        
    public void afterExceptionInvoke(MethodInvocation method) throws Throwable {        
            Object[] args
    =method.getArguments();
            HttpServletRequest httpRequest
    =((CometEvent)args[0]).getHttpServletRequest();
            String uuid
    =(String)httpRequest.getAttribute(Constants.PROPS_MULE_CORRELATION_ID);
            
    if (uuid!=null) HttpResponseManager.getInstance().removeResponse(uuid);         
        }

    }
       
        當應答消息返回時,我們所需要做的只是從jms消息屬性中獲取關聯對象的值,查找對象的HttpServletResponse對象,然后發送應答給客戶端。

        測試

        一些測試可以提供我們這個架構優點的清晰見解。使用Apache JMeter,每個案例都執行一個測試,一個架構使用異步servlet和SEDA模型,另一個架構不使用這個模型。測試運行了1個小時,每秒10個線程,兩種類型的請求交互使用。為了這些測試,我們分配了總共6個工作線程。在沒有擴展性提升的案例中,所有6個線程都被Tomcat的線程池占用。





        可以非常清楚的看到,吞吐量(綠線)是如何下降到大概 23 請求每分鐘的。



        現在讓我們在我們的組件中分配這6個線程。每個組件分配一個單一線程。

        在Jakarta Tomcat中,server.xml配置文件中的下面這些行需要修改:

    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" 
            maxThreads
    ="1" minSpareThreads="0"/>

        在Mule的案例中,需要在Mule配置文件中為每個服務組件在service標簽中增加以下行:

    <component-threading-profile 
        
    maxThreadsActive="1" maxThreadsIdle="0" poolExhaustedAction="RUN" 
        maxBufferSize
    ="20" threadWaitTimeout="300"/>

        異步和SEDA模型架構的測試在下面可以看到。吞吐量在23請求每分鐘保持不變。

        如果我們運行性能測試超過1小時,第一個案例的吞吐量還將繼續下降,但是第二個案例依然將保持同樣的值。





    posted on 2008-09-04 22:58 sky ao 閱讀(2453) 評論(0)  編輯  收藏 所屬分類: Thread


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 暖暖在线日本免费中文| 成年私人影院免费视频网站| 国产成人精品亚洲精品| 亚洲a∨无码精品色午夜| 手机看片久久国产免费| 国产精品亚洲专区无码不卡| 在线免费观看国产视频| 国产综合激情在线亚洲第一页 | 18级成人毛片免费观看| 337p欧洲亚洲大胆艺术| 久草视频在线免费| 亚洲a视频在线观看| 波多野结衣久久高清免费| 菠萝菠萝蜜在线免费视频| 国产精品亚洲mnbav网站| 人妻免费一区二区三区最新| 亚洲AV日韩AV永久无码免下载| 69精品免费视频| 亚洲乱码无人区卡1卡2卡3| 四虎亚洲国产成人久久精品| 成在人线av无码免费高潮水| 亚洲an天堂an在线观看| 18勿入网站免费永久| 亚洲av无一区二区三区| 亚洲日韩国产精品第一页一区| 精品一区二区三区免费毛片爱 | 美女被免费视频网站a| 青青草原亚洲视频| 24小时在线免费视频| 亚洲国产成人精品无码区花野真一| 亚洲国产精品狼友中文久久久| 久久久精品免费国产四虎| 亚洲乱码在线视频| 亚洲精品一级无码鲁丝片| 少妇无码一区二区三区免费| 亚洲欧美第一成人网站7777| 久久精品夜色噜噜亚洲A∨| 亚欧免费视频一区二区三区| 人妻仑乱A级毛片免费看| 亚洲福利秒拍一区二区| 国产一级淫片视频免费看 |