MQ消息读取异常:读取显示Null、停止后消息回退为未提交
从你的描述和字节流对比来看,核心问题出在消息的字符编码不匹配,结合事务回滚的现象,我们可以一步步拆解分析:
1. 先理解字节流差异的含义
你提供的异常消息字节流3C 00 3F 00 78 00...是UTF-16LE(小端序)编码的XML开头<?xml——每个ASCII字符占用2个字节,高字节填充00;而正常能读取的消息是标准UTF-8编码,每个ASCII字符仅占1个字节。
这种编码不匹配会导致消费者按默认编码(通常是UTF-8)解析时无法正确识别消息体,进而出现打印message为null的情况;同时因为消费逻辑异常,XA事务未提交,当应用停止时事务回滚,消息被放回队列。
2. 具体排查步骤
(1)验证消息的编码属性
打开MQ Explorer,查看异常消息的JMS_IBM_Character_Set属性:
- 如果值是
1200,对应UTF-16LE;如果是819则是UTF-8。这个属性直接反映了生产者发送时使用的编码。
(2)检查生产者端的编码配置
大概率是生产者发送消息时误用了UTF-16编码,或者未正确设置字符集属性:
- 如果是使用JMS发送TextMessage,确认是否显式指定了字符集:
TextMessage msg = session.createTextMessage(xmlContent); // 错误示例:设置了UTF-16,而消费者默认用UTF-8 msg.setJMSProperty("JMS_IBM_Character_Set", "UTF-16"); producer.send(msg); - 如果是非JMS客户端发送(比如MQ API),检查发送时指定的CCSID是否为1200(UTF-16LE)而非819(UTF-8)。
(3)调整消费者的解析逻辑
如果无法修改生产者的编码,你需要在消费者端适配UTF-16编码读取消息:
修改你的代码,不要直接打印message(默认toString可能无法正确解析UTF-16的消息体),而是显式指定编码读取:
JmsThreadContext ctx = context.getContext(); message = ctx.consumer.receive(timeout); if (message != null) { try { if (message instanceof TextMessage) { TextMessage textMsg = (TextMessage) message; // 用UTF-16编码读取消息体 String msgContent = textMsg.getText("UTF-16"); logger.debug("Message content: " + msgContent); // 处理完消息后提交事务,避免回滚 ctx.session.commit(); } else { logger.warn("Received non-text message, rolling back"); ctx.session.rollback(); } } catch (JMSException e) { logger.error("Failed to process message", e); // 异常时回滚事务 ctx.session.rollback(); } } else { logger.debug("No message received within timeout"); }
(4)检查XA事务的处理逻辑
你的XaTransactedJmsMessageReceiver使用了事务上下文,如果消息解析失败(比如编码不匹配导致的异常),事务会自动回滚,这就是为什么停止应用后消息会回到队列。确保只有当消息成功处理完成后才提交事务,异常时明确回滚。
3. 总结
最根本的解决方式是让生产者和消费者使用一致的字符编码(推荐UTF-8);如果无法修改生产者,就在消费者端适配对应的编码读取消息体,同时确保事务逻辑正确,避免不必要的消息回滚。
内容的提问来源于stack exchange,提问作者sree1611

