技术开发 频道

实现ESB与异构应用的无缝集成

ESB的作用

    ESBConfiguration文档描述了ESB的作用(behavior)。ESBRouter MDB根据描述文件里的路径载入这个XML文档,然后文档中的信息便被转换成了如图2所示的数据结构。

图2 内存中的配置数据结构

    ESBRouter(通过ESBConfigManager)将使用这些信息对路由代码进行译解、应用已有的转换、并进行安全授权检验。这里要特别提一下依赖性注射技术(Dependency Injection,以及继承inheritance),它已被用来分离ESB的各种功能(比如多协议消息传输和消息转换),使ESB获得更高的扩展性和可定制性。

    如类关系图中所示,ESB设计中有两个关键的接口程序:TransformHandler和TransportHandler.你可以在这里编写路由消息的转换和传输实现方法。然后,这些实现类通过ESBConfiguration里的Bean元素与路由相连。比如,在上面所例的ESBConfiguration.xml文档里,用以下bean定义描述了具体的传输处理程序:
                       
     <Bean name="creditJMSTransport" className="com.foo.esb.transport.jms.JmsHandler">
         <Property name="ConnectionFactory" type="java.lang.String">
            <Value>myQCF</Value>
         </Property>
         <Property name="Destination" type="java.lang.String">
            <Value>myCreditQueue</Value>
         </Property>
      </Bean>

    然后可以在Route节点里通过插入TransportHandler子元素来引用这个传输处理程序,如下所示:
                       
  <TransportHandler beanName="creditJMSTransport"/>

    注意:本文中定义的传输与转换处理程序使用了Java接口。因此,所有新加的处理程序可能都要实现必需的接口,这可能比较繁琐。不过你可以修改ESBConfigManager,通过依赖性注射方式来调用实现类中的任意方法,从而消除编写实现接口的需要。然而ESBRouter总会传送一个javax.jms.Message实例,因此处理程序的实现类还是必须要用javax.jms.Message类型。

    现在我们来慢慢看一下ESB在接收到一个发向特定的路由(服务或应用)的消息后的处理过程。简单起见,我们假设所论及的这两个应用通过JMS进行交互。

1. 首先,根据配置描述符生成ESBRouter MDB的几个基本实例。
   然后调用ESBRouter的ejbCreate()方法来完成以下任务:
1. 根据“java:comp/env/EnvConfigUrl”环境项中描述的URL载入EnvConfig.xml文件。
2. 从EnvConfig.xml中读取ESBConfiguration.xml的地址(URL)并将其解析为XmlBean。
3. 创建并初始化EsbConfigManager实例——这个过程要创建并初始化EsbConfiguration.xml中所有的bean,并配置图2中的数据结构。
4. 初始化EsbRouterMonitorMBean实例。(这一步稍后详谈。现在,我们只需要知道它有利于实现ESB JMX(Java管理扩展)。)
 如果一个服务组件要通过ESB向另一个服务组件发送一个消息(M),它就会把JMS消息放到ESB输入队列中,从而产生对EsbRouter的onMessage()方法的调用。主要是以下几个步骤:
1. 根据传入的M消息中描述的属性读取目标路由R。这里所描述的属性、以及后面将用到的其它属性都是可以在EnvConfig.xml中进行设置的。
2. 在EsbConfigManager实例中查找与R相对应的RouteInfo。
3. 对请求者进行授权验证,确定其向目标路由发送消息的合法性。如果不合法,消息便会被丢弃。你也可以对这个处理行为进行配置,比如把这个消息记录到一个特定的队列中,或者请示管理员。
4. 如果配置了相应的TransformHandler bean,那么就能在EsbConfigManager实例中查找到,并通过发送自变量M消息调用TransformHandler的transformMessage()方法。
5. 对于每一个与路由R相应的TransportHandler,EsbRouter都会调用它的transportMessage()方法并传送M参数。
6. (if)如果没有在EsbConfigManager中找到与路由R相应的项,(then)就会开始查找与无效路由相应的TransportHandler。然后消息M就会被发送到无效路由。
(else if)此外,如果找到了该路由的处理程序,但是TransportHandler无法传送这个消息或者出现系统故障,(then)消息M就会被放到队列中等待重新发送。重新发送等待队列将激活以下步骤:
1. 在EsbConfigManager中查找给定路由的重新发送规则。
2. 根据重新发送规则中定义的重试间隔计算出下一次尝试发送的时间。
3. 这个时间会被设置为与消息相关的MessageRedeliveryTime属性,并发送到重试队列中。
4. 另一个重新发送请求消息被发送到重新发送请求主题中。重新发送请求是一个javax.jms.ObjectMessage,它包含一个代表下一次发送时间的Long对象。

    消息的再次发送:时间安排与处理

    接收到再次发送消息的请求后,RedeliveryRequestProcessor MDB会创建一个RedeliveryTask实例。然后用重新发送的时间(这个时间在请求消息的Long对象中)对RedeliveryTask进行初始化,并通过RedeliveryScheduler进行安排。RedeliveryScheduler是一个独立的类,它把编排任务分派给相关的java.util.Timer(见类关系图)。预定的时间一到,RedeliveryTask中的run()方法就被会被调用,它将尝试从重新发送队列中获取预定的消息。其中,消息接收器receiver包含一个消息选择器,只接收MessageRedeliveryTime属性符合预先编排的时间的消息。然后这个消息就被发送到ESB的输入队列中。

    以R为目标路由的消息M的重新发送过程如下所示:

1. 在EsbConfigManager中查找给定路由的重新发送规则。
2. 从M中读取包含“已有的发送尝试信息”的属性。(If)如果没有这种属性,(And)并且retry规则中定义的重试次数上限为零,(Then)那么消息M就会被发送到无效路由,并且不再继续进行下面的步骤。
3. (Else if)另外,如果重试次数已经超出retry规则定义的上限,(Then)M就会被发送到与R相关的无效路由。

    (Else)其它情况

1. 重新发送尝试计数增加1
2. 根据重新发送规则中定义的重试间隔计算出下一次尝试发送的时间。
3. 尝试次数的计数与下一次尝试时间都设定为M的属性。
4. 消息M就会被发送到重新发送路由。然后重新发送路由从TransportHandlerCache中查找合适的TransportHandler。
5. 一个重新发送请求消息被发送到重新发送请求路由。这个消息只包含一个安排重新发送消息M的时间信息(在第二步中计算得到的时间,计算方式与java.lang.Long一样)。

    如果在重新发送过程中有什么故障,消息M就会被发往与路由R相关的无效路由;如果这个过程又由于某些原因出现错误,消息M就会被发送到错误处理路由;如果发送到错误处理路由的过程又失败了,那么EsbRouter的onMessage()方法的处理操作就会回滚,结果就是消息M被放回ESB的输入队列。

    注意:我们用主题(topic)代替队列(queue)发送重试请求是因为大多J2EE容器不支持标准的定时服务——虽然它们可能会提供专有的API来完成类似的任务。本文所描述的ESB实现使用以单独类方式(singleton)封装的java.util.Timer实例进行计时。那么使用主题到底有什么优势呢?假使我们使用队列代替主题。我们可以设想一下,在一个部署了RedeliveryRequestProcessor MDB的群集环境中。如果只有一个服务器能够接收到队列中的请求消息。这个服务器把RedeliveryTask编排到将来的某个时间执行,而这时这个服务器突然故障了,就可能会导致无法及时发送消息,进而导致消息失效。而使用主题的话,请求消息就会发送到集群中所有的RedeliveryRequestProcessor MDB实例。因此即使一个服务器发生故障,其它的仍然会继续进行处理。

    在正式部署ESB的时候,我们可以为无效路由分配一个通用或单独的消费者(consumer,比如MDB)。这些消费者将从无效路由中提取消息并通知相关的管理员或应用程序。

0
相关文章