`

使用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配置信息展示如下:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->< 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的值使用。

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->/*  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对象。

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->/*  
* 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配置文件中的下面这些行需要修改:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->< Executor  name ="tomcatThreadPool"  namePrefix ="catalina-exec-"  
        maxThreads
="1"  minSpareThreads ="0" />


    在Mule的案例中,需要在Mule配置文件中为每个服务组件在service标签中增加以下行:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->< component-threading-profile 
    
maxThreadsActive ="1"  maxThreadsIdle ="0"  poolExhaustedAction ="RUN"  
    maxBufferSize
="20"  threadWaitTimeout ="300" />


    异步和SEDA模型架构的测试在下面可以看到。吞吐量在23请求每分钟保持不变。

    如果我们运行性能测试超过1小时,第一个案例的吞吐量还将继续下降,但是第二个案例依然将保持同样的值。




分享到:
评论
1 楼 SINCE1978 2011-01-14  
be short of
缺、缺少
“Thus we're short of an Admission Controller, Resource Controller and a Dynamically sized Thread Pool. ” —— 我们还缺许可控制器、资源控制器和动态线程池。

相关推荐

    基于java的BIO、NIO、AIO通讯模型代码实现

    Java作为一门广泛使用的开发语言,提供了多种I/O(Input/Output)通信模型,包括传统的阻塞I/O(BIO)、非阻塞I/O(NIO)以及异步I/O(AIO)。这些通信模型在不同的场景下有着各自的优势,理解和掌握它们对于优化...

    使用异步AIO大大提高应用程序的性能.docx

    应用程序可以使用`aio_error`检查操作状态,`aio_return`获取操作结果,而`aio_notify`和信号机制则用于通知I/O完成。此外,还可以使用线程池和回调函数来处理完成的I/O事件,进一步提升并发性。 AIO的优势在于,它...

    使用AIO实现非阻塞socket通信

    总之,通过这个使用AIO实现的非阻塞socket通信项目,我们可以学习到如何利用Java AIO进行高效的网络编程,理解和实践异步I/O模型,这对于构建高性能、高并发的网络应用至关重要。通过实际操作,你可以更好地理解非...

    Java编程中的IO模型详解:BIO,NIO,AIO的区别与实际应用场景分析

    BIO、NIO和AIO各有优缺点,选择哪种模型取决于具体的应用需求。对于小规模、固定连接数的简单应用,BIO可能是最佳选择,因为其编程简单。对于需要处理大量并发连接的高性能服务器,NIO提供了更好的性能和资源利用率...

    aio方式socket文件传输--改进

    在文件传输过程中,可以使用`asyncio.StreamReader`和`asyncio.StreamWriter`对象来读取和写入文件数据,它们提供了非阻塞的读写接口,非常适合于aio场景。 此外,为了确保文件传输的完整性和可靠性,通常会采用TCP...

    bio nio aio demo

    为了处理与外部世界的交互,Java提供了三种不同的I/O模型:BIO( Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O)。这些模型各有优缺点,适用于不同场景。下面我们将深入探讨这三种I/O模型,并...

    linux_aio.zip_LINUX下开启AIO_aio glibc_aio 两种方式_linux aio_linux_aio

    通过阅读代码和文档,你将能够更好地理解这两种AIO实现的区别和应用场景。 总的来说,AIO在处理大量并发I/O操作时能带来显著的性能提升,尤其适用于网络服务器、数据库系统等场景。通过深入学习和实践,你可以利用...

    BIO、NIO、AIO、Netty 、TCP全网最全解析!Netty中提供了哪些线程模型?

    在Netty中,使用NIO或AIO实现的TCP连接,可以结合其线程模型,如EventLoopGroup(事件循环组)和ChannelHandler(通道处理器)等组件,实现高效、可扩展的网络通信。例如,BossGroup处理新的连接请求,WorkerGroup...

    Netty5 AIO

    **Netty5 AIO 深度解析** Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端...对于想要深入理解网络编程和Java并发的开发者来说,Netty5 AIO是不可或缺的知识点。

    aio.rar_aio

    "www.pudn.com.txt"可能是一个文本文件,包含了更多关于该主题的链接或者引用信息,而"AIO"可能是主文档,详细阐述了AIO在uC/OS II中的实现和应用。在这个文档中,我们可能会看到以下知识点: 1. AIO的概念和原理:...

    JDK1.7 AIO

    **JDK1.7 AIO 深度解析** AIO(Asynchronous Input/Output,异步输入输出)是Java 7引入的一种新的I/O模型,它...对于大型、高性能的应用来说,投入学习AIO是值得的,因为它能带来显著的性能提升和更好的资源利用。

    Java通讯模型-BIO、NIO、AIO综合演练

    总结,理解和熟练掌握Java中的三种通讯模型,有助于我们构建更加高效、稳定的服务端应用,提高系统的并发处理能力和响应速度。在项目实践中,结合具体场景选择合适的通讯模型,是优化系统性能的关键。

    CISSP AIO4与AIO5的区别

    4. **案例研究和实例**:新版教材往往会有更多的实际案例研究和应用场景,以帮助考生更好地理解理论知识,并将其应用于实际工作中。AIO5可能会包含更多反映当前安全挑战和解决方案的实例。 5. **练习题与模拟测试**...

    构建高性能的大型分布式java应用

    在构建高性能的大型分布式Java应用时,我们面临的是复杂的技术挑战和优化目标。要实现这样的系统,我们需要深入了解Java平台的特点,以及如何利用其优势来处理大规模数据和高并发请求。以下是一些关键的知识点: 1....

    基于AIO的超轻量HTTP服务器实现

    标题 "基于AIO的超轻量HTTP服务器实现" 指的是使用异步I/O(Asynchronous Input/Output,简称AIO)模型构建一个轻量级的HTTP服务器。AIO在Java中通常指的是NIO.2(New I/O 2),它提供了非阻塞I/O操作,能够更有效地...

    联想超融合AIO H1000 V4.5.2 使用手册 R1.0 2021.01.11.pdf

    4. 安装和配置:提供详细步骤来指导用户如何安装和配置联想超融合AIO H1000系统,以及如何进行网络设置和软件安装。 5. 入门指导:为新用户提供快速上手指南,帮助他们了解产品的基本操作,包括用户界面介绍、如何...

    浅谈Java中BIO、NIO和AIO的区别和应用场景

    Java中IO模型有三种:BIO、NIO和AIO,下面我们来详细介绍它们的区别和应用场景。 BIO(Blocking I/O) BIO是Java中最古老的IO模型,它是同步并阻塞的。服务器的实现模式是一个连接一个线程,这样的模式很明显的一...

    cordova-plugin-fingerprint-aio_cardova_whispered587_

    Cordova是一种流行的开源框架,它允许开发者使用HTML、CSS和JavaScript来构建原生的移动应用程序。通过WebView技术,Cordova将Web应用包装成原生的移动应用,使得开发者可以利用熟悉的Web开发技术,同时享受移动平台...

Global site tag (gtag.js) - Google Analytics