本章介绍MessageQueue客户端编程的基本知识,其中包括下列主题:
MessageQueue应用程序的行为取决于多个因素:客户端设计、连接配置、代理配置、代理调整及资源管理。在这些因素中,一部分是开发者的责任,另外一部分则是管理员的责任。不过,最理想的情况是开发者了解MessageQueue服务如何支持和扩展应用程序设计,而管理员在调整应用程序时了解设计目标。通过重新设计以及仔细的监视和调整,可以优化消息传送性能。所以,开发出高性能MessageQueue应用程序的关键是开发者和管理员了解应用程序生命周期各阶段可实现的目标,并交流有关预期性能及观测性能的信息。
消息传送中间件使各组件和应用程序可通过生成并使用消息来进行通信。JMSAPI定义管理该通信的两种模式或消息传送域:点对点消息传送和发布/订阅消息传送。JMSAPI的组织可支持这些模式。基本JMS对象包括:用于指定两个域中的消息传送行为的连接、会话、生成方、使用方、目的地和消息。
在点对点域中,消息生成方被称为发送者,而使用方被称为接收者。它们通过被称为队列的目的地来交换消息:发送者生成队列中的消息;而接收者则使用队列中的消息。
这个较为复杂的图说明了有关点对点消息传送的其他几点。
点对点模型具有许多优势:
在发布/订阅域中,消息生成方被称为发布者,而消息使用方则被称为订户。它们通过称为主题的目的地来交换消息:发布者生成主题中的消息;订户则订阅主题并使用主题中的消息。
虽然发布/订阅模型不要求多个订户,但图中仍显示了两个订户来强调通过此域可以广播消息。一个主题的所有订户均可获得发布到该主题的任何消息的副本。
长期订户可能处于活动状态,也可能处于非活动状态。代理会为所有活动订户保留消息,但对于非活动订户,则只为那些长期订户保留消息。
这张较为复杂的图说明了有关发布/订阅消息传送的很多其他点。
发布/订阅模型的最大优点是消息可以广播给订户。
基本类型(统一域)
点对点域
发布/订阅域
Destination(队列或主题)
Queue
Topic
ConnectionFactory
QueueConnectionFactory
TopicConnectionFactory
Connection
QueueConnection
TopicConnection
Session
QueueSession
TopicSession
MessageProducer
QueueSender
TopicPublisher
MessageConsumer
QueueReceiver
TopicSubscriber
显示的连接工厂和目的地这两类对象驻留在对象存储库中。这是为了强调通常将这些对象作为受管理对象来创建、配置和管理。我们假定本章中的连接工厂和目的地都是以管理方式(而不是以编程方式)创建的。
生成消息
使用消息
1.管理员创建连接工厂受管理对象。
2.管理员创建物理目的地以及引用该目的地的受管理对象。
3.客户端通过JNDI查找获得连接工厂对象。
4.客户端通过JNDI查找获得目的地对象。
5.客户端创建连接并设置特定于此连接的任何属性。
6.客户端创建会话并设置用于决定消息传送可靠性的属性。
7.客户端创建消息生成方
客户端创建消息使用方
8.客户端创建消息。
客户端建立连接。
9.客户端发送消息。
客户端接收消息。
下面各节介绍生成方和使用方使用的对象:连接、会话、消息和目的地。之后,我们将通过说明消息的生成和使用来完成对JMS对象的介绍。
客户端使用连接工厂对象(ConnectionFactory)创建连接。连接对象(Connection)表示客户端与代理之间的活动连接。它使用在默认情况下启动或者由管理员为该客户端明确启动的底层连接服务。
创建连接时会分配通信资源并对客户端进行验证。这是一个相当重要的对象,大多数客户端均使用一个连接来完成所有的消息传送。连接支持并发使用:一个连接可由任意数量的生成方和使用方共享。
创建连接工厂时,可通过设置它的属性来配置从它派生的所有连接的行为。对于MessageQueue,这些设置可指定以下信息:
可以在用于启动客户端应用程序的命令行上覆盖连接工厂属性。还可以通过设置任意给定连接的属性来覆盖该连接的属性。
可使用连接对象来创建会话对象,从而设置异常侦听器或者获得JMS版本及提供者信息。
根据JMS规范,会话是生成和使用消息的单线程上下文。可以为一个会话创建多个消息生成方和使用方,但只能顺次使用它们。Java客户端和C客户端的线程实现稍有不同。有关线程实现与限制的其他信息,请参考相应的开发者指南。
还可以使用会话对象来完成以下任务:
消息由三部分组成:消息头、属性和主体。必须了解此结构,才能正确编写消息和配置某些消息传送行为。
头字段
描述
JMSDestination
指定消息要发送到的目的地对象的名称。(由提供者设置。)
JMSDeliveryMode
指定消息是否为持久性消息。(默认情况下由提供者设置,也可以由客户端为生成方或为单独的消息显式设置。)
JMSExpiration
JMSPriority
在0(低)到9(高)的范围内指定消息的优先级。(默认情况下由提供者设置,也可以由客户端为生成方或为单独的消息显式设置。)
JMSMessageID
指定消息在提供者安装的上下文中的唯一ID。(由提供者设置。)
JMSTimestamp
JMSCorrelationID
客户端用于定义两条消息之间的对应性的值。(由客户端在需要时设置。)
JMSReplyTo
指定使用方应发送回复的目的地。(由客户端在需要时设置。)
JMSType
可由消息选择器评估的值。(由客户端在需要时设置。)
JMSRedelivered
指定消息是否已传送但尚未得到确认。(由提供者设置。)
从表中可以看出,消息头字段有多种用途:标识消息、配置消息的路由以及提供有关消息处理的信息等。
其中最重要的字段之一JMSDeliveryMode,用于决定消息传送的可靠性。该字段指示一条消息是否为持久性消息。
部分消息头字段由提供者(代理或客户端运行时环境)设置,其他头字段则由客户端设置。消息生成方可能需要配置头字段值,才能实现某些消息传送行为;消息使用方可能需要读取头字段值,才能了解消息的路由方式以及可能需要对它执行哪些进一步处理。
头字段(JMSDeliveryMode、JMSExpiration和JMSPriority)可以在三个不同的级别上进行设置:
如果这些字段在多于一个级别上进行设置,则为连接工厂设置的值将覆盖为单条消息设置的值;为给定消息设置的值将覆盖为消息生成方设置的值。
消息还可以包含称为属性的可选头字段,这类字段以属性名/属性值对的形式来指定。客户端和提供者可以使用属性来扩展消息头,并可以在其中包含有助于客户端或提供者标识和处理消息的任何信息。通过消息属性,接收客户端可以只传送符合给定标准的消息。例如,使用方客户端可能请求获得有关新泽西州兼职雇员工资单的消息。提供者将不会传送不符合指定标准的消息。
消息主体包含客户端需要交换的数据。
类型
StreamMessage
主体中包含Java基元值流的消息。它的填充和读取均按顺序进行。
MapMessage
主体中包含一组名/值对的消息。没有定义条目顺序。
TextMessage
主体中包含Java字符串的消息,例如XML消息。
ObjectMessage
主体中包含序列化Java对象的消息。
BytesMessage
主体中包含连续字节流的消息。
Message
包含消息头和属性但不包含主体的消息。
Java客户端可以对属性进行设置,使客户端运行时环境压缩要发送的消息的主体。而使用方一端的MessageQueue运行时环境会在传送消息前先解压缩该消息。
消息由消息生成方在连接和会话的上下文中发送或发布。生成消息相当简单:客户端使用消息生成方对象(MessageProducer)将消息发送给物理目的地,在API中用目的地对象表示。
创建生成方时,可以指定发送所有生成方消息的默认目的地。您也可以指定决定持久性、优先级和有效期的消息头字段的默认值。这样,从该生成方发出的所有消息都使用这些默认值,除非您通过在发送消息时指定替代目的地,或者为给定消息的头字段设置替代值来覆盖它们。
消息由消息使用方在连接和会话的上下文中接收。客户端使用消息使用方对象(MessageConsumer)接收指定物理目的地中的消息,在API中表示为目的地对象。
消息如何由代理传送给使用方受三方面因素的影响:
消息使用方可以支持同步或异步消息使用。
消息使用方可以使用消息选择器,使消息服务只传送属性符合特定选择标准的消息。该标准在创建使用方时指定。
选择器使用类似SQL的语法与消息属性匹配。例如,
color=”red’size>10Java客户端还可以在浏览队列时指定选择器,这样就可以查看哪些选定消息正在等待使用。
可以使用会话对象创建主题的长期订户。即使订户变为非活动状态,代理也会为这些类型的订户保留消息。
由于代理必须维护订户的状态并在订户被重新激活后恢复消息传送,因此,代理必须能够在给定订户的订阅期内识别该订户。订户标识是根据创建该订户的连接的ClientID属性以及创建订户时指定的订户名构造的。
可以在一个连接中同时包含生成方和使用方(在使用统一API时甚至可包含会话)。此外,JMSAPI允许使用临时目的地来实现消息传送操作的请求-回复模式。
要设置请求-回复模式,需要执行以下操作:
消息使用方处理消息时,将检查消息的JMSReplyTo字段以决定是否需要回复,并将该回复发送到指定目的地。
请求-回复机制为生成方免去了设置回复目的地的受管理对象的麻烦,同时让使用方可以轻松地响应请求。当生成方只有在确保请求已处理后才能继续时,此模式非常有用。
如图所示,MyTopicPublisher生成Msg1并将它发送到目的地MyTopic。MyTopicSubsriber1和MyTopicSubscriber2接收该消息并向MyTempQueue发送回复,MyTQReceiver从MyTempQueue中检索该回复。该模式可能适用于向大量客户端发布定价信息,并将客户端的回复订单排队以便按顺序进行处理的应用程序。
由于请求/回复取决于临时目的地的创建,因此在下列情况中不应使用此模式:
消息传送在两个跃点上进行:第一个跃点从代理上的物理目的地的生成方获得消息;第二个跃点从使用方的目的地获得消息。因此,在下面的三个阶段,消息可能丢失:在至代理的跃点上,当代理发生故障时在代理内存中,以及在代理至使用方的跃点上。可靠传送可保证传送过程在上述任一阶段都不会失败。由于当代理发生故障时非持久性消息总是会丢失,因此可靠传送仅适用于持久性消息。
使用了两种机制来确保可靠传送:
以下各节将介绍这两个方面的可靠性保证措施。
确认是指客户端与消息服务之间为确保可靠消息传送而发送的消息。对于生成方和使用方,确认的用途是不同的。
如果是生成消息,则代理将确认收到消息、将消息传送到其目的地并将其持久存储。生成方的send()方法会被阻止,直至它收到此确认为止。这些确认对于持久性消息要发送到的客户端是透明的。
使用消息时,客户端确认已收到从某个目的地传送来的消息并已使用它,然后代理从该目的地中删除该消息。JMS指定不同的确认模式代表不同的可靠度。
对于更关心性能而不是可靠性的客户端,MessageQueue服务通过提供NO_ACKNOWLEDGE模式来扩展JMSAPI。在该模式下,代理不跟踪客户端确认,所以不保证使用方客户端已成功处理了消息。对于发送至非长期订户的非持久性消息,选择该模式可提高性能。
事务是将一条或多条消息的生成和/或使用组合到一个工作单位的方法。上述客户端和代理确认过程同样适用于事务。在这种情况下,客户端运行时环境和代理确认默认在事务级别进行。当事务提交时,将自动发送代理确认。
可以将会话配置为事务,并且JMSAPI提供了启动、提交和回滚事务的方法。
在事务中生成或使用消息时,消息服务跟踪各个发送和接收过程,并只有在JMS客户端发出提交事务的调用时才完成这些操作。如果事务中特定的发送或接收操作失败,将引发异常。客户端代码可以通过忽略异常、重试操作或回滚整个事务来处理异常。事务提交时,所有操作都已完成。事务回滚时,所有成功的操作都取消。
事务的作用范围始终为一个会话。也就是说,可以将在单个会话上下文中执行的一个或多个生成方或使用方操作组成一个事务。由于事务只能跨越单个会话,因此不存在既包括消息生成又包括消息使用的端对端事务。
JMS规范还支持分布式事务。也就是说,消息的生成和使用可以是大型分布式事务的一部分,该事务中包括涉及其他资源管理器(如数据库系统)的操作。必须有事务管理器(例如,JavaSystemsApplicationServer提供的事务管理器)才能支持分布式事务。
在分布式事务中,分布式事务管理器使用在Java事务API(JavaTransactionAPI,JTA)XA资源API规范中定义的两阶段提交协议,来跟踪和管理由多个资源管理器(如消息服务和数据库管理器)执行的操作。在Java领域中,资源管理器和分布式事务管理器之间的交互在JTA规范中进行了说明。
支持分布式事务是指消息传送客户端可通过JTA定义的XAResource接口参与分布式事务。此接口定义了实现两阶段提交的许多方法。当客户端进行API调用时,JMS消息服务只与分布式事务管理器(由Java事务服务(JavaTransactionService,JTS)提供)协作来跟踪分布式事务中的各种发送和接收操作、跟踪事务状态并完成消息传送操作。与本地事务一样,客户端可以通过忽略异常、重试操作或回滚整个分布式事务来处理异常。
仅当MessageQueue用作JavaEnterpriseEdition平台中的JMS提供者时,MessageQueue才支持分布式事务。有关如何使用分布式事务的其他信息,请参考应用服务器提供者提供的文档。
另一方面的可靠性就是确保在将持久性消息传送至使用方之前,代理不会将它们丢失。这意味着,当消息到达物理目的地时,代理必须将其放入持久性数据存储库中。如果代理由于某种原因发生故障,它可以在以后恢复此消息并将此消息传送至相应的使用方。
代理还必须持久存储长期订阅。否则,当代理发生故障时,就无法向长期订户传送消息;当有消息到达主题目的地后,长期订户就会变为活动状态。
要保证成功传送消息,消息传送应用程序必须将消息指定为持久性消息,并将它们传送给具有长期订阅的主题目的地或传送给队列目的地。
作为对上述内容的总结,本节介绍如何使用MessageQueue服务从生成方向使用方传送消息。为了描绘一个完整的画面,我们需要补充另外一个细节:在传送过程中,系统处理的消息分为以下两类:
以持久、可靠的方式传送消息的步骤如下:
1.客户端运行时环境通过连接将消息从消息生成方传送到代理。
2.代理从连接中读取消息并将此消息放入相应的目的地中。
3.代理将(持久性)消息放入数据存储库中。
4.代理向消息生成方的客户端运行时环境确认已收到消息。
5.代理确定消息的路由。
6.代理将消息从目的地写入适当的连接,并使用使用方的唯一标识符标记该消息。
7.消息使用方的客户端运行时环境将消息从连接传送到消息使用方。
8.消息使用方的客户端运行时环境向代理确认消息已使用。
9.代理处理客户端确认,并在收到所有确认后删除(持久性)消息。
10.代理向使用方的客户端运行时环境确认,告知客户端确认已得到处理。
如果管理员删除目的地中的消息,或者管理员删除或重新定义长期订阅,导致主题目的地中的消息未被传送即被删除,则代理可以在消息被使用前将它丢弃。在其他情况下,您可能希望代理将消息存储在称为停用消息队列的特殊目的地中,而不是将它们丢弃。在以下情况,消息会被放入停用消息队列中:消息过期时、消息因内存限制而被删除时,以及因客户端引发异常而导致传送失败时。通过将消息存储在停用消息队列中,您可以解决系统问题并在某些情况下恢复消息。
MessageQueue通过两种软件包提供SOAP支持:javax.xml.messaging和com.sun.messaging.xml。可以使用这些库中实现的类接收SOAP消息、将SOAP消息包装为JMS消息以及从JMS消息提取SOAP消息。J2EE平台提供软件包java.xml.soap,您可以使用它来组合和分解SOAP消息。
MessageQueue向消息传送服务提供CAPI,使传统C应用程序和C++应用程序可参与基于JMS的消息传送。
与Java接口类似,C接口也支持以下功能:
但一定要理解Java消息服务规范是只适用于Java客户端的标准;而CMessageQueueAPI是特定于MessageQueue提供者的,且不能在其他JMS提供者中使用。包含C客户端的消息传送应用程序不能由另一个JMS提供者进行处理。