Java EE 应用程序中实现重新连接逻辑

在队列管理器发生故障时希望自动重新连接的 Enterprise Java Bean 和基于 Web 的应用程序需要实现自己的重新连接逻辑。

以下选项给出了有关如何实现此目的的更多信息。

允许应用程序失败

此方法不需要更改应用程序,但要求管理员将连接工厂定义重新配置为包含 CONNECTIONNAMELIST 属性。 但是,此方法要求调用者能够适当地处理故障。 请注意,对于与连接故障无关的故障(例如 MQRC_Q_FULL)也有此要求。

此过程的示例代码如下:
public class SimpleServlet extends HttpServlet {   
  public void doGet(HttpServletRequest request,                      
                    HttpServletResponse response)     
       throws ServletException, IOException {     
          try {      
 // get connection factory/ queue       
 InitialContext ic = new InitialContext();       
 ConnectionFactory cf =                 
           (ConnectionFactory)ic.lookup("java:comp/env/jms/WMQCF");  
 Queue q = (Queue) ic.lookup("java:comp/env/jms/WMQQueue");       

 // send a message       
 Connection c = cf.createConnection();  
 Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);  
 MessageProducer p = s.createProducer(q);    
 Message m = s.createTextMessage();  
 p.send(m); 

 // done, release the connection 
 c.close();
 }
 catch (JMSException je) {  
 // process exception 
   } 
 } 
}

上述代码假设此 servlet 使用的连接工厂已定义了 CONNECTIONNAMELIST 属性。

Servlet 首次处理时,将使用 CONNECTIONNAMELIST 属性创建新连接,假定没有来自连接到同一队列管理器的其他应用程序的合用连接可用。

当在 close() 调用之后释放连接时,会将此连接返回到池中,并在下次运行该 servlet 时进行复用(无需引用 CONNECTIONNAMELIST),直到发生连接故障(此时会生成 CONNECTION_ERROR_OCCURRED 事件)为止。 此事件提示池销毁发生故障的连接。

下次运行应用程序时,将没有可用的合用连接,并会使用 CONNECTIONNAMELIST 来连接到第一个可用的队列管理器。 如果发生队列管理器故障转移(例如,发生非暂时性网络故障),那么 servlet 将在备份实例可用时立即连接到该实例。

如果应用程序中包含其他资源(如数据库),那么可能需要指示应用程序服务器回滚该事务。

在应用程序内处理重新连接

如果调用者无法处理 servlet 中的故障,那么必须在应用程序内处理重新连接。 如以下示例中所示,要处理应用程序中的重新连接,需要应用程序请求新连接,以便它可以对从 JNDI 查找的连接工厂进行高速缓存,并处理 JMSException ,例如 JMSCMQ0001: WebSphere MQ call failed with compcode '2' ('MQCC_FAILED') reason '2009' ('MQRC_CONNECTION_BROKEN').
public void doGet(HttpServletRequest request, HttpServletResponse response) 
      throws ServletException, IOException { 

  // get connection factory/ queue 
  InitialContext ic = new InitialContext(); 
  ConnectionFactory cf = (ConnectionFactory) 
               ic.lookup("java:comp/env/jms/WMQCF"); 
  Destination destination = (Destination) ic.lookup("java:comp/env/jms/WMQQueue"); 

  setupResources(); 
  
  // loop sending messages 
  while (!sendComplete) { 
    try { 
      // create the next message to send 
      msg.setText("message sent at "+new Date()); 
      // and send it 
      producer.send(msg); 
    } 
    catch (JMSException je) { 
        // drive reconnection 
        setupResources(); 
    } 
  } 
在以下示例中, setupResources() 创建 JMS 对象并包含用于处理非瞬时重新连接的休眠和重试循环。 事实上,此方法会阻止许多重新连接尝试。 请注意,为了清晰起见,示例中已省略出口条件。
 private void setupResources() { 

    boolean connected = false; 
    while (!connected) { 
      try { 
        connection = cf.createConnection(); // cf cached from JNDI lookup 
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); 
        msg = session.createTextMessage(); 
        producer = session.createProducer(destination); // destination cached from JNDI lookup 
        // no exception? then we connected ok 
        connected = true; 
      } 
      catch (JMSException je) { 
        // sleep and then have another attempt 
        try {Thread.sleep(30*1000);} catch (InterruptedException ie) {} 
      } 
    } 

如果应用程序管理重新连接,那么无论这些资源是其他 IBM® MQ 队列管理器还是其他后端服务 (例如数据库) ,应用程序都必须释放与其他资源保持的任何连接。 当重新连接到新的 IBM MQ 队列管理器实例完成时,必须重新建立这些连接。 如果没有重新建立连接,那么在重新连接尝试期间,会导致不必要地占用应用程序服务器资源,并导致这些资源在复用之前便已超时。

WorkManager 的用法

对于处理时间超过几十秒的长寿命应用程序 (例如,批处理) ,可以使用 WebSphere® Application Server WorkManager 。 WebSphere Application Server 的代码片段示例如下所示:
public class BatchSenderServlet extends HttpServlet  { 
  
  private WorkManager workManager = null; 
  private MessageSender sender; // background sender WorkImpl 
  
  public void init() throws ServletException { 
    InitialContext ctx = new InitialContext(); 
    workManager = (WorkManager)ctx.lookup(java:comp/env/wm/default); 
    sender = new MessageSender(5000); 
    workManager.startWork(sender); 
  } 

  public void destroy() { 
    sender.halt(); 
  } 

  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException { 
    res.setContentType("text/plain"); 
    PrintWriter out = res.getWriter(); 
    if (sender.isRunning()) { 
      out.println(sender.getStatus()); 
    } 
} 
其中 web.xml 包含:
<resource-ref> 
      <description>WorkManager</description> 
      <res-ref-name>wm/default</res-ref-name> 
      <res-type>com.ibm.websphere.asynchbeans.WorkManager</res-type> 
      <res-auth>Container</res-auth> 
      <res-sharing-scope>Shareable</res-sharing-scope> 
   </resource-ref> 
而且,现在已通过 Work 接口实现了批处理:
import com.ibm.websphere.asynchbeans.Work; 

public class MessageSender implements Work { 

  public MessageSender(int messages) {numberOfMessages = messages;} 

  public void run() { 
    // get connection factory/ queue 
    InitialContext ic = new InitialContext(); 
    ConnectionFactory cf = (ConnectionFactory) 
               ic.lookup("java:comp/env/jms/WMQCF"); 
    Destination destination = (Destination) ic.lookup("jms/WMQQueue"); 

    setupResources(); 
  
    // loop sending messages 
    while (!sendComplete) { 
      try { 
        // create the next message to send 
        msg.setText("message sent at "+new Date()); 
        // and send it 
        producer.send(msg); 
        // are we finished? 
        if (sendCount == numberOfMessages) {sendComplete = true); 
      } 
      catch (JMSException je) { 
          // drive reconnection 
          setupResources(); 
      } 
  } 

  public boolean isRunning() {return !sendComplete;} 

  public void release() {sendComplete = true;} 

例如,如果需要较长时间来运行批处理(例如,因为大型消息、网速较慢或大量数据库访问(尤其是伴随着缓慢的故障转移)),那么服务器会开始输出挂起线程警告,这些警告类似于以下示例:

WSVR0605W:线程“WorkManager.DefaultWorkManager : 0”(00000035) 已保持活动 694061 毫秒,可能已挂起。 服务器中总共有 1 个线程可能挂起。

通过减小批处理大小或增加挂起线程超时,可以尽可能减少这类警告。 但是,一般而言,最好是在 EJB(针对批处理发送)或消息驱动的 bean(针对使用或使用并回复)处理中实现此处理。

请注意,应用程序管理的重新连接未提供用于处理运行时错误的通用解决方案,应用程序仍必须处理与连接故障无关的错误。

例如,尝试将消息放入已满队列 (2053 MQRC_Q_FULL) 或者尝试使用无效的安全凭证连接到队列管理器 (2035 MQRC_NOT_AUTHORIZED)。

应用程序还必须处理在进行故障转移期间没有可立即使用的实例时发生的 2059 MQRC_Q_MGR_NOT_AVAILABLE 错误。 这可以通过应用程序在发生 JMS 异常时立即报告异常来实现,而不是以静默方式尝试重新连接。