问题背景
客户系统使用ESB架构, 通信方式IBM MQ, 除了基础规范之外, 有一条约定是以MsgId作为同一笔交易的标识, 使用JMS集成时手动指定MsgId不生效, 导致请求方无法取回响应报文, 导致消息堆积
产生原因
为什么手动指定JMSMessageID无效
- 首先IBM MQ分配的
messageId
是在调用MQPUT
操作时设置到MQMD
中的, 它是一个24字节二进制的id, 而消费者经过JMS接收到的消息的id是JMSMessageID
, 这是一个48个字符的十六进制字符串, 同时带有前缀"ID:" - 但实际上, 很容易发现
JMSMessageID
实际上就是由MQMD.MessageId
二进制转为十六进制同时拼接的前缀"ID:"的字符串 - 实际上JMS是提供了设置
JMSMessageID
的方法的,javax.jms.Message#setJMSMessageID
, 但是会发现, 作为生产者, 指定了JMSMessageID
后, 消费者端接收到的始终是一个随机id, 即使根据上面的转换规则手动转换, 或者尝试使用原值覆盖, 都是无效的, 这是因为JMS的规范声明中, JMSMessageID必须由JMS提供程序设置, 因此在所有常规情况下, 这个设置方法实际是被忽略/无效的, 在send()
之前设置到JMSMessageID
的值都会被覆盖
解决方法
查阅文档发现, IBM MQ消息id是MQMD中的属性, 通过指定MQMD中JMS_IBM_MQMD_MsgId
最终会覆盖JMSMessageID
, 不过这样做意味着会破坏JMS的规范
Reading and writing the message descriptor from an IBM MQ classes for JMS application
Some IBM® MQ applications require specific values to be set in the MQMD of messages sent to them. IBM MQ classes for JMS provides message attributes that allow JMS applications to set MQMD fields and so enable JMS applications to “drive” IBM MQ applications.
具体实现
获取原MQMD.MessageId
想到两种方式
- 在配置时设置队列的
WMQ_MQMD_READ_ENABLED
属性为true
从// 其他配置... mqQueueConnectionFactory.setBooleanProperty(WMQConstants.WMQ_MQMD_READ_ENABLED, true);
javax.jms.Message
消息体中获取byte[] msgId = (byte[]) message.getObjectProperty(JmsConstants.JMS_IBM_MQMD_MSGID);
- 直接根据转换规则从
JMSMessageID
中获取
转为Pattern hexJmsId = Pattern.compile("[A-Fa-f0-9]{48}") String hexId = ReUtil.getGroup0(hexJmsId, message.getJMSMessageID())
byte[]
// 48个十六进制字符同时带前缀"ID:"的jmsId转为24字节的二进制值 byte[] binaryId = DatatypeConverter.parseHexBinary(hexId);
覆盖JMSMessageID
首先在配置时, 设置WMQ_MQMD_WRITE_ENABLED
为true
// ...其他配置
mqQueueConnectionFactory.setBooleanProperty(WMQConstants.WMQ_MQMD_WRITE_ENABLED, true);
设置响应消息MQMD
属性
message.setObjectProperty(JmsConstants.JMS_IBM_MQMD_MSGID, binaryId);
使用JmsTemplate
发送时如果遇到WMQ_TARGET_CLIENT
, WMQ_MQMD_READ_ENABLED
, WMQ_MQMD_WRITE_ENABLED
, WMQ_MQMD_MESSAGE_CONTEXT
等属性不生效, 那就在发送时指定destinationName
为:
"queue:///{你的目标队列名}?targetClient=1&mdMessageContext=2&mdWriteEnabled=true"