16boke - 一路博客

JMS Session对象

JMS Session是一个单线程的上下文(对能够使用会话对象或它创建的线程的数量没有限制。限制是会话的资源不应当被多个线程并发使用。这由用户来保证满足这个限制。最简单的方式是使用一个线程。在异步转发的情况下,使用一个线程来设置停止模式然后启动异步转发。在更复杂的情况下,用户必须提供显式的同步。),用于生产和消费消息。尽管会话可以给提供商分配JVM外部的资源,但是它被看作是轻量JMS 对象。

Session用于几个目的:

• 它是MessageProducer和MessageConsumer的工厂。

• 它是TemporaryTopic和TemporaryQueue的工厂。

• 它为需要动态操纵提供商专有目的地名字的客户端提供了一种创建Queue或Topic对象的途径。

• 它提供了提供商优化后的消息工厂。

• 它支持事务串,这些事务将跨会话生产者和消费者的工作组合成原子单元。

• 它为它消费的消息和它生产的消息定义了一个连续的顺序。

• 它保留它消费的消息直到这些消息被确认。

• 它序列化注册到它的MessageListener的执行。

• 它是QueueBrowser的工厂。

4.4.1 关闭会话

由于提供商可以代表会话分配位于JVM之外的资源,客户端应当在这些资源不需要的时候关闭它们。依靠垃圾回收来最终回收它们不是非常及时的。对于由会话创建的MessageProducer和MessageConsumer来说同样需要这样做。

关闭会话会中止这个会话上的所有消息处理。它必须处理由会话消费者未处理的接收或正在运行的消息监听器的关闭,正如在节4.3.5“关闭连接”中所述。

关闭会话的方法可以从一个不同于当前控制这个会话的控制线程调用。

当会话关闭被调用时,在消息处理被按顺序停止前关闭方法不能返回。这意味着不会有消息监听器正在运行,且如果有未处理的接收,则它返回null或者一个消息。

当会话被关闭时,不需要关闭它的消息生产者和消费者。会话关闭足够通知JMS提供商释放会话的所有资源。

关闭有事务的会话必须回滚处理中的事务。关闭客户端确认的会话不强迫确认。

一旦会话被关闭,那么企图使用它或它的消费者和生产者必须跑出IllegalStateException(必须忽略对这些对象close方法的调用)。继续使用通过这个会话创建或接收的消息对象是有效的,除了已接收消息的acknoledge方法。

关闭一个已关闭的会话不能抛出异常。

4.4.2 创建MessageProducer和MessageConsumer

会话可以创建和服务于多个MessageProducer和MessageConsumer。参见节4.5“MessageConsumer”和节4.6“MessageProducer”了解创建和使用它们的详细信息尽管会话可以创建多个生产者和消费者,它们必须依次使用。在效果上,只有一个逻辑控制线程可以使用它们。这在后面会详细的解释。

4.4.3 创建临时目的地

尽管会话被用于创建临时目的地,这只是为了方便。它们的范围实际上是整个连接。它们的生命周期与连接相同,连接的所有会话都可以创建临时目的地的MessageConsumer。

临时目的地(TemporaryQueue或TemporaryTopic对象)是系统为连接生成的唯一的目的地。只有它们自己的连接才可以为它们创建MessageConsumer。

临时目的地的通常用法师作为JMSReplyTo的目的地。

每个TemporaryQueue或TemporaryTopic对象都是唯一的。不能够被复制。

由于临时目的地可以分配JVM外部的资源,如果这些资源不再使用,应当及时删除它们。当它们被垃圾回收或连接关闭时,这些资源将被自动删除。

4.4.4 创建目的地对象

大多数的客户端都将使用Destination,Destination是JMS的受管理对象,通过JNDI查找它们。这是最方便的方法。

某些特殊的客户端可能需要动态操纵使用提供商特有的目的地名来创建Destination。会话为JMS提供商提供了实现这个功能的提供商专用的方法。

4.4.5 优化消息实现

会话提供使用提供商优化过的实现来创建消息的方法。这可以让提供商最小化处理消息的负荷。

会话必须能够发生所有的JMS消息而不管它们如何被实现的。

4.4.6 使用会话的约定

会话被设计成一个时间只能由一个线程使用。一个例外情况是在会话或它的连接依次关闭时可以多个线程使用。参见节4.3.5“关闭连接”和节4.4.1“关闭会话”了解更详细的信息。

一个典型的用法是让一个线程阻塞在同步的MessageConsumer 上,直到有消息到达。这个线程然后使用一个或多个MessageProducer。

如果已经有一个客户端控制线程正在这个会话中等待接收消息,那么另一个客户端控制线程不能同步接收消息。

另一个典型的用法是让一个线程通过创建会话的生产者和一到多个异步消费者来设置会话。在这种情况下,消息生产者不能独占消费者消息监听器。由于会话按顺序执行消费者的MessageListener,因此这些监听器可以安全地共享会话的资源。

如果在会话正在被设置时连接处于停止模式,那么客户端在完全准备好处理消息之前不需要处理到达的消息。这是最好的策略,因为它降低了设置和消息处理间出现冲突的可能性。当连接正在接收消息时可以创建和设置会话。在这种情况下,要注意保证会话的MessageProducer、MessageConsumer 和MessageListener要按正确的顺序创建。例如,错误的顺序可能引起MessageListener使用还没有创建号的MessageProducer;或由于MessageListener注册的顺序不正确而使得消息按错误的顺序到达。

如果客户端希望在其他线程消费消息时有一个线程产生消息,那么客户端应当为生产消息的线程使用独立的会话。

一旦连接被启动,则它的所有带有消息监听器的会话都致力于给它们转发消息的控制线程。从另外一个控制线程中使用这些会话的客户端代码是错误的。唯一的例外是这种非那个是可以用于会话或连接的关闭方法。

对会话增加单线程控制的限制的结果是带消息监听器的会话也不能同步接收消息。

会话要么致力于用作转发消息到消息监听器的控制线程,要么致力于由客户端初始化的控制线程。不能在同一个会话中同时使用。

另一个结果是,连接必须在停止模式下对带多个消息监听器的会话进行设置。原因是当连接正在转发消息,一旦第一个消息监听器被注册,则会话就由向这个监听器转发消息的控制线程控制。此时,客户端的控制线程不能进一步配置这个会话。

对大多数客户端来说,将它们的工作分派到多个会话是很正常的。这个模型可以让客户端简单的启动和随着并发增长的需要而逐渐地增加消息处理。

4.4.7 事务

Session可选地可以被指定为事务性的。每个事务性的会话支持单序列的事务。每个事务将一系列生成的消息和一系列消费的消息分组到一个原子工作单元。效果是,事务将会话的输入消息流和输出消息流组织称一系列的原子单元。当事务提交时,输入原子单元被确认,输出原子单元被发送。如果事务回滚,则它生产的消息被销毁,它消费的消息被自动恢复。关于会话恢复的详细信息,参见节4.4.11“消息确认”。

会话使用它的commit()或rollback()方法来完成会话。当前事务完成时会自动启动下一个事务。因此,事务性的会话当前总会有一个事务。

JTS或一些其他的事务监控器工具可以被用于将会话的事务和其它资源(数据库,其他的JMS会话等等上的事务组合在一起。由于Java分布式事务由JTA事务分割API控制,因此在这种上下文中使用会话的commit和rollback方法将抛出JMS的TransactionInProgressException。

4.4.8 分布式事务

JMS不要求提供商支持分布式事务;但是如果提供商支持了,则应当通过JTAXAResourceAPI来提供分布式事务。

JMS提供商也可以是分布式事务监听器。如果它是,那么它应当通过JTA API提供事务控制。

尽管对JMS客户端来说它可以直接处理分布式事务,但JMS客户端尽量不要这样做。

使用基于XA接口的JMS客户端在第8章“JMS应用服务器工具”中描述,但这种客户端不能跨不同的JMS实现进行移植,因为这些接口是可选的。在JMS中支持JTA是为了系统提供商能够将JMS集成到他们的应用服务器产品中。参见第8章“JMS应用服务器工具”了解更详细的信息。

4.4.9 多会话

一个客户端可以创建多个会话。每个会话都是一个独立的消息生产者和消费者。

对于Pub/Sub,如果两个回合都有一个TopicSubscriber,且都订阅同一个Topic,那么每个订阅者都会收到消息。它们之间不会相互阻塞。

对于PTP,JMS没有指定对同一个Queue并发QueueReceiver的语义。但是,JMS没有禁止提供商提供这个功能。因此,向多个QueueReceiver转发消息将依赖JMS提供商的实现。但这是不可移植的。

4.4.10消息排序

JMS客户端不需要理解它们什么时候可以依靠消息排序,什么时候不能。

4.4.10.1 消息接收的顺序

由会话消费的消息定义了一系列的顺序。这个顺序是重要的,因为它定义了消息确认的顺序。参见4.4.11“消息确认”了解详细信息。每个会话消费者交叉读取会话输入消息流中的消息。

JMS定义了由会话发送到目的地的消息必须按它们发送的顺序被接收(参见节4.4.10.2“消息发送的顺序”了解限制条件)。这节定义了一部分在会话输入消息流上排序的约束。

JMS没有定义跨目的地接收消息的顺序或从多个会话发送的跨目的地的消息的顺序。会话输入消息流的顺序依赖于时间。不在应用的控制之下。

4.4.10.2 消息发送的顺序

尽管客户端松散地查看在会话中生产的消息,这些消息形成了一个有序的发送消息流,进行整体排序是没有意义的。对接收客户端唯一可见的排序是会话发送到一个特定目的地的消息的顺序。有几个事情可以影响整个顺序:

• 高优先级的消息可以跳到低优先级消息的前面。

• 客户端可以不接收NON_PERSISTENT的消息,因为JMS提供商失败造成。

• 如果PERSISTENT和NON_PERSISTENT消息被发送到一个目的地,那么只在转发模式中保证顺序。也就是说,稍晚的NON_PERSISTENT消息可以在稍早的PERSISTENT之间到达;但是不会在稍早的同优先级的NON_PERSISTENT消息之前到达。

• 客户端使用事务性的会话将会话发送的消息分组到原子单元(一个JMS事务的生产者组件)。发送到特定目的地的消息的事务顺序是有意义的。跨目的地发送的消息的顺序是没有意义的。参见4.4.7“事务”了解更详细的信息。

4.4.11 消息确认

如果会话是事务性的,那么消息确认自动由commit处理,且恢复自动由rollback处理。

如果会话不是事务性的,有三个确认选择,且手工处理恢复:

• DUPS_OK_ACKNOWLEDGE——这个选项告诉会话懒惰确认消息的传递。如果JMS失败,这很可能造成传递重复消息,因此这个选项只用于可以忍受重复消息的消费者。它的好处是减少了会话为防止重复所要做的工作。

• AUTO_ACKNOWLEDGE——使用这个选项,当消息被成功地从调用接收返回或处理消息的MessageListener成功返回时,会话自动确认客户端的消息接收。

• CLIENT_ACKNOWLEDGE——使用这个选项,客户端通过调用消息的acknowledge方法来确认消息。确认一个被消费的消息会自动确认被该会话转发的所有消息。

当使用CLIENT_ACKNOWLEDGE模式时,客户端可以在处理它们时产生大量未确认消息。JMS提供商应当为管理员提供限制客户端超量运行的途径,以便客户端不会造成资源耗尽并保证当它们使用的资源被临时阻塞时造成失败。

会话的recover方法用于停止一个会话然后使用第一个未确认消息来重新启动它。

事实上,会话的被转发消息序列被重新设置到最后一个确认消息之后。现在转发的消息序列可以与起初转发的消息序列不同,因为消息到期和收到更高优先级的消息。

会话必须设置消息的redelivered标记,表示它是由于恢复而被重新转发的。

4.4.12 消息的重复转发

JMS提供商不能重复转发已确认消息。

当客户端使用AUTO_ACKNOWLEDGE模式时,它不会直接控制消息的确认。由于这种客户端不能确切知道某个消息是否已经被确认,因此它们必须做好再次收到最后消费的消息的准备。这可能由于客户端完成它的工作恰好在防止消息确认发生失败之前引起。只有会话测最后消费的消息会遇到这种情况。JMSRedelivered消息头字段将用于这种情况下被重发的消息。

4.4.13 消息的重复产生

JMS提供商不能生产重复的消息。这意味着生产消息的客户端可以依赖JMS提供商来保证消息的消费者一个消息只会接收一次。客户端的错误不会引起提供商重复一个消息。

如果在客户端提交和提交方法返回期间出现错误,那么客户端不能决定事务是否被提交还是被回滚。当非事务性的发送一个PERSISTENT消息和发送方法返回之间产生错误时,会产生同样的不确定性问题。

这种不确定性由JMS应用来处理。在某些情况下,这可能会造成客户端生产重复消息。

由于恢复被重发的消息不认为是重复消息。

4.4.14 客户端代码的有序执行

尽管java语言本身提供多线程,但写多线程程序仍然比写单线程程序困难。

因此,JMS不会引起客户端代码的并非执行,除非客户端显式地要求这样做。做到这一点的一种途径是一个会话对所有消息的异步转发进行排序。

为了异步接收消息,客户端向MessageConsumer注册实现了JMS MessageListener接口的对象。事实上,会话使用一个单线程来运行所有的MessageListener。当线程正在执行一个监听器时,所有其他被异步转发的消息必须等待。

4.4.15 并行消息转发

希望并行转发的客户端可以使用多会话。事实上,每个会话的监听器线程并行的运行。当会话上的监听器正在执行时,在另一个会话上的监听器也可以被执行。

注意,JMS本省不提供并行处理主题消息集合的功能(这些消息被转发到单个消费者)。客户端可以使用单个消费者但实现多线程逻辑来并行处理这些消息;但是,这样做是不可靠的,因为JMS没有事务功能来处理这种方式需要的并发事务。