PAYATHON 2026

MQQueueManager 是否每次都新建 TCP/IP 连接?如何复用连接或使用连接池?

技术老齐

结论

new MQQueueManager(qMgrName) 会创建一个新的 Java 对象,并取得到队列管理器的连接,但不一定新建 TCP/IP socket。是否新建 socket,取决于连接池是否启用,以及底层是否允许多个 conversation 共享 TCP 连接。

对于短时 put/get 操作多、并发较高的应用,常见做法有:

  • 使用固定数量的工作线程,每个线程长期持有自己的 MQQueueManager,复用已打开的队列。
  • 使用 base MQ classes 内置的 MQSimpleConnectionManager。每个请求创建并使用自己的 MQQueueManager,结束时调用 disconnect(),由连接池保留底层连接。
  • 使用 JMS 时,通过 Spring 连接缓存、JMS 连接池或应用服务器的受管资源管理连接。

IBM MQ classes for Java 提供内置连接池,需要应用显式启用或配置。使用了 MQQueueManager,并不代表连接池已经生效。IBM 官方连接池说明

Java 对象、MQ 连接和 TCP socket 有什么区别?

这几层资源不一定一一对应:

层次含义复用方式
MQQueueManager 实例应用持有的 Java 对象长期持有,执行多次操作
底层 MQ 连接与队列管理器之间的连接及相关状态由连接池保留,供后续实例使用
TCP/IP socket客户端与队列管理器之间的网络连接符合条件的多个 conversation 可以共享

反复执行以下代码时:

MQQueueManager mgr = new MQQueueManager(qMgrName);
// put/get
mgr.disconnect();

连接的处理方式取决于当时的资源和配置:

  1. 连接池已启用,且有匹配的空闲连接时,复用底层 MQ 连接。
  2. 池中没有可复用的连接,但已有 TCP 连接允许共享时,新建 MQ conversation,复用已有 socket。
  3. 两个条件都不满足时,建立新的 TCP 连接,并执行相应的 MQ 连接流程;如果配置了 TLS,还涉及 TLS 握手。

因此,不能一概认为每次都会新建 socket,也不能认为每次都没有连接开销。

SHARECNV 能代替连接池吗?

不能。SHARECNV 控制一个 TCP/IP 客户端通道实例可以承载多少个 conversation,用于减少 socket 和通道实例的数量。连接池则保留已经建立的 MQ 连接,供后续使用。

客户端和服务端的共享设置需要同时满足条件,协商时取较小值。base MQ classes 也提供 MQEnvironment.sharingConversations 等配置入口。只修改服务端 SVRCONN,无法保证所有客户端连接都按预期共享。IBM 通道调优说明、MQEnvironment API

共享程度越高,socket 通常越少,但繁忙的 conversation 之间可能产生竞争。高吞吐量应用应通过压测确定设置,增大 SHARECNV 并不适合所有场景。

如何长期复用 MQQueueManager?

如果工作线程数量固定、访问目标稳定,可以让每个工作线程持有自己的 MQQueueManager 和 MQQueue,按以下生命周期使用:

  1. 工作线程启动时建立连接、打开队列。
  2. 连续处理多个业务任务。
  3. 每个任务完成自己的事务。
  4. 工作线程退出时关闭队列,再断开连接。

以下代码展示方法内的资源生命周期。外层方法需要处理或声明 MQException、IOException,连接参数应替换为实际配置。

Hashtable<String, Object> props = new Hashtable<>();
props.put(CMQC.HOST_NAME_PROPERTY, "mq.example.com");
props.put(CMQC.PORT_PROPERTY, 1414);
props.put(CMQC.CHANNEL_PROPERTY, "APP.SVRCONN");
props.put(CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_CLIENT);

MQQueueManager mgr = new MQQueueManager("QM1", props);
try {
    MQQueue queue = mgr.accessQueue(
        "APP.IN",
        CMQC.MQOO_OUTPUT | CMQC.MQOO_FAIL_IF_QUIESCING
    );
    try {
        for (String payload : payloads) {
            MQMessage message = new MQMessage();
            message.writeString(payload);

            MQPutMessageOptions pmo = new MQPutMessageOptions();
            pmo.options = CMQC.MQPMO_NO_SYNCPOINT
                        | CMQC.MQPMO_FAIL_IF_QUIESCING;

            queue.put(message, pmo);
        }
    } finally {
        queue.close();
    }
} finally {
    mgr.disconnect();
}

需要导入的类型:

import com.ibm.mq.*;
import com.ibm.mq.constants.CMQC;
import java.util.Hashtable;

payloads 是待发送的字符串集合,示例采用非事务 put。如果需要事务,应调整选项,并明确调用 commit() 或 backout()。

反复访问同一队列的短操作,可以把 accessQueue() 放在循环外,减少重复打开、关闭队列的开销。生产代码还应记录清理阶段的异常,避免清理异常覆盖原始业务异常。

多个线程能否共用一个实例?

可以,但通过同一个 MQQueueManager 访问队列管理器的调用会同步执行,实际是串行的。

例如,一个线程通过该实例调用带 MQGMO_WAIT 的 get() 时,其他线程使用同一实例执行 put/get,需要等待这次调用结束。因此,IBM 建议需要同时访问队列管理器的线程分别使用自己的 MQQueueManager。IBM 多线程说明

复用实例时还需注意:

  • 同一连接上的本地事务由该连接管理。多个请求交错执行事务操作,可能被同一次 commit() 或 backout() 一起处理。内部同步不提供业务事务隔离。
  • 一个线程调用 disconnect() 时,其他线程应已停止使用该实例及其打开的队列。
  • 每次操作应独立持有 MQMessage、选项对象等可变数据。IBM 特别指出,并发接收线程不能共享同一个 MQGetMessageOptions。
  • 长时间等待消息的消费者适合使用独立连接,避免占用短请求所需的连接。

高并发场景更适合使用多个独立连接,每个连接同一时刻只供一个工作单元使用,并限制连接总量。

base MQ classes 如何使用内置连接池?

显式使用 MQSimpleConnectionManager

创建应用级的 MQSimpleConnectionManager,并在构造 MQQueueManager 时传入。如果池中有合适的连接,构造过程会复用它;调用 disconnect() 后,底层连接返回池中。IBM 配置示例

以下代码沿用前面的 props 和 payloads。其中的数值仅用于展示 API,不代表推荐容量:

// 应用初始化时创建,供多个请求共用。
MQSimpleConnectionManager pool = new MQSimpleConnectionManager();
pool.setMaxConnections(16);
pool.setMaxUnusedConnections(8);
pool.setTimeout(60_000L); // 空闲连接超时,单位:毫秒
pool.setActive(MQSimpleConnectionManager.MODE_ACTIVE);

try {
    for (String payload : payloads) {
        // 每个请求持有自己的 MQQueueManager。
        MQQueueManager mgr = new MQQueueManager("QM1", props, pool);
        try {
            MQQueue queue = mgr.accessQueue("APP.IN", CMQC.MQOO_OUTPUT);
            try {
                MQMessage message = new MQMessage();
                message.writeString(payload);

                MQPutMessageOptions pmo = new MQPutMessageOptions();
                pmo.options = CMQC.MQPMO_NO_SYNCPOINT;
                queue.put(message, pmo);
            } finally {
                queue.close();
            }
        } finally {
            // 结束当前实例的使用,底层连接可返回池中。
            mgr.disconnect();
        }
    }
} finally {
    // 应用停止、所有在途请求结束后执行。
    pool.setActive(MQSimpleConnectionManager.MODE_INACTIVE);
}

在实际并发应用中,池的创建和关闭应由应用生命周期管理,避免每个请求都创建、关闭一个池。

这种方式节省了建立底层 MQ 连接的开销,但仍会创建新的 Java 包装对象,并重复打开、关闭队列。调用过 disconnect() 的 MQQueueManager 及其队列对象不能继续使用,下次请求应创建新实例。MQQueueManager API

使用默认连接池

也可以在组件启动时注册 token:

MQPoolToken token = MQEnvironment.addConnectionPoolToken();

在组件运行期间保留 token,待组件停止且所有连接都使用完毕后再移除:

MQEnvironment.removeConnectionPoolToken(token);

只要至少有一个已注册的 token,默认池就保持启用。token 应覆盖组件的整个生命周期。如果每次请求结束都移除最后一个 token,池就会停用,无法有效保留连接供下次请求使用。IBM 默认池生命周期说明

显式传入连接管理器通常更便于控制资源归属,也能减少多个组件对默认配置的依赖。

池满时会等待吗?

不能假定它会阻塞等待空闲连接。MQSimpleConnectionManager 达到配置上限时,新的连接请求可能抛出 MQException,原因码为 MQRC_MAX_CONNS_LIMIT_REACHED。IBM 连接池限制说明

如果需要排队、连接获取超时和背压,可以在应用层使用有界任务队列或并发许可控制。

通用对象池也可以管理长期存活的 MQQueueManager,但应用需要自行实现独占借用、事务清理、失效连接销毁和停机释放。叠加使用对象池与底层连接池之前,应先设计好两者的生命周期。

JMS、Spring 和 WebSphere 有什么不同?

环境常见管理方式需要注意
独立应用使用 base MQ classes长期持有实例,或使用 MQSimpleConnectionManager应用负责并发限制、事务边界和故障恢复
独立应用使用 JMS缓存或池化 Connection、Session 等资源JMS ConnectionFactory 接口本身不保证连接池行为
Spring JMSCachingConnectionFactory,或适配的 JMS 连接池明确缓存大小与并发上限的区别
WebSphere 等应用服务器受管 JMS ConnectionFactory、IBM MQ resource adapter由容器管理连接池、会话及事务

Spring

CachingConnectionFactory 复用一个 JMS Connection,并缓存 Session、producer 和 consumer。sessionCacheSize 表示 Session 缓存容量,不能作为业务并发的硬上限。Spring API 文档

如果需要多个连接,并希望明确配置 Session 池容量和等待策略,可以评估 pooled-jms 等方案。选型时需确认版本与 IBM MQ 客户端、javax.jms 或 jakarta.jms API 匹配。pooled-jms 配置说明

JMS Session 应由单个执行线程使用,即使共享 Connection,也不能并发共享同一个 Session。

如果 Spring 应用直接使用 com.ibm.mq.MQQueueManager,Spring JMS 的这些配置不会自动管理它,仍需按 base MQ classes 的方式配置连接管理。

WebSphere 等应用服务器

使用服务器配置的 JMS 资源时,应用通常通过 JNDI 获取受管 ConnectionFactory。应用按规范关闭句柄,容器根据连接池和事务规则管理底层资源。服务器的池配置独立于 base MQ classes 的连接池。IBM WebSphere 连接池说明

在应用中直接执行 new MQQueueManager(...),不会进入服务器的 JMS 连接池。还需核对容器的支持范围,例如 IBM 明确说明,WebSphere Liberty 不支持使用 base MQ classes。IBM 使用限制

高吞吐量、低延迟场景如何落地?

  1. 固定消费者或工作线程优先使用长期连接,同时复用常用队列句柄。
  2. 请求式访问使用共享连接池。每个请求独占自己的实例和事务,结束后及时释放。
  3. 按同时执行 MQ 操作的数量设置容量,并限制排队请求数量,避免流量突增直接带来大量连接。
  4. 通过压测调整连接数和 SHARECNV,同时观察延迟、吞吐量、客户端线程与内存,以及队列管理器的连接、通道和句柄使用情况。

验证复用时,需要分别观察 MQ 连接和 TCP 连接。在队列管理器的 MQSC 环境中,可以使用:

DISPLAY CONN(*) ALL
DISPLAY CHSTATUS(APP.SVRCONN) ALL

结合通道实例的 CURSHCNV、MAXSHCNV 和客户端 socket,判断共享情况。要确认是否反复握手,还需观察 TCP 新建连接事件或抓包。仅比较两个时刻的连接数量,无法证明连接得到了复用。IBM 通道状态命令

连接池不保证业务重试安全。base MQ classes 不支持自动客户端重连,应用需要处理连接故障,重建实例及队列句柄。如果在发送或提交期间断线,还需考虑操作结果不确定和重复消息的情况,不能无条件重试。IBM 重连说明

备注:内容仅供参考。