【问题标题】:JMS Queue Resource Leak Suspected - injecting Connection, Session, and QueueJMS Queue Resource Leak Suspected - 注入连接、会话和队列
【发布时间】:2020-01-04 03:13:09
【问题描述】:

我正在尝试确定与我构建的 JMS 队列相关的可疑内存/资源泄漏的位置。我是 JMS 队列的新手,所以我使用了许多标准的 JMS 类对象来确保稳定性。但是在我的代码或配置中的某个地方我做错了,我的队列正在填满或资源正在减慢,这可能是我尝试实现的架构中的未知缺陷所固有的。

在对我的 API 进行负载测试时(使用 Gatling),我可以在 10 分钟的大部分时间内每秒运行 20 条消息(这是一个很小的负载)。但在那之后,消息似乎备份了,处理它们的能力变慢了。通常,一旦完成整个请求超过 60 秒,就会开始出现超时错误。还有更多的业务逻辑可以处理数据并将其保存到关系数据库中,但这些似乎都不是问题。

有趣的是,随后的测试运行继续表现不佳,这表明任何资源泄漏都超出了测试范围。重新启动应用程序可以清除任何已变得臃肿的泄漏。然后测试再次快速运行,在最初的七八分钟内……循环重复。只有重新启动应用程序才能解决问题。由于问题不会自行纠正,即使等待一段时间后,资源也已被占用。

当从逻辑中提取 JMS 调用时,我可以每秒处理 数百条消息。而且我可以在不泄漏或填满队列的情况下运行背靠背测试。

虽然这是一个 Spring 项目,但我使用 Spring 的 JMS Template,所以我编写了自己的 Connection 对象,我将其作为 Spring Bean 注入并实现为单个连接以避免创建为我发送的每条 JMS 消息建立一个新连接。

同样,我将我的JMS Session 配置为是一个注入 Bean,我在其中使用连接 Bean。通过这种方式,我可以持久保存我的 Connection 和 Session 对象,以便通过一次发送一条发送我的所有 JMS 消息。我正在调用的 Qpid 服务器接收这些消息。虽然我可能超出了使用我正在生成的消息的能力,但我希望资源泄漏与我的代码相关联,而不是与 JMS 服务器相关联。

这里有一些代码 sn-ps 让您了解我的方法。感谢您提供任何反馈。

JmsConfiguration(关键方法)

@Bean
public ConnectionFactory jmsConnectionFactory() {
   return new JmsConnectionFactory(user, pass, host);
}

@Bean(name="jmsSession")
public Session jmsConnection() throws JMSException {

   Connection conn = jmsConnectionFactory().createConnection();
   Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);

   return session; //Injected as Singleton
}

@Bean(name="jmsQueue")
public Queue jmsQueue() throws JMSException {
    return jmsConnection().createQueue(queue);      
}

//Jackson's objectMapper is heavy enough to warrant injecting and re-using it.
@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper();
}

JmsMessageEnqueuer

@Component
public class MessageJmsEnqueuer extends CommonThreadScope {

   @Autowired
   @Qualifier("Session")
   private Session jmsSession;

   @Autowired
   @Qualifier("jmsQueue")
   private Queue jmsQueue;

   @Value("${acme.jms.queue}")
   private String jmsQueueName; 

   @Autowired
   @Qualifier("jmsObjectMapper")
   private ObjectMapper jmsObjectMapper;

   public void enqueue(String message, String dataType) {

      try {
         String messageAsJson = objectMapper.writeValueAsString(message);
         MessageProducer jmsMessageProducer = jmsSession.createProducer(jmsQueue);

         TextMessage message = jmsSession.createTextMessage(message);
            message.setStringProperty("dataType", dataType.name());

         jmsMessageProducer.send(message); 

         logger.log(Level.INFO, "Message successfully sent.  Queue=" + jmsQueueName + ", Message -> " + message);

      } catch (JMSRuntimeException | JsonProcessingException jmsre) {
           String msg = "JMS Message Processing encountered an error...";
           logService.severe(logger, messagesBuilder() ... msg)
      }

   //Skip the close() method to persist connection...
   //Reconnect logic exists to reset an expired connection from server.
   }
}

【问题讨论】:

  • 您是在发送持久性消息还是非持久性消息?代理配置是什么样的?代理通常非常复杂,有很多方法可以调整资源使用、性能等。您是否尝试过以任何方式更改代理配置?如果是这样,你改变了什么,结果是什么?此外,您说当您删除 JMS 调用时,您可以处理数百条消息。这些是什么类型的消息?我认为它们不是 JMS,因为您删除了 JMS 逻辑。请澄清。
  • AFAIK,JMS Session 不是线程安全的,因此将其设为单例 bean 不是一个好主意。
  • @JustinBertram :很好的问题,以及需要添加的重要信息,因为 JMS 的范围在服务器、客户端和实现中肯定是巨大的。我正在发送持久消息,因此客户端和代理之间需要完全握手。我正在使用 JMS 1.1(不是简化的 2.0 API),使用 Qpid Client 作为代理连接到 Qpid Server。我正在与在他们的 Stage 和 Production 服务器上配置和维护代理的团队合作,我们将测试其他客户没有大量使用的替代代理。我会在这里发布结果。
  • @JustinBertram :正如您正确推断的那样,“数百条消息”不是 JMS。相反,它们是对用户数据(包括关系数据库后端)的一系列数据操作,我们的 JMS 消息只是所处理逻辑的一个子集。 QA 测试对用户数据执行 CRUD 事务。正是通过这些负载测试观察到资源泄漏/锁定。在不调用 JMS 消息生成的情况下,我们可以每秒轻松地以这种方式处理数百个用户。随着 JMS 调用的发生,我们以每秒 20 个用户的速度遇到问题。
  • @user1516873 :这是一个很好的观点。我阅读的文档还表明 JMS 会话不是线程安全的,尽管 JMS 连接应该是。我认为这不是问题,因为 Session 的单线程性质不应该死锁,只要我不进行事务处理。多个执行操作的线程不应该相互阻塞吗?但是如果进程线程正在冲突,那可能是我的根本问题。当我在本地运行测试套件时,我们肯定会出现某种资源死锁!我正在重写代码以仅重用连接本身。

标签: java spring jms


【解决方案1】:

我只需重写代码以使用 JMS 2.0 版本提供的简化 API 即可解决我的资源泄漏/死锁问题。虽然我永远无法确定是哪个 Connection / Session / Queue 对象让我的代码感到痛苦,但在这种情况下,使用 Context 对象来构建我的连接和会话是金票。

切换到简化的 API 后(因为我已经引入了 JMS 2.0 依赖项),资源泄漏立即消失了!这让我相信,简化的 API 不仅仅是通过为开发人员提供更简单的 API 来编写代码,从而让生活更轻松。虽然这已经是一个优势(即使没有简化 API 不支持的少数功能),但我现在很清楚底层连接和会话对象由 API 管理,因此解决了任何问题填满或死锁。

此外,由于不再发生资源积累,我能够将传递的消息数量增加三倍,让我每秒可以处理 60 个用户,而不是 20 个。这是一个显着的增长,而且我已经修复了阻止我开始使用简化 JMS API 的兼容性问题。

虽然我希望准确地确定是什么弄脏了代码,但这可以作为一种解决方案。此外,2013 年 4 月发布的 JMS 2.0 版这一事实表明简化的 API 绝对是首选解决方案。

【讨论】:

    【解决方案2】:

    只是一个猜测,但MessageProducer 扩展了AutoClosable,建议在不再使用后将其关闭。由于您没有使用 try-with-resources 或之后明确关闭它,因此 jmsSession 可能会随着时间的推移包含越来越多的生产者。虽然我不确定你是应该关闭每个方法调用,还是重新使用创建的生产者。

    您是否尝试过使用诸如 VisualVM 之类的分析器来可视化堆和元空间?如果有,随着时间的推移,您是否发现任何重大变化?

    【讨论】:

    • 现在这是一个有趣的问题!我查阅了有关 Java 的 JMS MessageProducer 类的文档,您当然是正确的。我完全错过了这一点,而且我绝对没有使用“try-with-resources”声明。在我的故障排除的相当早的时候,我返回并提取了所有我作为注入的 Spring bean 自动连接的类,并且只留下了 JMS Connection 作为可重用的 bean。基于来自这个社区的有用的 cmets,以及我阅读的 JMS 1.1 文档......不幸的是,其中大部分相互冲突...... Connection 是唯一可以注入的安全对象。
    • 所以对于你的问题,@Michiel,我还没有使用分析器。但是,您关于 JmsSession 可能会随着时间的推移包含越来越多的生产者的建议是一种有效的可能性。由于我使用的是 JMS 2.0 依赖项,因此我切换到了简化的 API(也随 JMS 2.0 发布)并首先尝试了它。资源泄漏立即消失了,这让我相信简化的 API 不仅仅是让开发人员更容易编写代码。很明显,底层连接和会话对象正在被管理,从而解决了任何填充或死锁的问题。
    猜你喜欢
    • 1970-01-01
    • 2017-07-17
    • 2011-09-23
    • 1970-01-01
    • 2014-09-17
    • 2012-01-01
    • 1970-01-01
    • 2019-05-19
    • 2012-07-03
    相关资源
    最近更新 更多