在我们的实现中, 如图二所示, Retailer 组件是以MDBs形式部署的, 因此是通过消息事件而不是session EJB接口来提供服务的.下面是完整的流程.
首先,客户通过Jsp/Servlet提供货物的名称或是型号, Servlet调用Search EJB组件的方法, Search EJB组件根据输入构造了三个JMS消息,每个对应一个将要搜索的数据源, 然后将三个消息放入请求队列. 然后Search EJB组件等待响应队列上的回应消息(步骤2). 请求消息触发Retailer MDBs, 每个Retailer MDB响应一个消息, 同时开始消息的处理.(步骤3). 完成价格和供应信息的搜索, Retailer MDBs将结果包装在JMS消息中,然后将消息放入响应队列中(步骤4), 注意所有的Retailer MDBs是并行的处理消息的, 所以它们都是在大致15秒内返回结果. 等待中的Search EJB组件从响应队列中取出三个回应消息, 解析结果, 然后返回结果给Servlet/Jsp, 由后者显示结果.
在这个方法中, 最短的响应时间取决于单个数据源的响应时间,而不是象第一个方法还要依赖于数据源的数量.因此, 即便数据源增加到10个, 最短的响应时间仍然是将近15秒. 消息的封装和解释以及与消息系统的交互只需要花费非常短的时间. 这个响应时间对比串行处理的150秒时间, 是个非常突出的改善.
当有多个客户同时发起查询请求的时候, 将有多个Search EJB组件的实例提供服务, 每个服务一个servlet请求. 在这种情况下, 必须有一种机制来映射这组Retailer MDBs与相应的调用他们的EJB组件.
有以下几种方法来实现这种映射:
1. 为每个请求创建一个临时的响应队列. Search EJB 组件可以创建临时的响应队列并且将它的名字与请求参数一起包装在请求消息中, 然后放入请求队列. Retailer MDBs通过查询请求消息中的临时队列信息, 将响应消息放入响应的临时响应队列中. 这个方法简单的解决了不同请求之间的消息冲突, 但是, 必须要考虑创建临时队列的开销. 如果开销可以被接受, 那么这个方法是可取的,它完全消除了多个Search EJB组件之间数据混淆的可能性.
2. 使用响应队列池. 这个方法类似第一个方法,每个临时响应队列是从池中取出, 而不是每次都创建新的. 为了管理队列池, 必须开发一个额外的管理类或者是EJB 组件, 由它负责将池中的队列分派给某个Search EJB 组件实例, 这个管理类还要负责清理池中队列对象中的无用消息, Search EJB组件在每次处理完毕后,都要将临时队列归还池.
3. 使用JMS 的 Selector机制, 从响应队列中只取出相应的目标响应消息.JMS允许客户通过消息头来指定对自己有意义的消息数据.使用Selector,接收者只会接收到消息头和其他属性满足若干条件的消息.这时只存在一个响应队列, Search EJB组件创建一个键值,然后通过消息头传给Retailer MDBs, 然后Search EJB组件利用该键值创建一个Selector对象, 所有的Retailer MDBs都会在响应消息头加入该键值.Search EJB组件只会接收到消息头中包含该键值的消息.这个方法的缺点是当响应队列中的消息很多的时候,速度会下降.
/* Step 1: Create a unique key, use some class, say
UniqueKeyCreater. **/
String uniqueKey = UniqueKeyCreater.getKey();
String retailers [] = new String []{"A", "B", "C"};
/* Step 2: Put three messages in the REQUESTQ, one for each retailer.
Define a header called "KEY" and set its value to uniqueKey
Note: The retailer MDBs also have to set the same header/value
in the response messages. **/
QueueSender queueSender = queueSession.createSender(requestQueue);
Message message = queueSession.createTextMessage();
message.setStringProperty("KEY",uniqueKey);
for(int i = 0; i < retailers.length; i++) {
message.setText("Retailer:" + retailers[i] + ",Item:" + itemName);
queueSender.send(message);
}
/* Step 3: Now wait for messages in the response queue. Get only those
messages that have the header KEY with value set to uniqueKey.
Use a JMS selector for this purpose. The while loop will break if
all
three responses arrive in RESPONSEQ or 30 seconds are over. Even if
all three response messages do not arrive in 30 seconds, the code
will be out of while loop, ensuring a guaranteed response time of 30
seconds. **/
String selector = "KEY = '" + uniqueKey + "'";
QueueReceiver queueReceiver =
queueSession.createReceiver(responseQueue, selector);
long startTime = System.currentTimeMillis();
int messagesExpected = retailers.length;
long waitTime = 30000; // 30 seconds
Vector responses = new Vector(retailers.length);
while(messagesExpected > 0 && waitTime > 0 ) {
Message rcvdMsg = queueReceiver.receive(waitTime);
//Check if we got a msg, if not then break.
if (rcvdMsg == null){
//Wait time expired.
break;
}
responses.add(rcvdMsg);
messagesExpected--;
waitTime = 30000 - (System.currentTimeMillis() - startTime);
}
每个讨论过的方法都有各自的长处和缺点, 不同的应用, 都有各自适合的解决方法. 而合适的解决方案依赖于许多因素,例如负载峰值, 创建临时队列的开销, JMS服务的性能,服务器的配置等等. 有时候, 也可以将第二和第三种方法的混合使用,来用于一些大型的应用.
到目前为止, 我们讨论的都是并行处理是如何改善基于Web的应用的响应时间,但是使用并行处理也有另外的副作用: 应用程序的运行中,短时间内资源的开销提高很多. 对比使用串行处理机制的程序, 并行处理的负载峰值高很多.
在Price Buster应用中, 如果有20个客户同时发起请求, 存在10个数据源,那么,在非MDB的实现中, 将有20个Retailer EJB组件 , 而在MDB实现中,存在200个Retailer MDB. 因此, 后者的应用服务器需要提供更强力的性能, 不过,单是一个能够支持上千并发线程的服务器也不能完全解决问题, 因为并发处理同时提高了对数据源存储系统的负载峰值的要求.
基于MDB实现并发的系统除了能够提高响应时间, 还有个更重要的作用就是能够保证响应时间.举个例子, 对于不支持并发或者是非基于MDB实现的系统, 当系统运行正常的时候, 如果有三个查询数据源,那么响应时间大概在45秒, 但如果其中一个数据源出现故障, 响应时间延迟到200秒怎么办? 这个时间显然是不能接受的. 这时候一般会忽略出错的那个数据源,只返回正常的两个数据源的查询结果. 但不幸的是, 对于非MDB实现的系统, 并不容易做到这一点. 因为Search EJB组件对于Retailer EJB组件的调用是同步堵塞的, 所以是没有办法中断的. 当然, 如果只存在一个数据源, 那么固定响应时间的问题会容易解决很多. 但实际应用很多时候并非如此.
然而, 如果是基于MDB实现的系统,上面的问题就很容易解决了. Search EJB组件在发送完请求消息后, 就等待回应消息, 在一定时间内如果没有得到响应就超时返回. 可以通过调用queueReceiver对象的receive(long waitimeout)方法, 来设置等待的时间. 如果没有设置超时时间, 那么只有当收到响应消息的时候, 该方法才会返回. 为了保证固定的30秒响应时间, 可以把最长等待时间设置为28-29秒.
同时也要为组织和格式化结果预留一些时间. 即便不是所有的数据源都能在指定时间内返回查询结果, 程序也能够在30秒内返回数据. 对于那些速度太慢的数据源所返回的结果可以忽略不计. 对于一个存在多个数据源的系统来说, 应该要设置一个最长的等待时间, 因为不能永远保证所有的数据源都运行正常. 调用外部系统服务的时候, 总是应该设置一个超时时间的.
从上面的讨论可以看到, 对于那些大型的IO操作频繁的应用, 如搜索引擎, 并发的支持是不可缺少. 使用JMS和MDBs, 可以很容易的为基于J2EE架构的这类应用增加并发的支持. 基于MDB的解决方案有两个最主要的好处:
1. 对比线性处理, 支持并发处理, 提供更快的响应时间.
2. 保证固定的响应时间.
关于翻译作者:
wqx,职业程序员,喜欢健身,爬山,现居广州。可以通过[email protected]与他联系,或者点击http://www.matrix.org.cn/user_view.asp?username=wqx查看他的信息