|
重新发送 JMS 消息的问题涉及两个方面。一个方面关系到消息在不需要的时候被重新发送,另一个方面关系到消息在需要的时候却没有被重新发送。本文只说明这个问题的第一个方面。 |
|
问题描述 JMS 消息被多次重新发送给接收器/订阅用户。一旦有消息到达 JMS 目标,JMS 服务器试图将其发送给有效用户然后等待用户确认。无论什么原因,只要 JMS 服务器没有收到消息确认,它就会重新发送消息。从 JMS 服务器的角度来看,在 JMS 消息被确认收到以前,该消息就不被视为“已发送”。故障症状包括:
- JMS 接收器的 onMessage 方法被多次执行。
- 服务器进程可能会因“毒性消息”而减慢。
毒性消息是接收者必须拒绝的消息。通常,消息可能会因消息本身的问题而被拒绝,但是消息还可能会因临时的资源中断而被拒绝。
|
故障排除 请注意,并非下面所有任务都需要完成。有些问题仅通过执行几项任务就可以解决。
快速链接
|
| 为什么 JMS 消息被重新发送?
JMS Server 可能会因下列任一原因而重新发送消息:
- java.lang.Error 或 java.lang.RuntimeException 已经从接收器/MDB 的 onMessage 方法中抛出
- 用户已经在其 MDB 的 onMessage 方法中调用了 ejbcontext.setRollbackOnly()(这仅适用于容器管理的事务)
- 参与事务的 MDB 因某种原因而失败。例如,如果该 MDB 调用另一个 EJB,这会进行 JDBC 调用并导致数据库操作失败。在这种情况下,该 MDB 所参与的事务将被回滚,消息将被重新发送。
- MDB 是事务性的,但是需要很长的时间进行处理,事务监视器将使事务超时并将其回滚,因此,JMS 将重新发送消息。
- 服务器进程减慢,这可能是由于毒性消息而引起的。
- 用户已经从独立的接收器调用 Session.recover()
- Session.acknowledgement() 不是从具有客户端确认的独立接收器调用的
返回页首
|
应用程序编码诊断
将以下代码片断添加到接收器的 onMessage 方法中,以便发现消息是否被重新发送。 |
|
"Public void onMessage (javax.jms.Messsage msg)
{ try{ System.out.println ("Mesg ID:"+msg.getJMSMessageID ()); System.out.println ("Is Message redelivered:"+msg.getJMSRedelivered ()); ... }
}
|
| 如果同一个消息 ID 被打印多次,而且 JMSRedelivered() 标志返回 true,则它会确认消息被重新发送。
返回页首
|
排除 JMS 消息重新发送问题的检查清单
- 检查接收器类型 - 它是 MDB、独立同步接收器还是异步接收器?
- 检查接收器的确认选项:
- 使用的是确认模式、事务会话还是 Bean 托管?
- 事务还是容器管理的事务?
- 如果是CMT、BMT 的 MDB 或独立接收器,验证 onMethod () 是否成功返回。
- 如果从独立接收器/MDB 的 onMessage() 方法中抛出了 java.lang.Error 或 java.lang.RuntimeException,则 JMS Server 不会收到确认,消息将被 JMS Server 重新发送。
- 如果 MDB(仅限 CMT)调用 ejbcontext.setRollbackOnly(),这将强制 JMS Server 重新发送消息(因为事务已被标记为回滚)。
- 如果 MDB 调用包含 JDBC 调用的 EJB(会话或实体),而且 DB 操作因某些未知原因而失败,则在其中登记 MDB 的事务将被回滚,消息将被重新发送。为了避免发生此问题,请探查 EJB 端的问题。
- 如果 MDB 的 onMessage() 方法(仅限 CMT)长时间运行,则事务将超时并且被回滚。因此,JMS Server 将重新发送消息。
- 在最近的版本中,如果参与 TX 的 MDB 超时,则消息将被重新发送,而且不出现任何异常。请参考 CR103047。
- JMS 消息重新发送问题主要是由于应用程序编码错误引起的。通过启用下列调试标志,可以发现消息重新发送问题的根本原因。
- DebugJMSMessagePath="true" DebugJMSXA="true"
- 在 config.xml 中,在每个 Server 标记的 ServerDebug 标记下,设置下列调试标志:
- <ServerDebug DebugJMSXA="true" DebugJMSMessagePath="true" Name="myserver"/>
- 确保服务器的 stdOutSeverity 级别是 INFO,StdoutDebugEnabled 设置为“true”。
- 如果 session.recover() 在 CLIENT_ACKNOWLEDGE 模式下调用(仅限独立接收器),JMS Server 就会停止在该会话中发送消息,并针对未确认时间最长的消息重新启动消息发送。
- 为了避免出现毒性消息,可以针对 JMS 目标配置下列参数。
- 重新发送延迟时间
- 当消息被回滚或恢复时,重新发送延迟时间是在试图重新发送消息之前消息被闲置的时间。
- 重新发送极限
- 重新发送极限是 JMS Server 试图将消息重新发送到接收器/MDB 的次数
- 错误目标
- 如果 JMS Server 试图重新发送的次数大于“重新发送极限”,JMS Server 会将这些未发送的消息转发到“错误目标”。如果未配置错误目标,而且消息超出了其重新发送极限,就会删除该消息。
- 调试处理此问题的另一种方法是打印/记录 onMessage() 方法中的异常。下面的代码片断将记录异常堆栈跟踪,还将从接收器的 onMessage() 方法重新抛出异常。
|
public void onMessage(Message msg) { try { TextMessage tm = (TextMessage) msg; String text = tm.getText(); System.out.println("Msg: " + text+" Mesg ID:"+msg.getJMSMessageID()); } catch(JMSException ex) { ex.printStackTrace(); } catch(java.lang.RuntimeException ey) { ey.printStackTrace(); throw ey; } catch(java.lang.Error ez) { ez.printStackTrace(); throw ez; }
}
|
返回页首
|
了解 JMS 消息重新发送调试信息 JMS 调试标志 DebugMessagePath 和 DebugJMSXA 提供有关消息重新发送问题根本原因的详细信息。DebugMessagePath 将有助于确定消息是否被重新发送。 |
|
<Feb 3, 2004 5:01:25 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging MSG_PATH! BACKEND/BEQueue: Assigning to the backend consumer, message ID:P<802808.1075856485894.0>>
<Feb 3, 2004 5:01:25 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging MSG_PATH! BACKEND/BEQueue: Adding backend session's unacked message list, message ID:P<802808.1075856485894.0>>
<Feb 3, 2004 5:01:25 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging MSG_PATH! BACKEND/BEQueue: Dispatching to the frontend, message ID:P<802808.1075856485894.0>>
<Feb 3, 2004 5:01:25 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging MSG_PATH! FRONTEND/FESession (id: <576180353183090550.27>) : Pushing to the client, message ID:P<802808.1075856485894.0>> >>Print From the onMessage method: I am the MESSAGE, MsgID: ID:P<802808.1075856485894.0> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <Feb 3, 2004 5:02:05 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging MSG_PATH! BACKEND/BEQueue: Assigning to the backend consumer, message ID:P<802808.1075856485894.0>>
<Feb 3, 2004 5:02:05 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging MSG_PATH! BACKEND/BEQueue: Adding backend session's unacked message list, message ID:P<802808.1075856485894.0>>
<Feb 3, 2004 5:02:05 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging MSG_PATH! BACKEND/BEQueue: Dispatching to the frontend, message ID:P<802808.1075856485894.0>>
<Feb 3, 2004 5:02:05 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging MSG_PATH! FRONTEND/FESession (id: <576180353183090550.34>) : Pushing to the client, message ID:P<802808.1075856485894.0>> >>Print From the onMessage method: I am the MESSAGE, MsgID: ID:P<802808.1075856485894.0>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
从上面的调试输出结果可以看出,同一个消息被多次发送给接收器。
备注:此调试标志适用于 WLS 8.1 和更高版本。
DebugJMSXA 将有助于确定消息是否因事务相关问题而被重新发送。 |
<Feb 3, 2004 5:16:10 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging XA ! XA(25421973,1007511,0000013BC2697F28EDB9) >RM-rollback() >
<Feb 3, 2004 5:16:10 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging XA ! XA(25421973,1007511,0000013BC2697F28EDB9) >TE-recv-startRollback() (TE-recv hash=31838215 xid=0000013BC2697F28EDB9 mId=<712671.1075857330638.0> queue=TestDest)>
<Feb 3, 2004 5:16:10 PM PST> <Debug> <JMS> <BEA-040002> <JMS Debugging XA ! XA(25421973,1007511,0000013BC2697F28EDB9) <TE-recv-startRollback() (TE-recv hash=31838215 xid=0000013BC2697F28EDB9 mId=<712671.1075857330638.0> queue=TestDest)OK>
<Feb 3, 2004 5:16:10 PM PST> <Debug> <JMS> <BEA-040002>
<JMS Debugging XA ! XA(25421973,27221567,0000013BC2697F28EDB9)
>RM-rollback() >
|
返回页首
返回页首
|
背景信息 一旦 JMS 消息传送到用户,JMS Server 将等待用户的确认。用户可通过四种方法向 JMS Server 发送确认:
- 事务会话
- 容器管理的事务
- Bean 托管的事务(或)用户事务
- 确认模式
事务会话 当 JMS 事务会话用于发送/接收 JMS 消息时,会在内部启动一个本地事务。此事务的范围限制为在该会话内部的发送/接收操作。当接收器/用户使用事务会话时,消息重新发送取决于针对此会话的提交/回滚调用。接收器对此会话的回滚调用将使得 JMS Server 重新发送消息。JMS 事务会话的事务在此会话范围之外没有任何影响。因此,它不能参与外部事务(BMT 或 CMT)(只要它没有 XA 识别功能)。
容器管理的事务 (CMT) 容器管理的事务适用于消息驱动 Bean。为了在容器管理的事务中运行 MDB,ejb-jar.xml 中的 <transaction-type> 标记和 weblogic-ejb-jar.xml 中的 <transaction attribute> 应当分别设置为“Container”和“Required”。例如:
在从目标拉出消息之前,EJB 容器启动某个事务并将其发送到 MDB。一旦 onMessage() 方法没有出现任何异常就成功返回,事务就由该容器提交。EJB 容器负责开始、提交和回滚此事务。如果 TX 由于某种原因而被回滚,JMS Server 就必须将消息立即重新发送到下一个有效用户。
Bean 托管的事务 (BMT) 或用户事务 Bean 托管的事务对于 MDB 和独立用户均适用。对于 Bean 托管的事务或用户事务,应用程序代码应当负责开始、提交或回滚事务。下面的代码片断示例创建一个用户事务。 |
|
UserTransaction tx1 =(UserTransaction)context.lookup("javax.transaction.UserTransaction") public void onMessage(Message msg) { try { tx1.begin(); String msgText = ((TextMessage)msg).getText(); System.out.println("Mesg ID:"+msg.getJMSMessageID()+"msg:"+msgText); tx1.commit(); } catch (Exception jmse) { jmse.printStackTrace(); }
}
|
| 为了配置 MDB 以便在 Bean 托管的事务中运行,ejb-jar.xml 中的 <transaction-type> 标记值应当设置为“Bean”。
在 BMT 中,因为事务是在 MDB 内部启动的,所以从目标中拉出消息不属于事务本身。因此,无论事务的结果是提交还是回滚,都不会影响消息重新发送。即使事务被回滚,消息也不会重新发送。
确认模式 确认模式可以在创建 JMS 会话时指定。确认模式可以设置为下列任一值:
- AUTO_ACKNOWLEDGE
- DUPS_OK_ACKNOWLEDGE
- CLIENT_ACKNOWLEDGE
下面的示例代码片断将确认模式设置为 AUTO_ACKNOWLEDGE。
|
| QueueSession
qs = qconn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); |
|
对于 AUTO_ACKNOWLEDGE 和 DUPS_OK_ACKNOWLEDGE,将自动确认从 onMessage() 方法成功返回的消息。
对于 CLIENT_ACKNOWLEDGE 模式,针对要确认的消息显式调用 message.acknowledge()。
如果 MDB 不在 Tx 中运行,则它可以在下列某个模式下运行:
- TO_ACKNOWLEDGE
- DUPS_OK_ACKNOWLEDGE
MDB 不支持 CLIENT_ACKNOWLEDGE 模式
对于独立的同步监听器,如果消息因某种原因而未被确认,则消息将只被重新发送一次。如果重新发送失败,则消息将从 JMS Server 中隐式删除。因此,重新发送计数将不进行更新,消息将不会被重定向到错误目标(即使针对此目标有相关设置也是如此)。 |
返回页首
|
反馈
请给我们提供您的意见,说明此支持诊断模式“排除 JMS 消息重新发送故障”一文对您有何帮助,您需要的任何解释,以及对支持诊断模式的新主题的任何要求。
|
|
免责声明
依据 BEA 与您签署的维护和支持协议条款,BEA Systems, Inc. 在本网站上提供技术技巧和修补程序供您使用。虽然您可以将这些信息和代码与您获得 BEA 授权的软件一起使用,但 BEA 并不对所提供的技术技巧和修补程序做任何形式的担保,无论是明确的还是隐含的。
本文档中引用的任何商标是其各自所有者的财产。有关完整的商标信息,请参考您的产品手册。
|
返回页首
|