JmsTemplate 陷阱
连接 > 容器 > Spring 支持 > JmsTemplate 陷阱
需要注意的是,JmsTemplate 旨在用于使用 EJB 容器 JMS 池抽象的 EJB 中。因此,每个方法通常都会创建连接、会话、生产者或消费者,执行某些操作,然后关闭所有这些。这样做的目的是利用 J2EE 容器的池机制在后台池化 JMS 资源。如果不使用 EJB 容器的池化 JMS 提供者,这是处理 JMS 的最糟糕的方式;因为通常每个创建/关闭连接、生产者/消费者的操作都会导致与 JMS 代理之间的请求-响应。
用户故事
我们曾经在 ActiveMQ Classic 中遇到过一个错误,如果在几秒钟内创建 65535 个 MessageProducer 实例,代理就会抛出异常。这样做有点愚蠢,在短时间内创建这么多生产者(每个要发送的消息一个) - JMS 旨在预先创建生产者和消费者等资源并在许多消息交换中重用它们。该错误是由一个没有使用 JMS 池的 JmsTemplate 用户发现的。至少这有助于我们找到这个错误,我很高兴地说现在已经修复了,但这并没有太多帮助他们的性能。
您应该只将 JmsTemplate 与池化的 JMS 提供者一起使用。在 J2EE 1.4 或更高版本中,通常意味着基于 JCA 的 JMS ConnectionFactory。如果您在 EJB 中,请确保使用 J2EE 容器 ConnectionFactory,而不是普通连接工厂。如果您不在 EJB 中,那么您应该使用我们的 PooledConnectionFactory,这样就可以很好地进行池化。如果您需要参与 XA 事务,请查看我们基于 Spring 的 JCA 容器。
我见过的另一个陷阱是,人们在 SessionCallback 方法中创建 MessageConsumer,然后想知道为什么没有收到消息。在调用 SessionCallback 后,会话将关闭;这也会关闭您的消费者。因此,如果您要创建 MessageConsumer,您应该自己创建连接、会话和消费者。
我遇到的另一个问题是,人们使用 JmsTemplate.receive() 方法;正如我在上面所说,如果您不在使用 J2EE 容器 ConnnectionFactory 的 EJB 中,则会为每个 receive() 方法创建连接、会话和消费者,并在使用后关闭。如果使用池化,一切都很好 - 但是非常慢 - 但是请注意,这种机制在没有池化的情况下可能会错过消息。如果您正在消费具有 NON_PERSISTENT 传递模式发送的消息的主题,那么您很可能会错过消息,因为每个 receive() 调用都是一个全新的消费者,它不会接收消费者存在之前发送的任何消息。要有效地接收消息,您应该使用 Spring 的 MessageListenerContainer。
4.x 中的新功能
在 ActiveMQ Classic 4.x 中,我们有一个名为 订阅恢复策略 的新功能,即使在非持久性传递模式下,也允许新消费者追溯到过去并接收在窗口(固定数量的 RAM 或时间窗口)内传递的消息。例如,如果您的代理崩溃,您有 2 分钟的时间重新连接到另一个代理,并且不会错过任何消息 - 即使没有持久性传递。
使用 JmsTemplate 的建议
- 除非您完全确定它已完成所有必要的池化,否则永远不要使用常规 ConnectionFactory
- 如果在 EJB 中使用,请确保使用 EJB 容器 ConnectionFactory
- 如果您只发布消息,并且不在 EJB 容器中,并且使用的是 ActiveMQ Classic,那么您可以使用 PooledConnectionFactory 或 Spring CachingConnectionFactory
- 如果您正在消费消息,那么避免使用 receive() 方法并使用 Spring 的 MessageListenerContainer 可能会更简单、更高效,并且不太可能丢失消息。
- 另请参阅 使用 Spring 发送 JMS 消息 和 使用 Spring 接收 JMS 消息