创建健壮的JMS应用(Creating Robust JMS Applications)《译》待续

 

创建健壮的JMS应用

本章介绍了如何应用JMS的特性来获得你的应用需要的可靠级别和性能。很多人选择实现JMS应用是因为他们不能忍受中止的或者重复消息被发送,并且每条消息能被接受并且只能接受一次。JMS API提供了这些功能。

This section explains how to use features of the JMS API to achieve the level of reliability and performance your application requires. Many people choose to implement JMS applications because they cannot tolerate dropped or duplicate messages and require that every message be received once and only once. The JMS API provides this functionality.

最可靠的产生消息的方法是在事务里发送一条持久化的消息。JMS消息缺省是持久化的。事务是这样一个工作单元,你可以把一系列的操作组合起来,这些操作比如消息发送和接受,所以,这些操作或者都成功或者都失败。细节请看下面内容。

The most reliable way to produce a message is to send a PERSISTENT message within a transaction. JMS messages are PERSISTENT by default. A transaction is a unit of work into which you can group a series of operations, such as message sends and receives, so that the operations either all succeed or all fail. For details, see Specifying Message Persistence and Using JMS API Local Transactions.

消费消息的最可靠的方式是在事务里面,无论是从一个队列或是从主题订阅。

The most reliable way to consume a message is to do so within a transaction, either from a queue or from a durable subscription to a topic. For details, see Creating Temporary Destinations, Creating Durable Subscriptions, and Using JMS API Local Transactions.


对于一般系统,较低的可靠性可以通过降低开销而提高性能。

For other applications, a lower level of reliability can reduce overhead and improve performance. You can send messages with varying priority levels--see Setting Message Priority Levels--and you can set them to expire after a certain length of time (see Allowing Messages to Expire).


JMS API提供了多种方式来获得不同类型和级别的可靠性。本章把这些讨论分成两类:
  • 采用基本的可靠性机制
  • 采用高级可靠性机制 

The JMS API provides several ways to achieve various kinds and degrees of reliability. This section divides them into two categories:


以下章节描述了应用到JMS客户端的功能。其中的一些功能与J2EE应用在不同的方式工作;这种区别在这里详细讨论 Using the JMS API in a J2EE Application.

The following sections describe these features as they apply to JMS clients. Some of the features work differently in J2EE applications; in these cases, the differences are noted here and are explained in detail in Using the JMS API in a J2EE Application.

This section includes three sample programs, which you can find in the directory <INSTALL>/j2eetutorial14/examples/jms/advanced/src/, along with a utility class called SampleUtilities.java.

To compile the programs in advance, go to the <INSTALL>/j2eetutorial14/examples/jms/advanced directory and use the following asant target:

asant build  

采用基本可靠性机制 Using Basic Reliability Mechanisms


基本可靠性机制如下:
  • 控制消息应答:你可以指定多种消息应答的控制级别
  • 指定消息持久化:你可以指定消息是持久化的,意味着他们一定不能丢失,即便是提供者失败
  • 设置消息优先级别:你可以设置消息的多种优先级,这些优先级将影响消息发送的顺序
  • 允许消息过期:你可以指定消息的过期时间,陈旧的消息从而不会被发送

The basic mechanisms for achieving or affecting reliable message delivery are as follows:

    • Controlling message acknowledgment: You can specify various levels of control over message acknowledgment.
    • Specifying message persistence: You can specify that messages are persistent, meaning that they must not be lost in the event of a provider failure.
    • Setting message priority levels: You can set various priority levels for messages, which can affect the order in which the messages are delivered.
    • Allowing messages to expire: You can specify an expiration time for messages so that they will not be delivered if they are obsolete.
    • Creating temporary destinations: You can create temporary destinations that last only for the duration of the connection in which they are created.

控制消息应答 Controlling Message Acknowledgment
除非JMS消息被应答,否则它不会被认为成功消费掉。成功的消息消费顺序发生在三个阶段:
       1,客户收到消息
       2,客户处理消息
       3,消息被应答,消息应答既不是被JMS提供者也不是客户来初始化,而是依赖于会话应答模式。

Until a JMS message has been acknowledged, it is not considered to be successfully consumed. The successful consumption of a message ordinarily takes place in three stages.

    • The client receives the message.
    • The client processes the message.
    • The message is acknowledged. Acknowledgment is initiated either by the JMS provider or by the client, depending on the session acknowledgment mode.
在事务化的会话中,应答会在事务提交时自动发生。如果事务回滚,所有的消费了的消息会重新发送。

In transacted sessions (see Using JMS API Local Transactions), acknowledgment happens automatically when a transaction is committed. If a transaction is rolled back, all consumed messages are redelivered.

在非事务的会话中,何时,何种方式应答依赖于 createSession方法的第二个参数被指定的值。三个可能的参数值时:
Session.AUTO_ACKNOWLEDGE:会话自动应答客户收到的消息,无论时客户从调用receive成功返回或MessageListener被调用来处理消息成功返回。

In nontransacted sessions, when and how a message is acknowledged depend on the value specified as the second argument of the createSession method. The three possible argument values are as follows:

    • Session.AUTO_ACKNOWLEDGE: The session automatically acknowledges a client's receipt of a message either when the client has successfully returned from a call to receive or when the MessageListener it has called to process the message returns successfully. A synchronous receive in an AUTO_ACKNOWLEDGE session is the one exception to the rule that message consumption is a three-stage process as described earlier.

    In this case, the receipt and acknowledgment take place in one step, followed by the processing of the message.

    • Session.CLIENT_ACKNOWLEDGE: A client acknowledges a message by calling the message's acknowledge method. In this mode, acknowledgment takes place on the session level: Acknowledging a consumed message automatically acknowledges the receipt of all messages that have been consumed by its session. For example, if a message consumer consumes ten messages and then acknowledges the fifth message delivered, all ten messages are acknowledged.
    • Session.DUPS_OK_ACKNOWLEDGE: This option instructs the session to lazily acknowledge the delivery of messages. This is likely to result in the delivery of some duplicate messages if the JMS provider fails, so it should be used only by consumers that can tolerate duplicate messages. (If the JMS provider redelivers a message, it must set the value of the JMSRedelivered message header to true.) This option can reduce session overhead by minimizing the work the session does to prevent duplicates.

If messages have been received from a queue but not acknowledged when a session terminates, the JMS provider retains them and redelivers them when a consumer next accesses the queue. The provider also retains unacknowledged messages for a terminated session that has a durable TopicSubscriber. (See Creating Durable Subscriptions.) Unacknowledged messages for a nondurable TopicSubscriber are dropped when the session is closed.

If you use a queue or a durable subscription, you can use the Session.recover method to stop a nontransacted session and restart it with its first unacknowledged message. In effect, the session's series of delivered messages is reset to the point after its last acknowledged message. The messages it now delivers may be different from those that were originally delivered, if messages have expired or if higher-priority messages have arrived. For a nondurable TopicSubscriber, the provider may drop unacknowledged messages when its session is recovered.

The sample program in the next section demonstrates two ways to ensure that a message will not be acknowledged until processing of the message is complete.

A Message Acknowledgment Example

The AckEquivExample.java program in the directory <INSTALL>/j2eetutorial14/examples/jms/advanced/src/ shows how both of the following two scenarios ensure that a message will not be acknowledged until processing of it is complete:

    • Using an asynchronous message consumer--a message listener--in an AUTO_ACKNOWLEDGE session
    • Using a synchronous receiver in a CLIENT_ACKNOWLEDGE session

With a message listener, the automatic acknowledgment happens when the onMessage method returns--that is, after message processing has finished. With a synchronous receiver, the client acknowledges the message after processing is complete. (If you use AUTO_ACKNOWLEDGE with a synchronous receive, the acknowledgment happens immediately after the receive call; if any subsequent processing steps fail, the message cannot be redelivered.)

The program contains a SynchSender class, a SynchReceiver class, an AsynchSubscriber class with a TextListener class, a MultiplePublisher class, a main method, and a method that runs the other classes' threads.

The program uses the following objects:

Use the Admin Console to create the new queue and connection factory as follows:

    • Create a physical destination of type queue with the name ControlQueueP.
    • Create a destination resource with the name jms/ControlQueue and type javax.jms.Queue. Find the Name property and give it the value ControlQueueP.
    • Create a connection factory with the name jms/DurableConnectionFactory and the type javax.jms.ConnectionFactory. Find the property named ClientId and give it the value MyID.

You can also create all the resources needed for these examples with the following asant target:

asant add-objects  

If you did not do so previously, compile the source file:

asant build  

To package the program, follow the instructions in Packaging the Clients, except for the values listed in Table 33-5.

Table 33-5 Application Values for AckEquivExample 
Wizard Field or Area
Value
AppClient File
< INSTALL >/j2eetutorial14/examples/jms/advanced/AckEquivExample.jar
AppClient Display Name
AckEquivExample
Available Files classes
build/AckEquivExample*.class (7 files)
build/SampleUtilities*.class (2 files)
Main Class
AckEquivExample

To run the program, use the following command:

appclient -client AckEquivExample.jar  

The program output looks something like this:

Queue name is jms/ControlQueue
Queue name is jms/Queue
Topic name is jms/Topic
Connection factory name is jms/DurableConnectionFactory
SENDER: Created client-acknowledge session
SENDER: Sending message: Here is a client-acknowledge message
RECEIVER: Created client-acknowledge session
RECEIVER: Processing message: Here is a client-acknowledge
message
RECEIVER: Now I'll acknowledge the message
PUBLISHER: Created auto-acknowledge session
SUBSCRIBER: Created auto-acknowledge session
PUBLISHER: Receiving synchronize messages from jms/
ControlQueue; count = 1
SUBSCRIBER: Sending synchronize message to jms/ControlQueue
PUBLISHER: Received synchronize message; expect 0 more
PUBLISHER: Publishing message: Here is an auto-acknowledge
message 1
PUBLISHER: Publishing message: Here is an auto-acknowledge
message 2
SUBSCRIBER: Processing message: Here is an auto-acknowledge
message 1
PUBLISHER: Publishing message: Here is an auto-acknowledge
message 3
SUBSCRIBER: Processing message: Here is an auto-acknowledge
message 2
SUBSCRIBER: Processing message: Here is an auto-acknowledge
message 3

After you run the program, you can delete the physical destination ControlQueueP and the destination resource jms/ControlQueue.

指定消息持久化 Specifying Message Persistence
JMS API支持两种消息传输模式来指定当JMS提供者失败的时候,消息是否丢失。这些传输模式是 DeliveryMode接口的属性。
  • 持久(PERSISTENT) 模式,这是缺省模式,通知JMS提供者更多地关注,确保当消息提供者失败时消息没有在传输过程中丢失。用这种模式发送的消息会在被发送时记入存储。
  • 非持久(NON_PERSISTENT)模式,这种模式不需要JMS提供者存储消息或确保消息在提供者失败时没有丢失。

The JMS API supports two delivery modes for messages to specify whether messages are lost if the JMS provider fails. These delivery modes are fields of the DeliveryMode interface.

    • The PERSISTENT delivery mode, which is the default, instructs the JMS provider to take extra care to ensure that a message is not lost in transit in case of a JMS provider failure. A message sent with this delivery mode is logged to stable storage when it is sent.
    • The NON_PERSISTENT delivery mode does not require the JMS provider to store the message or otherwise guarantee that it is not lost if the provider fails.
你可以用两种方式指定传输模式。
  • 你可以用MessageProducer接口的setDeliveryMode方法来为这个消息生产者发送的所有消息的设定传输模式。例如,下面的调用为当前消息生产者设定了非持久的发送模式producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
  • 你可以用send或publish方法的long form为指定消息设置传输模式。第二个参数设置了传输模式。例如,下面的调用为消息设置了非持久的传输模式:producer.send(message, DeliveryMode.NON_PERSISTENT, 3,10000);
第三个和第四个参数设置了优先级和失效时间,下面的两个小节将讨论这个问题。

You can specify the delivery mode in either of two ways.

    • You can use the setDeliveryMode method of the MessageProducer interface to set the delivery mode for all messages sent by that producer. For example, the following call sets the delivery mode to NON_PERSISTENT for a producer:

    producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

    • You can use the long form of the send or the publish method to set the delivery mode for a specific message. The second argument sets the delivery mode. For example, the following send call sets the delivery mode for message to NON_PERSISTENT:

    producer.send(message, DeliveryMode.NON_PERSISTENT, 3,
      10000);

    The third and fourth arguments set the priority level and expiration time, which are described in the next two subsections.

如果你没有指定发送模式,缺省时持久模式。应用非持久模式可以提升性能并减少存储开销,但只有您的应用能接受消息丢失。

If you do not specify a delivery mode, the default is PERSISTENT. Using the NON_PERSISTENT delivery mode may improve performance and reduce storage overhead, but you should use it only if your application can afford to miss messages.

设置消息优先级 Setting Message Priority Levels

你可以用消息优先级指示JMS提供者先发送紧急消息。你可以用两种方式设置优先级。

  • 你可以用MessageProducer接口的setPriority方法为消息生产者发送的所有消息设定优先级。例如,下面的调用为消息生产者设定优先级7:producer.setPriority(7);
  • 你可以send或publish方法的long form来为指定消息设定优先级。第三个参数设定优先级。例如,下面的send调用设定优先级为3:producer.send(message, DeliveryMode.NON_PERSISTENT, 3,10000);

You can use message priority levels to instruct the JMS provider to deliver urgent messages first. You can set the priority level in either of two ways.

    • You can use the setPriority method of the MessageProducer interface to set the priority level for all messages sent by that producer. For example, the following call sets a priority level of 7 for a producer:

    producer.setPriority(7);

    • You can use the long form of the send or the publish method to set the priority level for a specific message. The third argument sets the priority level. For example, the following send call sets the priority level for message to 3:

    producer.send(message, DeliveryMode.NON_PERSISTENT, 3,
      10000);

10个优先级范围为,从0(最低)到9(最高)。如果你没有指定优先级,缺省的级别是4。JMS提供者尝试优先发送高优先级消息,但不会一定发送高优先级消息。

The ten levels of priority range from 0 (lowest) to 9 (highest). If you do not specify a priority level, the default level is 4. A JMS provider tries to deliver higher-priority messages before lower-priority ones but does not have to deliver messages in exact order of priority.

允许消息过期 Allowing Messages to Expire

缺省地,消息永远不会过期。如果消息在一段时间内便得陈旧,可能你就需要设置一个过期时间,你可以用两种方式实现。

  • 你可以用MessageProducer接口的setTimeToLive方法为当前消息生产者发送的所有消息设定过期时间。例如,以下的调用就会当前消息生产者发送的消息设定过期时间为一分钟producer.setTimeToLive(60000);
  • 你可以用send或publish方法的long form为特定消息设定过期时间。第四个参数设定超时时间为10万毫秒producer.send(message, DeliveryMode.NON_PERSISTENT, 3,10000);

By default, a message never expires. If a message will become obsolete after a certain period, however, you may want to set an expiration time. You can do this in either of two ways.

    • You can use the setTimeToLive method of the MessageProducer interface to set a default expiration time for all messages sent by that producer. For example, the following call sets a time to live of one minute for a producer:

    producer.setTimeToLive(60000);

    • You can use the long form of the send or the publish method to set an expiration time for a specific message. The fourth argument sets the expiration time in milliseconds. For example, the following send call sets a time to live of 10 seconds:

    producer.send(message, DeliveryMode.NON_PERSISTENT, 3,
      10000);

如果指定的生命周期值为0,则消息永不过期。

当消息发送时,指定的生命周期就会被加入当前时间作为超时时间点。所有未在指定超时时间点前发送的消息都会销毁。陈废消息的销毁保护了存储和计算资源。

If the specified timeToLive value is 0, the message never expires.

When the message is sent, the specified timeToLive is added to the current time to give the expiration time. Any message not delivered before the specified expiration time is destroyed. The destruction of obsolete messages conserves storage and computing resources.

Creating Temporary Destinations

Normally, you create JMS destinations--queues and topics--administratively rather than programmatically. Your JMS provider includes a tool that you use to create and remove destinations, and it is common for destinations to be long-lasting.

The JMS API also enables you to create destinations--TemporaryQueue and TemporaryTopic objects--that last only for the duration of the connection in which they are created. You create these destinations dynamically using the Session.createTemporaryQueue and the Session.createTemporaryTopic methods.

The only message consumers that can consume from a temporary destination are those created by the same connection that created the destination. Any message producer can send to the temporary destination. If you close the connection that a temporary destination belongs to, the destination is closed and its contents are lost.

You can use temporary destinations to implement a simple request/reply mechanism. If you create a temporary destination and specify it as the value of the JMSReplyTo message header field when you send a message, then the consumer of the message can use the value of the JMSReplyTo field as the destination to which it sends a reply. The consumer can also reference the original request by setting the JMSCorrelationID header field of the reply message to the value of the JMSMessageID header field of the request. For example, an onMessage method can create a session so that it can send a reply to the message it receives. It can use code such as the following:

producer = session.createProducer(msg.getJMSReplyTo());
replyMsg = session.createTextMessage("Consumer " +
  "processed message: " + msg.getText());
replyMsg.setJMSCorrelationID(msg.getJMSMessageID());
producer.send(replyMsg);

For more examples, see Chapter 34.


应用高级可靠性的机制 Using Advanced Reliability Mechanisms

更高的消息传输可靠性级别的机制如下:
  • 创建永久的subscriptions::你可以创建永久主题订阅,它可以在订阅者不活动时结束发布的消息。永久订阅为发布/订阅消息域提供了队列可靠性。
  • 应用本地事务:你可以用本地事务,它允许你将一系列的发送和接受工作组合成一个自动工作单元。。如果他们在任何时间点失败了,事务就会回滚。

The more advanced mechanisms for achieving reliable message delivery are the following:

    • Creating durable subscriptions: You can create durable topic subscriptions, which receive messages published while the subscriber is not active. Durable subscriptions offer the reliability of queues to the publish/subscribe message domain.
    • Using local transactions: You can use local transactions, which allow you to group a series of sends and receives into an atomic unit of work. Transactions are rolled back if they fail at any time.
Creating Durable Subscriptions

To ensure that a pub/sub application receives all published messages, use PERSISTENT delivery mode for the publishers. In addition, use durable subscriptions for the subscribers.

The Session.createConsumer method creates a nondurable subscriber if a topic is specified as the destination. A nondurable subscriber can receive only messages that are published while it is active.

At the cost of higher overhead, you can use the Session.createDurableSubscriber method to create a durable subscriber. A durable subscription can have only one active subscriber at a time.

A durable subscriber registers a durable subscription by specifying a unique identity that is retained by the JMS provider. Subsequent subscriber objects that have the same identity resume the subscription in the state in which it was left by the preceding subscriber. If a durable subscription has no active subscriber, the JMS provider retains the subscription's messages until they are received by the subscription or until they expire.

You establish the unique identity of a durable subscriber by setting the following:

    • A client ID for the connection
    • A topic and a subscription name for the subscriber

You set the client ID administratively for a client-specific connection factory using the Admin Console.

After using this connection factory to create the connection and the session, you call the createDurableSubscriber method with two arguments: the topic and a string that specifies the name of the subscription:

String subName = "MySub";
MessageConsumer topicSubscriber =
  session.createDurableSubscriber(myTopic, subName);

The subscriber becomes active after you start the Connection or TopicConnection. Later, you might close the subscriber:

topicSubscriber.close();

The JMS provider stores the messages sent or published to the topic, as it would store messages sent to a queue. If the program or another application calls createDurableSubscriber using the same connection factory and its client ID, the same topic, and the same subscription name, the subscription is reactivated, and the JMS provider delivers the messages that were published while the subscriber was inactive.

To delete a durable subscription, first close the subscriber, and then use the unsubscribe method, with the subscription name as the argument:

topicSubscriber.close();
session.unsubscribe("MySub");

The unsubscribe method deletes the state that the provider maintains for the subscriber.

Figures 33-9 and 33-10 show the difference between a nondurable and a durable subscriber. With an ordinary, nondurable subscriber, the subscriber and the subscription begin and end at the same point and are, in effect, identical. When a subscriber is closed, the subscription also ends. Here, create stands for a call to Session.createConsumer with a Topic argument, and close stands for a call to MessageConsumer.close. Any messages published to the topic between the time of the first close and the time of the second create are not consumed by the subscriber. In Figure 33-9, the subscriber consumes messages M1, M2, M5, and M6, but messages M3 and M4 are lost.

Nondurable Subscribers and Subscriptions

Figure 33-9 Nondurable Subscribers and Subscriptions

With a durable subscriber, the subscriber can be closed and re-created, but the subscription continues to exist and to hold messages until the application calls the unsubscribe method. In Figure 33-10, create stands for a call to Session.createDurableSubscriber, close stands for a call to MessageConsumer.close, and unsubscribe stands for a call to Session.unsubscribe. Messages published while the subscriber is closed are received when the subscriber is created again. So even though messages M2, M4, and M5 arrive while the subscriber is closed, they are not lost.

A Durable Subscriber and Subscription

Figure 33-10 A Durable Subscriber and Subscription

See A J2EE Application That Uses the JMS API with a Session Bean for an example of a J2EE application that uses durable subscriptions. See A Message Acknowledgment Example and the next section for examples of client applications that use durable subscriptions.

A Durable Subscription Example

The DurableSubscriberExample.java program in the directory <INSTALL>/j2eetutorial14/examples/jms/advanced/src/ shows how durable subscriptions work. It demonstrates that a durable subscription is active even when the subscriber is not active. The program contains a DurableSubscriber class, a MultiplePublisher class, a main method, and a method that instantiates the classes and calls their methods in sequence.


The program begins in the same way as any publish/subscribe program: The subscriber starts, the publisher publishes some messages, and the subscriber receives them. At this point, the subscriber closes itself. The publisher then publishes some messages while the subscriber is not active. The subscriber then restarts and receives the messages.


Before you run this program, compile the source file and create a connection factory that has a client ID. If you did not already do so in A Message Acknowledgment Example, perform the following steps:

    • Compile the source code as follows:

    asant build

    • Create a connection factory with the name jms/DurableConnectionFactory and the type javax.jms.ConnectionFactory. Find the property named ClientId and give it the value MyID.

To package the program, follow the instructions in Packaging the Clients, except for the values listed in Table 33-6.


Table 33-6 Application Values for DurableSubscriberExample 

Wizard Field or Area
Value
AppClient File
< INSTALL >/j2eetutorial14/examples/jms/advanced/DurableSubscriberExample.jar
AppClient Display Name
DurableSubscriberExample
Available Files classes
build/DurableSubscriberExample*.class (5 files)
build/SampleUtilities*.class (2 files)
Main Class
DurableSubscriberExample


Use the following command to run the program. The destination is jms/Topic:

appclient -client DurableSubscriberExample.jar 
The output looks something like this:
Connection factory without client ID is jms/ConnectionFactory
Connection factory with client ID is jms/
DurableConnectionFactory
Topic name is jms/Topic
Starting subscriber
PUBLISHER: Publishing message: Here is a message 1
SUBSCRIBER: Reading message: Here is a message 1
PUBLISHER: Publishing message: Here is a message 2
SUBSCRIBER: Reading message: Here is a message 2
PUBLISHER: Publishing message: Here is a message 3
SUBSCRIBER: Reading message: Here is a message 3
Closing subscriber
PUBLISHER: Publishing message: Here is a message 4
PUBLISHER: Publishing message: Here is a message 5
PUBLISHER: Publishing message: Here is a message 6
Starting subscriber
SUBSCRIBER: Reading message: Here is a message 4
SUBSCRIBER: Reading message: Here is a message 5
SUBSCRIBER: Reading message: Here is a message 6
Closing subscriber
Unsubscribing from durable subscription

应用JMS API本地事务 Using JMS API Local Transactions
你可以把一系列的操作编入一个被成为“事务"的自动工作单元。如果任何一个操作失败了,事务就会回滚,并且造作会被尝试从开始重新执行。如果所有的操作成功了,事务就会提交。

在JMS客户中,你可以用本地事务(local transaction)编组发送和接收。JMS API中的Session接口提供了你在JMS客户中要使用的commit和rollback方法。一个事务提交意味着所有产生的消息被发送,所有的消费了的消息被应答。事务回滚则意味着所有产生的消息被销毁,所有消费了的消息被恢复并重新发送,直至过期(see Allowing Messages to Expire)。

事务化的session总是和事务联系在一起。当commit或rollback方法被调用,一个事务就会结束,另外一个事务就会启动。关闭事务化的session就会回滚它的事务,包含任何未决的发送和接收。

在EJBs组件中,你不能用 Session.commit和 Session.rollback方法。可是,你可以用分布式事务,见 Using the JMS API in a J2EE Application.

你可以在一个单一的JMS API本地事务中组合多个发送和接受。如果你这么做了,你就需要小心操作的顺序。如果事务包含所有的发送或包含所有的接收或接收在发送前进行,你就不会有什么麻烦。但是如果你要用应答(request/reply)机制,当你发送一条消息,接下来尝试在一个事务中收到回应来发送消息,程序就会挂掉,因为发送操作不能发生在事务提交前。下面的代码片断表示出这个问题:

In an Enterprise JavaBeans component, you cannot use the Session.commit and Session.rollback methods. Instead, you use distributed transactions, which are described in Using the JMS API in a J2EE Application.

You can combine several sends and receives in a single JMS API local transaction. If you do so, you need to be careful about the order of the operations. You will have no problems if the transaction consists of all sends or all receives or if the receives come before the sends. But if you try to use a request/reply mechanism, whereby you send a message and then try to receive a reply to the sent message in the same transaction, the program will hang, because the send cannot take place until the transaction is committed. The following code fragment illustrates the problem:
// Don't do this!
outMsg.setJMSReplyTo(replyQueue);
producer.send(outQueue, outMsg);
consumer = session.createConsumer(replyQueue);
inMsg = consumer.receive();
session.commit();

Because a message sent during a transaction is not actually sent until the transaction is committed, the transaction cannot contain any receives that depend on that message's having been sent.


In addition, the production and the consumption of a message cannot both be part of the same transaction. The reason is that the transactions take place between the clients and the JMS provider, which intervenes between the production and the consumption of the message. Figure 33-11 illustrates this interaction.


Using JMS API Local Transactions

Figure 33-11 Using JMS API Local Transactions


The sending of one or more messages to one or more destinations by client 1 can form a single transaction, because it forms a single set of interactions with the JMS provider using a single session. Similarly, the receiving of one or more messages from one or more destinations by client 2 also forms a single transaction using a single session. But because the two clients have no direct interaction and are using two different sessions, no transactions can take place between them.


Another way of putting this is that the act of producing and/or consuming messages in a session can be transactional, but the act of producing and consuming a specific message across different sessions cannot be transactional.


This is the fundamental difference between messaging and synchronized processing. Instead of tightly coupling the sending and receiving of data, message producers and consumers use an alternative approach to reliability, one that is built on a JMS provider's ability to supply a once-and-only-once message delivery guarantee.


When you create a session, you specify whether it is transacted. The first argument to the createSession method is a boolean value. A value of true means that the session is transacted; a value of false means that it is not transacted. The second argument to this method is the acknowledgment mode, which is relevant only to nontransacted sessions (see Controlling Message Acknowledgment). If the session is transacted, the second argument is ignored, so it is a good idea to specify 0 to make the meaning of your code clear. For example:

session = connection.createSession(true, 0); 

The commit and the rollback methods for local transactions are associated with the session. You can combine queue and topic operations in a single transaction if you use the same session to perform the operations. For example, you can use the same session to receive a message from a queue and send a message to a topic in the same transaction.


You can pass a client program's session to a message listener's constructor function and use it to create a message producer. In this way, you can use the same session for receives and sends in asynchronous message consumers.


The next section provides an example of the use of JMS API local transactions.


A Local Transaction Example

The TransactedExample.java program in the directory <INSTALL>/j2eetutorial14/examples/jms/advanced/src/ demonstrates the use of transactions in a JMS client application. This example shows how to use a queue and a topic in a single transaction as well as how to pass a session to a message listener's constructor function. The program represents a highly simplified e-commerce application in which the following things happen.

    • A retailer sends a MapMessage to the vendor order queue, ordering a quantity of computers, and waits for the vendor's reply:

    producer =
      session.createProducer(vendorOrderQueue);
    outMessage = session.createMapMessage();
    outMessage.setString("Item", "Computer(s)");
    outMessage.setInt("Quantity", quantity);
    outMessage.setJMSReplyTo(retailerConfirmQueue);
    producer.send(outMessage);
    System.out.println("Retailer: ordered " +
      quantity + " computer(s)");

    orderConfirmReceiver =
      session.createConsumer(retailerConfirmQueue);
    connection.start();

    • The vendor receives the retailer's order message and sends an order message to the supplier order topic in one transaction. This JMS transaction uses a single session, so we can combine a receive from a queue with a send to a topic. Here is the code that uses the same session to create a consumer for a queue and a producer for a topic:

    vendorOrderReceiver =
      session.createConsumer(vendorOrderQueue);
    supplierOrderProducer =
      session.createProducer(supplierOrderTopic);

    The following code receives the incoming message, sends an outgoing message, and commits the session. The message processing has been removed to keep the sequence simple:

    inMessage = vendorOrderReceiver.receive();
    // Process the incoming message and format the outgoing
    // message
    ...
    supplierOrderProducer.send(orderMessage);
    ...
    session.commit();

    • Each supplier receives the order from the order topic, checks its inventory, and then sends the items ordered to the queue named in the order message's JMSReplyTo field. If it does not have enough in stock, the supplier sends what it has. The synchronous receive from the topic and the send to the queue take place in one JMS transaction.

    receiver = session.createConsumer(orderTopic);
    ...
    inMessage = receiver.receive();
    if (inMessage instanceof MapMessage) {
      orderMessage = (MapMessage) inMessage;
      // Process message
    MessageProducer producer =
      session.createProducer((javax.jms.Queue)
        orderMessage.getJMSReplyTo());
    outMessage = session.createMapMessage();
    // Add content to message
    producer.send(outMessage);
    // Display message contents
    session.commit();

    • The vendor receives the replies from the suppliers from its confirmation queue and updates the state of the order. Messages are processed by an asynchronous message listener; this step shows the use of JMS transactions with a message listener.

    MapMessage component = (MapMessage) message;
    ...
    orderNumber =
      component.getInt("VendorOrderNumber");
    Order order =
      Order.getOrder(orderNumber).processSubOrder(component);
    session.commit();

    • When all outstanding replies are processed for a given order, the vendor message listener sends a message notifying the retailer whether it can fulfill the order.

    javax.jms.Queue replyQueue =
      (javax.jms.Queue) order.order.getJMSReplyTo();
    MessageProducer producer =
      session.createProducer(replyQueue);
    MapMessage retailerConfirmMessage =
      session.createMapMessage();
    // Format the message
    producer.send(retailerConfirmMessage);
    session.commit();

    • The retailer receives the message from the vendor:

    inMessage =
      (MapMessage) orderConfirmReceiver.receive();


Figure 33-12 illustrates these steps.


Transactions: JMS Client Example

Figure 33-12 Transactions: JMS Client Example


The program contains five classes: Retailer, Vendor, GenericSupplier, VendorMessageListener, and Order. The program also contains a main method and a method that runs the threads of the Retailer, Vendor, and two supplier classes.


All the messages use the MapMessage message type. Synchronous receives are used for all message reception except for the case of the vendor processing the replies of the suppliers. These replies are processed asynchronously and demonstrate how to use transactions within a message listener.


At random intervals, the Vendor class throws an exception to simulate a database problem and cause a rollback.

All classes except Retailer use transacted sessions.

The program uses three queues named jms/AQueue, jms/BQueue, and jms/CQueue, and one topic named jms/OTopic. Before you run the program, do the following:

    • Compile the program if you did not do so previously:

    asant build

    • Create the necessary resources:
      • In the Admin Console, create three physical destinations of type queue named AQueueP, BQueueP, and CQueueP.
      • Create a physical destination of type topic named OTopicP.
      • Create three destination resources with the names jms/AQueue, jms/BQueue, and jms/CQueue, all of type javax.jms.Queue. For each, set its Name property to the value AQueueP, BQueueP, or CQueueP, respectively.
      • Create a destination resource with the name jms/OTopic of type javax.jms.Topic. Set its Name property to the value OTopicP.

To package the program, follow the instructions in Packaging the Clients, except for the values listed in Table 33-7.


Table 33-7 Application Values for TransactedExample 
Wizard Field or Area
Value
AppClient File
< INSTALL >/j2eetutorial14/examples/jms/advanced/TransactedExample.jar
AppClient Display Name
TransactedExample
Available Files classes
build/TransactedExample*.class (6 files)
build/SampleUtilities*.class (2 files)
Main Class
TransactedExample


Run the program, specifying the number of computers to be ordered. To order three computers, use the following command:

appclient -client TransactedExample.jar 3 

The output looks something like this:

Quantity to be ordered is 3
Retailer: ordered 3 computer(s)
Vendor: Retailer ordered 3 Computer(s)
Vendor: ordered 3 monitor(s) and hard drive(s)
Monitor Supplier: Vendor ordered 3 Monitor(s)
Monitor Supplier: sent 3 Monitor(s)
Monitor Supplier: committed transaction
Vendor: committed transaction 1
Hard Drive Supplier: Vendor ordered 3 Hard Drive(s)
Hard Drive Supplier: sent 1 Hard Drive(s)
Vendor: Completed processing for order 1
Hard Drive Supplier: committed transaction
Vendor: unable to send 3 computer(s)
Vendor: committed transaction 2
Retailer: Order not filled
Retailer: placing another order
Retailer: ordered 6 computer(s)
Vendor: JMSException occurred: javax.jms.JMSException:
Simulated database concurrent access exception
javax.jms.JMSException: Simulated database concurrent access
exception
at TransactedExample$Vendor.run(Unknown Source)
Vendor: rolled back transaction 1
Vendor: Retailer ordered 6 Computer(s)
Vendor: ordered 6 monitor(s) and hard drive(s)
Monitor Supplier: Vendor ordered 6 Monitor(s)
Hard Drive Supplier: Vendor ordered 6 Hard Drive(s)
Monitor Supplier: sent 6 Monitor(s)
Monitor Supplier: committed transaction
Hard Drive Supplier: sent 6 Hard Drive(s)
Hard Drive Supplier: committed transaction
Vendor: committed transaction 1
Vendor: Completed processing for order 2
Vendor: sent 6 computer(s)
Retailer: Order filled
Vendor: committed transaction 2

When you have finished with this sample application, use the Admin Console to delete the physical destinations AQueueP, BQueueP, CQueueP, and OTopicP, and the destination resources jms/AQueue, jms/BQueue, jms/CQueue, and jms/OTopic.

Use the following command to remove the class files:
asant clean 
If you wish, you can manually remove the client JAR files.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值