D Using the JMS Destination Availability Helper APIs with Distributed Queues

The following sections provide information on how to use the JMSDestinationAvailabilityHelper API to provide a means for getting notifications when destinations become available or unavailable.

Note:

This guide includes advanced information for experienced JMS developers. Oracle recommends that you use Message Driven Beans when interacting with Distributed Queues. The MDB container automatically creates and closes internal consumers across all members of a Distributed Queue as needed. It also handles security, threading, pooling, application life cycle, automatic reconnect, and transaction enlistment. If you cannot use MDBs, you can also consider simpler workarounds, such as periodically restarting consumers to rebalance consumers across a distributed queue, or, if messaging ordering and performance are not a concern, enabling the distributed queue forwarding option.

Introduction

A distributed queue (DQ) is a group of JMS physical queues that is accessed as a single logical destination. Messages are load balanced across members, and clients can failover between member destinations.

Distributed destination users that don't leverage MDBs may encounter problems with consumer applications. These include:

  • Failing to ensure that all DQ members are serviced by consumers.

  • Unprocessed messages accumulating on DQ members that have no consumers.

  • DQ Consumers not automatically rebalancing in the event of a JMS server migration, WebLogic Server restart, or any other event that results in DQ member changes.

To address these use cases, WebLogic Server includes the JMS Destination Availability Helper APIs.

Controlling DQ Producer Load Balancing

Before discussing consumer load balancing, it is helpful to first explore producer load balancing basics and best practices.

Basic JMS

A JMS program sets up message sends in three stages:

  1. Clients create a JMS connection into WebLogic using a JMS connection factory.

  2. Clients use the connection to create JMS sessions and senders.

  3. Clients use the senders to send messages.

In WebLogic JMS, the WebLogic server that the client is connected to is called the client's connection host, and messages always route from the sender, through its connection host, and then on to a destination that's in the same cluster as the connection host. Connections stay pinned to their connection host for the life of the connection.

A WebLogic connection factory can be targeted at one or more WebLogic servers. If a client is running on the same WebLogic server where a connection factory is targeted, then the factory always returns a connection with a connection host that is the same server as the client (the connection is local). On the other hand, if a client is not running on a WebLogic server that is included in its connection factory targets, the factory automatically load balances among the targets and returns a connection to one of them.

Senders to Distributed Queues (DQs)

When working with a distributed destination, senders should always send to the JNDI name of the DQ (its "logical name") instead of sending to the JNDI names of the individual members, as this enables automatic load balancing behavior.

The default behavior for a sender to a DQ is if there are members that run on the sender's connection host, all sent messages go to one of these local members, otherwise messages round-robin among all members.

To force messages from the same DQ sender to round-robin among all active members even when local members reside on the sender's connection host, use a custom connection factory with Server Affinity set to false and Load Balance set to true.

Using the JMS Destination Availability Helper API

The following sections provide information on how to use the JMSDestinationAvailabilityHelper APIs:

Overview

When a consumer is created using the client javax.jms API and a DQ logical JNDI name is specified, the consumer is load balanced to an active DQ member and remains pinned to that member over its lifetime. If new members become active after all consumers have been created, the new members have no consumers.

The JMSDestinationAvailabilityHelper APIs provide a means for getting notifications when destinations become available or unavailable. These notifications can help ensure that an application creates consumers on all DQ members even when there are unavailable members at the time the application is initialized. The same mechanism can also be used to detect availability of other types of destinations (not just WebLogic distributed destinations, but also regular destinations and foreign vendor destinations).

Applications register a notification listener with the helper by specifying JNDI context parameters and the JNDI name of a destination. For DQs, the helper notifies listeners when members become available and unavailable, as they are undeployed, added as a new member, migrated, shutdown, or restarted.

Note that MDBs in WebLogic Server internally use this same mechanism for both local MDBs (deployed in the same cluster as a DQ) and remote MDBs (deployed in a cluster that is separate from the cluster that hosts the DQ). MDBs provide an out-of-the-box solution that achieves the same dynamic adaptability to DQ topology changes that the JMSDestinationAvailabilityHelper APIs provide.

General Flow

Applications that use the JMSDestinationAvailabilityHelper APIs should follow these general steps:

  1. Implement the weblogic.jms.extensions.DestinationAvailableListener interface to provide behavior as per step 3 below.

  2. Register interest with the helper by specifying JNDI context properties (typically just a URL and context factory), the JNDI name of the destination, and a listener instance. Do not specify a URL if the client is running in the same cluster as the DQ.

    import java.util.Hashtable;
    import javax.naming.Context;
    import weblogic.jms.extensions.JMSDestinationAvailabilityHelper;
    
    Hashtable contextProps = new Hashtable();
    contextProps.put(javax.naming.Context.PROVIDER_URL, myURL);
    contextProps.put(Context.INITIAL_CONTEXT_FACTORY,                 "weblogic.jndi.WLInitialContextFactory");
    JMSDestinationAvailabilityHelper dah =    JMSDestinationAvailabilityHelper.getInstance();
    
    RegistrationHandler rh = dah.register(
       contextProperties,
       destinationJNDIName,
       myDestinationAvailableListener
    )
    
  3. Handle listener callbacks. Callbacks are single-threaded, so no two callbacks occur concurrently.

    1. onDestinationsAvailable()—Typically the first notification. Implementations of this callback usually react by creating zero or more consumers on each given destination, and if this fails, periodically retrying.

    2. onDestinationsUnavailable()—This callback is usually used to destroy existing consumers on the destination.

    3. onFailure()—This callback is normally used simply to log the given failure. The helper continues to retry internally and make subsequent callbacks, but administrators may need to see the failure. The helper makes a best effort to just call onFailure() once for the same repeated failures.

  4. When done, unregister interest in a destination by calling rh.unregister().

Handling weblogic.jms.extension.DestinationDetail

As described previously, an onDestinationsAvailable() notification indicates that a stand-alone destination, foreign destination, or distributed destination member has become available. The notification consists of a list of DestinationDetail instances, where key information is obtained by calling getDestinationType(), getJNDIName(), isLocalWLSServer(), and isLocalCluster() on each Detail.

The destination detail helps determine the actions that the caller should take. If the destination is of type DD_QUEUE then the detail's getJNDIName() method returns the JNDI name of a specific DQ member and the caller may or may not want to deploy instances of the application consumer on the member. If the destination is of type PHYSICAL or FOREIGN, the application treats the destination as a regular destination.

Especially when working with DQs, it is highly recommended to take advantage of the co-location flags in DestinationDetail. You can determine the co-location nature of a destination by calling isLocalWLSServer(), and isLocalCluster(). See Best Practice for Local Server Consumers.

For more details on APIs and their methods, see "DestinationDetail" in Oracle WebLogic Server API Reference.

Best Practices for Consumer Containers

The following sections provide best practice guidelines for consumer containers:

When to Register and Unregister

  1. Register with JMSDestinationAvailabilityHelper at application deployment time. Do not fail the deployment if the helper calls the onFailure() callback on your listener (assume it could be an intermittent failure).

  2. Unregister with JMSDestinationAvailabilityHelper at application undeployment time.

URL Handling

  1. If the client is running on the same server or same cluster as the destination, don't specify a URL when registering with the helper or creating a JNDI context. This ensures that the helper creates a local context.

  2. Consider logging a single warning if isLocalCluster() or isLocalServer() returns true, but a URL was specified (as no URL is needed in this case).

Failure Handling

  1. Log the errors reported by onFailure() notifications, so that the application developer can have a chance to correct possible configuration/application errors. Avoid repeatedly logging the same exception. The helper continues to retry internally and make subsequent callbacks on success or different types of failures, but administrators may need to see the failures. The error may be caused by an application or administrative error such as an incorrect URL, invalid security information, or non-existent destination. It might also be caused by temporary unavailability of the JNDI context host or the destination.

  2. When a JMS call throws an exception, or when a JMS connection exception listener reports a connection failure, close the connection. Once all resources have been cleaned up, then periodically attempt to re-initialize all resources. Re-initialization generally involves creating a context, performing JNDI lookups, and then creating a connection, session, and a consumer.

  3. Avoid immediately retrying after a failure. Instead periodically retry every few seconds to avoid overloading the server.

JNDI Context Handling

  1. Share the same JNDI context between multiple threads for JNDI lookups.

  2. Call close() on a context on undeploy to prevent a memory leak.

  3. Call close() on a context and recreate on any failure (including a lookup failure).

JMS Connection Handling

  1. For JMS connections, always register a standard JMS connection "exception listener".

  2. On an onException(), close the connection and periodically retry JNDI lookups, recreating a JMS connection, and setting up consumers in another thread.

  3. Close connections on undeploy to prevent memory leaks.

  4. Instead of sharing a WebLogic Server connection among multiple sessions, consider creating one connection per session. With WebLogic Server, multiple connections allow for better load balancing. There's no performance penalty when working with WebLogic Server, but this might have unexpected overhead with foreign vendors, as some foreign vendors create a TCP/IP socket or a similarly expensive resource for each connection.

Interoperability Guide-Lines

The "JMSDestinationAvailabilityHelper" in Oracle WebLogic Server API Reference includes details about usage and behavior of the various methods available, including details about interoperability guidelines discussed in the following sections:

API Availability

The public JMS Destination Availability Helper API is available on AS11gR1PS2 (WebLogic Server version 10.3.3) and later clients and servers.

Foreign Contexts

The context properties that are specified when registering a notification listener with the DA Helper can resolve to any valid JNDI context, including contexts from foreign vendors and older versions of WebLogic Server.

For foreign (non-WebLogic) contexts, the foreign JNDI vendor's classes must be in the current classpath and the Context.INITIAL_CONTEXT_FACTORY property must reference the foreign vendor JNDI context factory class name.

Destination Type Support

The JMSDestinationAvailabilityHelper API works with any type of destination that can be registered in a JNDI context, including non-distributed destinations and foreign vendor destinations. However, unavailable notifications are only generated for DQ members and certain DestinationDetail fields apply only to DQ members. Unavailable notifications do not apply to foreign destinations.

Unavailable Notifications

Unavailable notifications only apply to DQ type destinations.

Interoperating with Pre WebLogic Server 9.0 Distributed Queues

When interoperating with a WebLogic Server 9.0 or later DQs, the DA Helper generates notifications for each individual member of the DQ, but when working with versions prior to 9.0, the helper only generates a single DestinationDetail notification which contains the logical JNDI name for the DQ destination and getDestinationType() returns PHYSICAL.

Pre WebLogic Server 9.0 DQs are therefore usually treated as a regular destination, and consequently will suffer with from the same problems outlined in the introduction.

DestinationDetail Fields

The behavior of some destination detail fields changes based on the type of destination, the JMS vendor, and, when working WebLogic JMS, the WebLogic Server version. See "JMSDestinationAvailabilityHelper" in Oracle WebLogic Server API Reference.

Strategies for UDQ Consumers

A consumer application can be either running in the same JVM of a WebLogic Server or not, which are called a "server side consumer" and "stand-alone consumer" respectively.

While a JMS UDQ consumer is deployed on a WebLogic Server or cluster, the application can either run on the same cluster/server as the UDQ, or on a different cluster. We call these two different application configurations the local case and the remote case respectively.

It is strongly recommended that server side applications use WebLogic Server MDBs when possible.

For application that cannot use MDBs in their application architecture for some reason, the following guidelines should be followed:

General Strategies

In order to for an application to receive all messages that are sent to a UDQ, the application needs to make sure that it creates one consumer on each member of the UDQ using the member JNDI name. This requires that applications know the topology of the domains and UDQ configuration, and this is where JMSDestinationAvailabilityHelper can help.

The general strategy is that each deployment instance of a particular application should register with JMSDestinationAvailabilityHelper. The listener will receive notifications about member availability.

  • Upon receipt of an onDestinationsAvailable() notification, the application gets a list of DestinationDetail instances for all available members, and then it needs to create one or more consumer instances using the member JNDI name for each member in the list. For remote consumers, each instance of the application should create a consumer on each member of the UDQ. For local consumers, the application should create a consumer on the local UDQ member only. See Best Practice for Local Server Consumers for more details.

  • Upon receipt of an onDestinationsUnavailable() notification, the application gets a list of DestinationDetail instances for all destinations that becomes unavailable since the last notification. Then for each member destination in the list, the application needs to find the consumer previously created for the member destination and close it.

Best Practice for Local Server Consumers

When an application is deployed on the same cluster as the JMS distributed destination, the application should be deployed on the same set of the servers that hosts the UDQ whenever possible. Under this configuration, for best performance, the application should receive messages only from the local members; local members can be determined using the DestinationDetail isLocalWLSCluster() and isLocalWLSServer() calls. This approach yields high performance because all messaging is local (it avoids transferring messages over network calls), and still ensures that all members are serviced by consumers.

In some use cases, the local server optimization network savings does not outweigh the benefit of distributing message processing for unbalanced queue loads across all JVMs in a cluster. This is especially a concern when message backlogs develop unevenly throughout the cluster, and message processing is expensive. In these use cases, the optimization should be avoided in favor of the general strategy model for remote consumers.