搜索文章:

首页  |  Java技术  |  Asp.net  |  Asp编程  |  VC/C++  |  Delphi  |  VB编程
上一篇:实体bean的承诺 >>

并行业务逻辑处理

作者:Murali Kashaboina, Bin Liu

 当必须同时执行独立的业务组件时,应用程序业务逻辑的并行处理对系统的性能有着直接影响,然而,从历史来看,实现应用程序级别的并行处理是一个著名的难题。
  对于依赖于应用服务器容器提供服务的J2EE应用程序来说,实现并行的和并发的处理好比打一场硬仗。随着时间的推移,通过利用API提供的异步消息传送功能,Java消息服务(Java messaging services,JMS)API已经成为事实上用于并行业务逻辑处理的解决方案。
  尽管JMS为开发使用异步消息传送的J2EE应用程序提供了一个可靠的解决方案,但它并未自动生成预期的最终结果。JMS面临着以下急需解决的难题:

  • JMS提供了用于解决难题的各部分内容,但它并未将各个部分组织在一起,以实现一个精确的解决方案。
  • 即使是JMS专家也必须克服通往成功实现路上的种种圈套和陷阱。
  • 最佳实践不赞成在整个应用程序中分散复杂的JMS代码。

  因为这些难题的存在,必须构建出一个可重用的、灵活的和健壮的框架,从而使J2EE应用程序能够利用异步JMS功能来实现高效的并行业务逻辑处理。使用JMS 1.0.2和BEA WebLogic 6.1 Application Server中对EJB 2.0消息驱动bean的支持可以实现这样一个框架。
  这种设计策略的一个现实例子可以产生一个可重用的框架,该框架可以用在任何需要异步执行模式的J2EE应用程序开发场景中。这个特定的实例通过利用传统的代理和命令模式,采用了一种黑箱策略。

一般方法
  考虑可用于并行业务逻辑处理的可能解决方案时,首先想到的方法应该是线程的产生。可以创建多个线程来运行每个业务任务。这对于简单的应用程序可以工作得很好;然而,对于涉及到关键客户事务和在多个全异系统之间交换大量数据的企业应用程序来说,创建线程并不是一个健壮的解决方案。这类设计可以导致对完美实现本身的无止境追求,因为在线程生命周期管理、线程池的维护、事务基本性管理和消除并发性问题等方面还存在许多难题。俗话说,预防胜于治疗,因此,要避免首先就使用这种方法,而是从一开始就要采用健壮的设计方案。

设计概要
  为了实现这样一种框架,有多种不同的策略曾被纳入考虑范围。当然,可能存在无数种实现方法,但是最好的方法是利用底层的应用服务器。而这个最好的方法是以利用BEA WebLogic Server的高性能JMS实现为基础的。
  设计策略是基于异步地将包含动作命令的消息发送给JMS队列。基于实现,动作命令可以执行任何业务任务。设计中的主要组件有:

  • 动作命令
  • 动作消息
  • 外观bean
  • 作为消息生产者的JMS处理器
  • 作为消息使用者的消息驱动bean
  • 消息队列
  • 临时队列

  bean图可视化地描述了参与的组件,从框架的客户端到消息驱动bean的请求消息流,以及从消息驱动bean返回客户端的响应消息流(参见图1)。


图1:J2EE环境

实现细节
动作命令
  动作命令代表要执行的实际业务逻辑。存在一个为动作命令而创建的标准接口,所以可以基于不同的要求而插入不同的命令实现。该命令接口定义了两个重要的方法,如以下代码片断所示:

public interface Action extends Serializable{
public void execute() throws Throwable;
public Object getResult();
}


  动作命令被设计为一个自包含的实体,用于包含业务逻辑的执行结果。getResult方法返回作为结果的对象。该方法保证了业务逻辑和业务逻辑的执行结果完全与框架隔离。框架本身就像一个黑箱,仅仅提供用于执行业务逻辑的手段,而把复杂的业务逻辑细节留给动作命令的实际实现去处理。

动作消息
  动作消息的作用是充当到动作命令的适配器。动作消息的目的是把消息驱动bean与处理错误情况和动作命令的执行状态屏蔽开来。动作消息还帮助JMS处理器将发往消息队列的消息和接收自临时队列的消息关联起来。通过查看相应的动作消息,框架客户端可以检查动作命令的执行状态。下面的代码显示了ActionMessage类中的重要方法:

public class ActionMessage implements
Serializable {
...............................
public boolean hasThrowable() {
return (throwable !=
null);
}
public void execute() {
try {
theAction.exe
cute();
} catch(Throwable t) {
setThrowable(t);
}
}
}

外观Bean
  将外观并入框架中是为了防止客户端知道JMS实现的复杂细节。这个外观被实现为一个无状态会话bean。外观bean是一个到框架中的切入点——框架的客户端创建一个动作命令列表,然后通过调用sendAll方法把该列表发送给外观bean。外部bean把动作命令的处理内部委派给JMS消息处理器。为JMS消息队列和连接工厂查找JNDI环境,然后使用该信息创建它自己的JMS消息处理器是可靠的做法。
  JMS消息处理器使用sendAll方法的超时值来设置接收自临时队列的消息的超时。
  使用外观bean有三个主要的优点。首先,它对客户端屏蔽了JMS实现的繁琐细节。其次,外观bean的方法调用可以参与事务管理。最后,因为EJB容器池化了外观bean实例,各种JMS资源,比如每个bean实例内部使用的临时队列、队列会话和队列连接,将自动获得池化效果,这确保了以最经济的方式利用资源。通过定义远程和本地接口,外观会话bean既支持远程调用,也支持本地调用(参见清单1)。

清单1:外观会话bean
public interface Async extends javax.ejb.EJBObject {
public List sendAll(List actionList, long timeout) throws
RemoteException;
public List sendAll(List actionList, long timeout, String orderId)
throws RemoteException;
}
public interface AsyncLocal extends javax.ejb.EJBLocalObject {
public List sendAll(List actionList, long timeout);
public List sendAll(List actionList, long timeout, String orderId);
}

JMS消息处理器
  JMS 消息处理器是封装了JMS实现本质细节的实际组件。AsyncMessenger类代表JMS处理器。通过调用sendAll方法,外观bean把动作命令的列表委派给了AsyncMessenger实例。AsyncMessenger实例首先把每个动作命令包装在一个动作消息对象中。然后,它创建一个javax.jms.ObjectMessage类型的JMS消息实例。AsyncMessenger把动作消息对象设置为JMS ObjectMessage实例中的可串行化有效负载。它还创建了一个临时队列,并注册了一个用于从临时队列接收响应消息的JMS队列接收器。它包括一个JMS消息中到临时队列的引用。最后,它把JMS消息异步地发送给消息队列。
  因为每个动作命令的执行时间可以各不相同,AsyncMessenger接收响应消息的顺序可能与发送请求消息的顺序不完全匹配。为了保证响应消息与相应的请求消息完全匹配,此处使用了一个与每条请求消息相关联的惟一多路复用ID。这个ID只不过是列表中原始动作命令的索引。AsyncMessenger把这个ID设置为请求动作消息中的一个属性,从响应动作消息获得它,然后使用它来把动作消息在响应列表中的位置设置为原始请求消息在输入列表中的相同位置。

消息驱动Bean(MDB)
  MDB被用作消息使用者,负责检索和处理经由消息队列传输的消息。EJB容器负责管理MDB的生命周期。在运行时,EJB容器会创建许多MDB实例,然后将它们保持在池中。在池中创建一个实例之后,EJB容器会基于MDB部署描述符提供的JMS信息为实例订阅指定的消息主题,或者使实例连接到指定的消息队列。与自定义JMS消息使用者相比,使用MDB的主要优点是,MDB是天生的JMS消息使用者,而且EJB容器为MDB提供了一个健壮的环境。因为池中的MDB实例可以同时使用和处理数以百计的消息,MDB能够比大多数传统JMS消息使用者提供更高的吞吐量和更好的可伸缩性。
  在当前的实现中,随着JMS消息通知的到来,MDB从JMS Object消息检索动作消息,然后调用动作消息的执行方法。这种调用将触发动作消息包含的动作命令的实际执行。执行完成之后,MDB将创建一条新的JMS Object消息,然后把动作消息设置为有效负载。然后,它将从输入JMS消息处检索对临时队列的引用,然后把新近创建的JMS消息发送给JMS处理器的临时队列。这种点到点的消息传送方式保证了响应一定会被发送给可信的原始调用者。

消息队列
  这里选择了点到点(Point-to-point,P2P)的消息传送机制用于示例实现,因为它可以保证“有且仅有一个”的消息使用者对消息进行“一次且仅有一次”的处理。在例子中,业务逻辑应该以级联的方式被执行一次且仅执行一次。使P2P发挥作用的主要组件是消息队列,它从根本上解除了消息生产者和消息使用者之间的耦合。几个消息使用者可以连接到同一个队列,但是底层的JMS服务器可保证队列中的消息将被发送给一个消息使用者,而且只会被发送给一个消息使用者。在示例实现中,消息队列被配置为所有动作消息的首要目的地(作为首要消息使用者的MDB实例)。
  通常,队列中的消息是以它在队列中的放置顺序发送给使用者的。BEA WebLogic 6.1 JMS实现提供了可对队列消息的发送顺序进行排序的灵活性,方法是通过配置目的地键。它对消息队列启用了“LIFO”和“FIFO”的排序方法。要启用FIFO(以先进先出的方式获得队列中消息),通过在BEA WebLogic 6.1服务器JMS配置中设置升序的目的地键来配置JMS消息的排列顺序。

临时队列
  通常,JMS客户端使用临时队列来参与使用JMS点到点协议的请求/响应消息传送。临时队列惟一地属于创建JMS队列会话,它的生命跨越了队列会话连接的整个过程,除非临时队列被删除。只有创建临时队列的JMS客户端才能访问它,除非使用JMSReplyTo头把临时队列的引用传递给其他的JMS客户端。尽管多个不同的JMS客户端可以将消息发送给一个临时队列,来自临时队列的JMS消息只能被与创建临时队列的JMS连接相关的JMS队列会话所使用。
  BEA WebLogic 6.1 Server支持轻松创建临时队列。必须在BEA JMS服务器上配置一个JMS模板,以便能够在该JMS服务器中创建临时队列。JMS模板提供一种高效的方法来定义具有类似属性设置的多个目的地。
在当前的框架中,JMS处理器创建临时队列是为了从执行动作消息的MDB接收响应。

总体情况

图2和3描述了组件类之间的关系和消息流。

图2:类图

图3:类图

部署细节
部署描述符
  部署的第一步是为外观会话bean和MDB创建部署描述符。清单2和3中显示的XML片断是部署描述的例子。下一步是用JAR包装EJB并运行BEA WebLogic ejbc实用程序。使用BEA WebLogic Admin Console来部署最后的JAR文件。

清单2:ejb-jar.xml
<enterprise-beans>
<session>
<display-name>AsyncBean Ejb</display-name>
<ejb-name>AsyncBean</ejb-name>
<home>com.uls.common.dao.async.AsyncHome</home>
<remote>com.uls.common.dao.async.Async</remote>
<local-home>com.uls.common.dao.async.AsyncLocalHome</localhome>
<local>com.uls.common.dao.async.AsyncLocal</local>
<ejb-class>com.uls.common.dao.async.AsyncBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<resource-ref>
<description />
<res-ref-name>async/ConnectionFactory</res-ref-name>
<res-type>javax.jms.QueueConnectionFactory</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-env-ref>
<description />
<resource-env-ref-name>async/RequestQueue</resource-envref-
name>
<resource-env-ref-type>javax.jms.Queue</resource-envref-
type>
</resource-env-ref>
</session>
<message-driven>
<display-name>AsyncMDBean</display-name>
<ejb-name>AsyncMDBean</ejb-name>
<ejb-class>com.uls.common.dao.async.AsyncMDBean</ejb-class>
<transaction-type>Container</transaction-type>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>
<resource-ref>
<description />
<res-ref-name>async/ConnectionFactory</res-ref-name>
<res-type>javax.jms.QueueConnectionFactory</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</message-driven>
</enterprise-beans>
清单3:weblogic-ejb-jar.xml
<weblogic-enterprise-bean>
<ejb-name>AsyncBean</ejb-name>
<stateless-session-descriptor>
<pool>
<initial-beans-in-free-pool>1</initial-beans-in-free-pool>
</pool>
</stateless-session-descriptor>
<reference-descriptor>
<resource-description>
<res-ref-name>async/ConnectionFactory</res-ref-name>
<jndi-name>com.uls.async.JMSConnectionFactory</jndi-name>
</resource-description>
<resource-env-description>
<res-env-ref-name>async/RequestQueue</res-env-ref-name>
<jndi-name>com.uls.async.RequestQueue</jndi-name>
</resource-env-description>
</reference-descriptor>
<jndi-name>com.uls.async.AsyncBean</jndi-name>
<local-jndi-name>com.uls.async.AsyncBeanLocal</local-jndi-name>
</weblogic-enterprise-bean>

<weblogic-enterprise-bean>
<ejb-name>AsyncMDBean</ejb-name>
<message-driven-descriptor>
<pool>
<max-beans-in-free-pool>200</max-beans-in-free-pool>
<initial-beans-in-free-pool>1</initial-beans-in-free-pool>
</pool>
<destination-jndi-name>com.uls.async.RequestQueue</destination-jndi-name>
<connection-factory-jndi-name>com.uls.async.JMSConnectionFactory
</connection-factory-jndi-name>
<jms-client-id>AsyncMDBean</jms-client-id>
</message-driven-descriptor>
<reference-descriptor>
<resource-description>
<res-ref-name>async/ConnectionFactory</res-ref-name>
<jndi-name>com.uls.async.JMSConnectionFactory</jndi-name>
</resource-description>
</reference-descriptor>
<enable-call-by-reference>True</enable-call-by-reference>
</weblogic-enterprise-bean>


BEA WebLogic 6.1设置

  1. 配置WebLogic JMS连接工厂(参见图 4)。


    图 4: JMS 连接工厂

    a. 在Admin Console的左侧面板中,点击Connection Factory节点,然后点击右侧面板中的“Configure a new JMS Connection Factory”链接。
    b. 在General选项卡上,为连接工厂指定一个名称和一个JNDI名称。适当时可填写其他属性,然后点击Create。
    c. 适当时,填写Transactions和Flow Control选项卡。修改完成之后,点击每个选项卡上的Apply按钮。
    d. 在Targets选项卡上,通过选择Servers或Clusters选项卡,选择一个要在其上部署连接工厂的WebLogic服务器实例或服务器群集作为目标。
  2. 配置WebLogic JMS模板。
    a. 在Admin Console的左侧面板中,点击Templates节点,然后点击右侧面板中的“Configure a new JMS Template”链接。
    b. 在General选项卡上,为模板指定一个名称,然后点击Create按钮。
    c. 适当时,填写Thresholds & Quotas、Override和Redelivery选项卡。修改完成之后,点击每个选项卡上的Apply按钮。
  3. 配置WebLogic目的地键。
    a. 在Admin Console的左侧面板中,点击Destination Keys节点,然后点击右侧面板中的“Create a new JMS Destination Key”链接。
    b. 在Configuration选项卡上,指定名称、排序属性、键类型和排序方向。
    c. 指定所有属性之后,点击Apply 按钮。
  4. 配置WebLogic JMS Server。
    a. 在Admin Console的左侧面板中,点击Server节点,然后点击右侧面板中的“Configure a new JMS Server”链接。
    b. 在General选项卡上,指定服务器名称、存储器(如果已经创建的话)、分页存储器(如果已经创建的话),然后选择前面创建的模板作为服务器的临时模板。
    c. 点击Create按钮以创建服务器。
    d. 适当时,填写Thresholds & Quotas选项卡,然后点击Apply 按钮以使修改生效。
    e. 在Targets选项卡上,选择一个WebLogic Server实例作为目标,然后应用修改。
  5. 配置WebLogic队列(参见图5)。


    图5:异步JMS队列
    a. 在Admin Console左侧面板的Servers节点下,展开新创建的JMS服务器实例,然后点击Destinations节点。
    b. 点击右侧面板中的“Configure a new JMSQueue”链接。
    c. 在General选项卡上,指定队列名称和队列JNDI名称。
    d. 从目的地键的可用列表中选择前面创建的目的地键,然后把它移动到已选列表中。
    e. 点击Create按钮创建队列。
    f. 适当时,填写Thresholds & Quotas/Override和Redelivery选项卡,然后点击每个选项卡上的Apply按钮,以使修改生效。
  6. 配置外观bean和MDB。
    a. 在Admin Console左侧面板中的Deployments节点中,点击EJB节点。
    b. 点击右侧面板中的“Configure a new EJB”链接。
    c. 在EJB组件面板中的General选项卡中,指定EJB名称、到EJB jar的路径和部署顺序。
    d. 点击Deployed复选框,以设置组件的部署状态。
    e. 点击Create按钮以创建组件。
    f. 在Targets选项卡上,选择一个WebLogic Server实例或群集实例作为目标,然后应用修改。

使用
  清单4示范了框架的使用。
清单4:示例框架使用
//create fa鏰de session bean
Context ctx = new InitialContext();
AsyncHome home = (AsyncHome)ctx.lookup("com.uls.async.AsyncBean");
Async async = home.create();
//create a list of request actions
ArrayList requestList = new ArrayList();
for(int i=0; i< 10; i++) {
requestList.add(new SomeBusinessLogicAction());
}
//set the timeout value and invoke sendAll method
long timeout = 30000;
List responseList = async.sendAll(requestList, timeout);
//Retrieve the response and inspect the result
for(int i=0; i< responseList.size(); i++) {
ActionMessage actMsg = (ActionMessage) responseList.get(i);
try {
Action action = (Action)actMsg.getAction();
System.out.println("action response is : " + action.getResult());
} catch(Exception e) {
e.printStackTrace();
}
}

基准测试
  通过一个用于显示旅客航线信息的航空电子商务应用程序,在使用和不使用框架的情况下进行性能基准测试。该应用程序通过与多个异构数据源(从关系数据库到大型主机遗留系统)交互而检索数据。不同的数据访问对象用于访问不同数据源中的数据,而且一个聚合器服务组件被用于与不同的数据访问对象交互和组装已检索的数据。
  如果不使用框架,聚合器服务将通过以同步的方式调用数据访问对象来组装数据。在这种情况下,预计总体的响应时间会很长,而且应用程序的性能将会很糟糕。对应用程序进行负载测试之后,证明这种预计是正确的:如果平均每位旅客有5条航线,检索完整信息的平均响应时间大约为35秒。对应用程序进行的负载测试证明了,除非实现并行和并发的数据检索机制,否则对响应时间进行优化将会格外困难。
  将框架结合到应用程序中,以便聚合器服务可以异步地同时调用数据访问对象,然后对应用程序进行另一轮负载测试。这次测试所获结果表明,平均响应时间大约为7秒。因此,通过使用框架,应用程序的性能提高了大约5倍。

结束语
  利用JMS提供的异步功能的、可重用的、可扩展的和健壮的框架,以及EJB容器提供的、健壮的运行时环境,可以解决企业级和任务关键型应用程序中并行和并发的业务逻辑处理的问题。当框架被结合到应用程序中,以便并行地和并发地访问多个数据源中的数据时,在BEA WebLogic 6.1 Application Server中运行航空应用程序的基准测试可以获得更短的响应时间和更好的性能。经证实,框架的实现比通常基于线程的实现要健壮得多。

致谢
  我们要感谢Virgil Bistriceanu总经理和Helen Agulnik经理,他们在本文的撰写过程中给予了支持和鼓励。我们还要特别感谢我们的同事Kelly Oliver,她花费了宝贵的时间查阅和编辑本文。


相关文章:
© 2006   www.java-asp.net