RocketMQ中拦截消息处理时出现乱码的问题
这里分享一个开发过程中遇到的关于RocketMQ中拦截消息处理时出现乱码的问题。
为何出现这类问题
在我们实现SendMessageHook接口对发送的消息做处理时,有可能会遇到拦截的数据为乱码的问题。例如
public class RocketMqSendMessageHook implements SendMessageHook {
Logger logger = LoggerFactory.getLogger("rocketmq");
@Override
public String hookName() {
return "自定义消息发送拦截器";
}
@Override
public void sendMessageBefore(SendMessageContext context) {
logger.info("准备发送消息,topic:{}", context.getMessage().getTopic());
// 我们主要是在这里对准备发送的消息进行处理,如对消息增加一些flag或者添加一些user信息等等操作。
}
@Override
public void sendMessageAfter(SendMessageContext context) {
SendResult sendResult = context.getSendResult();
if (sendResult.getSendStatus() == SendStatus.SEND_OK) {
logger.info("消息发送成功,topic:{}", context.getMessage().getTopic());
} else {
logger.error("消息发送失败,topic:{}, reason:{}", context.getMessage().getTopic(), sendResult.getSendStatus());
}
}
}
上述代码为实现SendMessageHook接口对发送消息进行处理,正常情况下我们可以在sendMessageBefore方法中拿到正常的消息并加以处理,但是有一种情况会出现乱码。**当我们传输的数据大于4k的时候,RocketMQ会对其进行zipCompress压缩算法,将消息进行压缩,然后我们在hook钩子中拿到的数据当然就是压缩之后的消息了。**我看接下来看看RocketMQ源码,看他是如何进行压缩的。
在DefaultMQProducerImpl.java这个类中的senKernelImpl方法会进行一个tryToCompressMessage(msg)的操作,顾名思义’尝试去进行压缩消息’
我们再进入这个方法中去看看
我们发现当这个消息不为批量消息且msg中的body不为空,且body长度要>=一个长度(4096)时就会触发zip压缩算法。成功返回true,失败返回false。
这样我们就能知道了,为什么我们在发送大数据(字节大于等于4k)时会在发送前拦截到的消息为乱码了。
解决问题
前面我们已经分析了这个问题的产生原因,接下来解决起来就很方便了。无非就是RocketMQ在消息发送前可能做了一个压缩操作。那么我们就可以把拦截到的消息尝试解压,无论解压成功与失败,我们都可以进行添加信息,再添加完信息之后,若原先解压成功,我们就得对消息再次进行压缩之后放行就OK了。实例代码如下:
public class RocketMqSendMessageHook implements SendMessageHook {
Logger logger = LoggerFactory.getLogger("rocketmq");
private int zipCompressLevel = Integer.parseInt(System.getProperty(MixAll.MESSAGE_COMPRESS_LEVEL, "5"));
@Override
public String hookName() {
return "自定义消息发送拦截器";
}
@Override
public void sendMessageBefore(SendMessageContext context) {
// 我们主要是在这里对准备发送的消息进行处理,如对消息增加一些flag或者添加一些user信息等等操作。
// 尝试解压
boolean msgBodyIsUnCompressed = tryToUnCompressMessage(context.getMessage());
JSONObject object = null;
try {
object = JSONObject.parseObject(context.getMessage().getBody());
} catch (Exception e) {
log.error("json转换失败", e);
}
// 添加信息
object.put("添加的数据key","添加的数据value");
// 设置会body
context.getMessage().setBody(JSON.toJSONBytes(object));
// 若开始解压成功,则需要重新压缩
if (msgBodyIsUnCompressed) {
tryToCompressMessage(context.getMessage());
}
}
@Override
public void sendMessageAfter(SendMessageContext context) {
SendResult sendResult = context.getSendResult();
if (sendResult.getSendStatus() == SendStatus.SEND_OK) {
logger.info("消息发送成功,topic:{}", context.getMessage().getTopic());
} else {
logger.error("消息发送失败,topic:{}, reason:{}", context.getMessage().getTopic(), sendResult.getSendStatus());
}
}
private boolean tryToUnCompressMessage(final Message msg) {
if (msg instanceof MessageBatch) {
return false;
}
byte[] body = msg.getBody();
if (body != null) {
try {
byte[] data = UtilAll.uncompress(body);
if (data != null) {
msg.setBody(data);
return true;
}
} catch (Exception e) {
/* log.error("tryToCompressMessage exception", e);
log.warn(msg.toString());不想看见报错可以直接不抛出*/
}
}
return false;
}
private boolean tryToCompressMessage(final Message msg) {
if (msg instanceof MessageBatch)
return false;
}
byte[] body = msg.getBody();
if (body != null) {
try {
byte[] data = UtilAll.compress(body, zipCompressLevel);
if (data != null) {
msg.setBody(data);
return true;
}
} catch (Exception e) {
/* log.error("tryToCompressMessage exception", e);
log.warn(msg.toString());不想看见报错可以直接不抛出*/
}
}
return false;
}
}