同步 MDB 通信,最大池大小问题

发布于 2024-11-06 19:37:37 字数 4028 浏览 0 评论 0原文

我正在将 Java Enterprise (3.1) 与 Glassfish 结合使用。我有两个独立的 EAR,它们通过 JMS 同步通信。更具体地说:

EAR1 使用 JMS 消息传递告诉 EAR2 要做什么。 EAR1 开始侦听来自 EAR2 的应答 (QueueReceiver.receive)。 EAR2 接收消息并进行相应的处理,然后将 JMS 消息连同输出发送回 EAR1。

这一切都运行良好。直到我得到这个异常:

[#|2011-05-10T15:05:27.382+0200|WARNING|glassfish3.1|javax.enterprise.resource.resourceadapter.com.sun.enterprise.connectors|_ThreadID=90;_ThreadName=Thread-1;|RAR5117 : Failed to obtain/create connection from connection pool [ jms/QueueConnectionFactory ]. Reason : com.sun.appserv.connectors.internal.api.PoolingException: In-use connections equal max-pool-size and expired max-wait-time. Cannot allocate more connections.|#]

所以看起来容器没有重用 MDB。相反,它会创建新的,直到我达到极限。我知道这是因为 EAR2 中的 MDB 使用 JMS 发回结果。我的猜测是,MDB 实例中仍然分配了一些资源,从而导致了该行为。

如果我只是使用MDB打印收到的消息,我可以整天继续发送消息,所以它肯定与JMS连接有关。

我已经在这两天了,所以如果有人愿意提供一些帮助,我将不胜感激。

这段代码整天都可以工作:

package xxx;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(
        propertyName="destinationType",
        propertyValue="javax.jms.Queue")
}, mappedName = "AssociationQueue1")
public class AssociationMDB implements MessageListener {

    @Override
    public void onMessage(Message arg0) {
        MapMessage msg = (MapMessage)arg0;
        String source = null;
        String target = null;
        try {
            source = msg.getString("source");
             target = msg.getString("target");
        } catch (JMSException e) {
            e.printStackTrace();
        }       
        System.out.println(source + " " + target);
    }
}

虽然这个不行:(

package xxx;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(
        propertyName="destinationType",
        propertyValue="javax.jms.Queue")
}, mappedName = "AssociationQueue1")
public class AssociationMDB implements MessageListener {

    @Override
    public void onMessage(Message arg0) {
        Logger logger = Logger.getLogger(this.getClass().getSimpleName());
        QueueConnection qConnect = null;
        QueueSession qSession = null;
        QueueSender qSender = null;
        try {
            InitialContext context = new InitialContext();
            Queue responseQ = (Queue)context.lookup("AssociationQueue2");
            QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup("jms/QueueConnectionFactory");
            qConnect = factory.createQueueConnection();
            qSession = qConnect.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
            qConnect.start();
            qSender = qSession.createSender(responseQ);

            TextMessage answer = qSession.createTextMessage();
            answer.setText("hey");
            qSender.send(answer);
            logger.info("message sent");
        }
        catch (JMSException jmse) {
            jmse.printStackTrace();
        } catch (NamingException e) {
            e.printStackTrace();
        }
        finally {
            try {
                if(qSender != null) {
                    qSender.close();
                    logger.info("cleaning qSender");
                }
                if(qSession != null) {
                    qSession.close();
                    logger.info("cleaning qSession");
                }
                if(qConnect != null) {
                    qConnect.close();
                    logger.info("cleaning qConnect");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }   
        }

    } 

我也尝试过使用更新的更奇特的 EJB 东西,比如符号等等,但也没有工作......)

Sebastian

I am using Java Enterprise (3.1) with Glassfish. I have two seperate EARs which are communicating synchronously via JMS. More specifically:

EAR1 uses JMS messaging to tell EAR2 what to do.
EAR1 starts to listen for an answer from EAR2 (QueueReceiver.receive).
EAR2 receives the message and does some processing accordingly and then sends a JMS message back to EAR1 with the output.

All this works fine. Until I get this exception:

[#|2011-05-10T15:05:27.382+0200|WARNING|glassfish3.1|javax.enterprise.resource.resourceadapter.com.sun.enterprise.connectors|_ThreadID=90;_ThreadName=Thread-1;|RAR5117 : Failed to obtain/create connection from connection pool [ jms/QueueConnectionFactory ]. Reason : com.sun.appserv.connectors.internal.api.PoolingException: In-use connections equal max-pool-size and expired max-wait-time. Cannot allocate more connections.|#]

So it seems like the container doesnt reuse MDBs. Instead it creates new ones until I reach the limit. I know the reason for this is because the MDBs in EAR2 are using JMS to send back the results. My guess is that there are still some resources allocated in the MDB instance which causes the behaviour.

If I just use the MDBs to print out the message received I can continue sending messages all day long, so it is definately related to the JMS connection.

I have been at this for two days now, so if anyone cares to offer some help, it would be greatly appreciated.

this code works all day long:

package xxx;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(
        propertyName="destinationType",
        propertyValue="javax.jms.Queue")
}, mappedName = "AssociationQueue1")
public class AssociationMDB implements MessageListener {

    @Override
    public void onMessage(Message arg0) {
        MapMessage msg = (MapMessage)arg0;
        String source = null;
        String target = null;
        try {
            source = msg.getString("source");
             target = msg.getString("target");
        } catch (JMSException e) {
            e.printStackTrace();
        }       
        System.out.println(source + " " + target);
    }
}

While this one doesnt:

package xxx;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(
        propertyName="destinationType",
        propertyValue="javax.jms.Queue")
}, mappedName = "AssociationQueue1")
public class AssociationMDB implements MessageListener {

    @Override
    public void onMessage(Message arg0) {
        Logger logger = Logger.getLogger(this.getClass().getSimpleName());
        QueueConnection qConnect = null;
        QueueSession qSession = null;
        QueueSender qSender = null;
        try {
            InitialContext context = new InitialContext();
            Queue responseQ = (Queue)context.lookup("AssociationQueue2");
            QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup("jms/QueueConnectionFactory");
            qConnect = factory.createQueueConnection();
            qSession = qConnect.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
            qConnect.start();
            qSender = qSession.createSender(responseQ);

            TextMessage answer = qSession.createTextMessage();
            answer.setText("hey");
            qSender.send(answer);
            logger.info("message sent");
        }
        catch (JMSException jmse) {
            jmse.printStackTrace();
        } catch (NamingException e) {
            e.printStackTrace();
        }
        finally {
            try {
                if(qSender != null) {
                    qSender.close();
                    logger.info("cleaning qSender");
                }
                if(qSession != null) {
                    qSession.close();
                    logger.info("cleaning qSession");
                }
                if(qConnect != null) {
                    qConnect.close();
                    logger.info("cleaning qConnect");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }   
        }

    } 

(I have also tried using newer more fancy EJB stuff like notations and so but didnt work either...)

Sebastian

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

动次打次papapa 2024-11-13 19:37:37

模式本身似乎没有问题。肯定允许 MDB 将消息发布到另一个队列。无论这是为了回复某件事还是只是委托工作,都与当前的问题无关。

问题似乎在于您调用了qConnect.start()。这会准备好连接以侦听入站流量。对于发送消息来说,这不是必需的。您也不必显式关闭会话和发件人。尽管您确实在finally块中关闭了连接,但如果上面的任何代码抛出异常,它很容易受到连接泄漏的影响。

顺便说一句,您的“不重用 MDB”的措辞也不完全正确。我想你的意思是“不重用连接”?

您似乎还在同一个 Glassfish 实例上运行两个 EAR。这意味着 EAR1 使用与 EAR2 相同的连接池,因此连接泄漏也可能是由调用 QueueReceiver.receive 的代码引起的。

最后请注意,在 Java EE(尤其是 EJB*)中实际上根本不允许直接监听 JMS 连接以获取入站流量。这是令人恼火的部分之一,JMS API 独立运行并在 Java EE 产品中使用时表现不同。有些服务器(例如JBoss AS)有不同的连接工厂,有些可以用于监听,有些则不能。我不知道 Glassfish 的确切细节,但您启动连接以静默监听的事实违反了规范,并且可能是问题的主要根源。

*)
还有一些不清楚的地方是它只涉及 EJB 还是也涉及 Servlet。例如,JBoss AS 6 禁止 EJB 和 Servlet 使用符合 Java EE 的 java:/JmsXA 监听连接,但 Weblogic 8 只允许 EJB 而不是 Servlet。

The pattern itself does not seem to be problematic. A MDB is surely allowed to post a message to another queue. Whether this is in reply to something or just delegates the work isn't relevant to the problem at hand.

What does seem to be a problem is that you call qConnect.start(). This prepares the connection for listening to inbound traffic. For sending a message this is thus not necessary. You also don't have to close the session and the sender explicitly. And although you do close the connection in a finally block, it's easily vulnerable to connection leaks if any of the code above it throws.

Your wording of "doesn't reuse MDBs" is btw also not entirely correct. I think you meant "doesn't reuse connections"?

It also seems that you are running both EARs on the same Glassfish instance. This means that EAR1 is using the same connection pool as EAR2 and thus the connection leak might also be caused by the code that calls QueueReceiver.receive.

Finally note that listening directly to JMS connections for inbound traffic is actually not at all allowed in Java EE, especially EJBs*. This is one of the irritating parts where the JMS API behaves differently standalone and used inside a Java EE product. Some servers (e.g. JBoss AS) have different connection factories, where some can be used for listening and some can't. I don't know the exact details for Glassfish, but the fact that you start the connection for listening silently violates the spec and may be the main root of your problem.

*)
There is additionally some unclarity whether it only concerns EJBs or also Servlets. E.g. JBoss AS 6 forbids both EJBs and Servlets to listen on connections using the Java EE compliant java:/JmsXA, but Weblogic 8 only EJBs and not Servlets.

孤芳又自赏 2024-11-13 19:37:37

Arjan给出了很好的解释。谢谢你。我只想补充一些东西。

就我而言,我通过使用 @Resource 而不是上下文查找来获取连接工厂,我认为这非常相似。现在需要注意的是 QueueConnection.close() 由容器处理。因此,触发关闭并不一定意味着您的连接在触发此调用时被关闭。对于所有@Resource,记住这一点很重要。创建 MDB 时,资源将通过 DI 进行初始化。当MDB被摧毁时,他们将再次被释放。这是为了提交和回滚目的。

因此,如果您可能从 MDB 会话发送多条消息,则只有在 MDB 会话完成后,这些连接才会被释放。是的,我知道,通常一个请求 = 一个响应,但在我的例子中,我收到了一个 CSV 作为请求,用于生成多个 XML 消息并将每一行路由到队列(如果您明白的话)。因此,一般来说,从 sendMessage 方法中调用 ConnectionFactory.createQueueConnection 可能是一个冒险的想法。而是将 QueueConnection 实例作为参数传递。

Arjan gave a very good explanation. Thanks for that. I would just like to add something.

In my case, I was getting the connection factory by using @Resource instead of the context lookup, which I guess is pretty much very similar. Now it is important to note that QueueConnection.close() is handled by the container. So firing a close doesn't necessarily imply that your connection gets closed when this call gets fired. This is important to remember on all @Resource's. On creation of the MDB, the resources will initialise by means of DI. On destruction of the MDB, they will be freed again. This is for commit and rollback purposes.

So, if you are perhaps sending multiple messages from the session of your MDB, these connections will only be freed once the session of your MDB completes. Yes, I know, usually one request = one response, but in my case I got a CSV as request, to generate multiple XML messages and route each line to a queue, if you get the idea. So, generally, calling ConnectionFactory.createQueueConnection from within your sendMessage method, might be a risky idea. Rather pass the QueueConnection instance as a parameter.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文