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();
连接的处理方式取决于当时的资源和配置:
- 连接池已启用,且有匹配的空闲连接时,复用底层 MQ 连接。
- 池中没有可复用的连接,但已有 TCP 连接允许共享时,新建 MQ conversation,复用已有 socket。
- 两个条件都不满足时,建立新的 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,按以下生命周期使用:
- 工作线程启动时建立连接、打开队列。
- 连续处理多个业务任务。
- 每个任务完成自己的事务。
- 工作线程退出时关闭队列,再断开连接。
以下代码展示方法内的资源生命周期。外层方法需要处理或声明 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 JMS | CachingConnectionFactory,或适配的 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 使用限制
高吞吐量、低延迟场景如何落地?
- 固定消费者或工作线程优先使用长期连接,同时复用常用队列句柄。
- 请求式访问使用共享连接池。每个请求独占自己的实例和事务,结束后及时释放。
- 按同时执行 MQ 操作的数量设置容量,并限制排队请求数量,避免流量突增直接带来大量连接。
- 通过压测调整连接数和
SHARECNV,同时观察延迟、吞吐量、客户端线程与内存,以及队列管理器的连接、通道和句柄使用情况。
验证复用时,需要分别观察 MQ 连接和 TCP 连接。在队列管理器的 MQSC 环境中,可以使用:
DISPLAY CONN(*) ALL
DISPLAY CHSTATUS(APP.SVRCONN) ALL
结合通道实例的 CURSHCNV、MAXSHCNV 和客户端 socket,判断共享情况。要确认是否反复握手,还需观察 TCP 新建连接事件或抓包。仅比较两个时刻的连接数量,无法证明连接得到了复用。IBM 通道状态命令
连接池不保证业务重试安全。base MQ classes 不支持自动客户端重连,应用需要处理连接故障,重建实例及队列句柄。如果在发送或提交期间断线,还需考虑操作结果不确定和重复消息的情况,不能无条件重试。IBM 重连说明
备注:内容仅供参考。
