使用BMT消息驱动BEAN和SPRING进行高性能的消息处理
2008-01-05 10:23:24 来源:WEB开发网简介
自从被引入EJB 2.0规范后,消息驱动bean (MDB)已经成为几乎所有企业java项目的基础。MDB拥有一个简单而整洁的API,用户不需要创建或生成home和local/remote接口,所有这些使得Java开发社区迅速且广泛地接受了MDB。如您所知,几乎任何大型J2EE项目都有一个重要部分是与其它系统的集成有关的。一种集成不同系统的可取方法是使用面向消息中间件(MOM)提供的企业队列(queue)和主题(topic)基础架构,这要求所有的J2EE服务器都拥有自己的兼容JMS的MOM实现。
看起来大部分企业J2EE项目取决于JMS消息的接收和/或发送。因此,J2EE架构设计者和开发者就一定要熟悉不同的消息消费和生产方法,因为这可能最终决定项目的成功或失败。最近,开始出现一些MDB模型的替代方案,例如SPRing和ActiveMQ的消息驱动POJO。当前的MDB模型除了本身是一项有趣有用的技术外,其地位也得以确定,它足够简单,并且在J2EE社区中得到了广泛的采用。
本文主要关注基于MDB的传统消费模型。本文将展示如何利用一种更高效的非事务型消息检索来重构这一场景,而同时仍然保持业务用例的once-and-only-once服务质量(QoS);换句话说,业务代码应该只处理消息一次,并且不应该丢失任何消息。这是最严格的服务质量,也是实现中最有趣的部分。
传统的消息消费模型
可以说,在大多数的J2EE项目中,只要出现消费JMS消息的用例,都会使用MDB。除了最普通的情况外,假如从业务角度来看丢失传入的消息和/或处理消息副本可以接受,这些MDB都使用容器治理事务(CMT)划分模型和事务型属性RequiresNew来部署。为了使这些设置可以生效,应当用事务型的QueueConnectionFactory(或TopicConnectionFactory)来配置MDB。在这样的设置中,消息消费过程可以用下面的步骤来描述:
- J2EE容器启动一个JTA事务。
- MDB所监听的队列/主题的XAResource被添加到事务中。
- 从队列/主题消费一条消息,并传递给MDB的onMessage()方法。
- MDB处理消息。任何在MDB处理消息时会使用的事务型资源(比如:数据库、其它队列或主题以及JCA适配器)会被添加到步骤1中启动的同一个JTA事务中(当然,只有在资源支持XA时才可以)。
- 假如处理成功(onMessage()调用无异常地返回),则J2EE容器会对JTA事务中的所有资源执行一次两阶段提交。因为JTA事务所拥有的资源之一是消费JMS消息的目的地,一旦JTA事务提交成功,消息就会从队列/主题中移除。
- 假如处理失败(onMessage()调用抛出RuntimeException 异常),则J2EE容器会对表示步骤3中消费JMS消息的目的地的XAResource执行回滚,因此消息会停留在队列/主题中,稍后可能被重新发送。此外,在这种情况下,MDB实例会被销毁并从处理池中移除。
如您所见,事务型消息消费的处理非常健壮。这样的处理模型保证了once-and-only-once的服务质量;换句话说,消息要么被成功地处理一次,要么根本不被处理(可能在超出预定义的重试次数或生存时间或者转移到停用(dead)消息队列之后,被从队列中移除)。
传统模型的缺点
事务型消息消费模型尽管健壮且成功,但是它还是有一些严重的缺点。首先,分布式事务严重地影响了处理性能(当从本地事务切换到XA时,性能降低50%是不足为奇的)。
第二个缺点是由CMT造成的,实际的事务提交发生在应用程序代码之外,在onMessage()调用返回之后。这也许没什么大不了的(究竟CMT的整体思想是要将应用程序从事务处理中解脱出来),但是存在一些令人不愉快的问题——一些错误情况直到事务提交后才进行侦测。例如,在BEA WebLogic Server中,默认情况下,所有由处理CMP bean(创建,更新等等)引起的DML操作都被延迟到事务提交阶段。这意味着应用程序可以认为它成功地更新了CMP bean的一个实例,而实际上实际的SQL更新可能会因为违反数据库的某种约束而失败。最糟糕的是应用程序代码不能够对此做出反应或者只是恰当地进行记录,因为它看不到异常。
优化提议
尽管对延迟的DML操作有一个应急方案(例如,在BEA WebLogic Server中,可以在部署描述符中禁用它),但是这伴随着性能损失。J2EE服务器将不能再聚集和/或批处理SQL更新(以便执行更高效),或者将它们全部忽略(假如事务稍后被标记为回滚的话)。
本文认为,采用一种bean治理事务(bean-managed transaction,BMT)方法可以提供同样的服务质量,并对事务生命周期有更多的控制。应用程序代码将有机会恢复和/或更清楚地报告错误,同时避免上述的CMT模型的所有缺点。此外,我们预料从事务作用域中移除消息检索能带来重大的性能提升。
在讨论BMT方法之前,我们需要分析在这种情况下从队列中消费消息会发生什么。假如我们使用BMT 划分部署MDB,J2EE服务器不会再把MDB监听的JSM目的地(队列或者主题)添加到事务中(事务将会在从队列中取走消息之后开始)。在这种情况下,BMT MDB应当用部署描述符中的非XA连接工厂配置;否则J2EE服务器会部署失败。
根据JMS规范(JMS1.1第一节4.5.2),假如使用AUTO_ACKNOWLEDGE 或者 DUPS_OK_ACKNOWLEDGE模式非事务型地部署消息监听器,并且onMessage()方法抛出RuntimeException或它的任何子类,则消息会被重新发送。换句话说,重新设计用例来使用BMT是有可能的,假如在处理消息时出错的话,应用程序代码可以抛出RuntimeException,消息就会被重新发送(重试)。这种方法很有效,因为使用RuntimeException来表示不可恢复的错误是很自然的(例如Spring Framework的异常层次结构基本上全是基于RuntimeException的子类)。消息会重新发送,直到达到一定的次数(可在MOM软件层配置),之后它通常被丢弃或转移到停用消息队列,或者应用程序代码会计算消息被重新发送的次数,并决定什么时候应当停止尝试处理以及不处理就消费(假如适当的话,会产生错误消息)还是转移到另外的队列。
更多精彩
赞助商链接