RocketMQ中拦截消息处理时出现乱码的问题

在实现RocketMQ的SendMessageHook接口时,当消息体超过4K,RocketMQ会自动进行zip压缩,导致在拦截器中看到乱码。文章解释了问题的原因,并提供了解决方案,即在拦截器中先尝试解压消息,处理后再根据需要重新压缩,确保消息正确处理。
摘要由CSDN通过智能技术生成

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)的操作,顾名思义’尝试去进行压缩消息’

image-20230727105854876

我们再进入这个方法中去看看

image-20230727110050866

image-20230727110332594

我们发现当这个消息不为批量消息且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;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RocketMQ提供了消息过滤功能,通过生产者和消费者对消息的属性、标签进行定义,并在服务端根据过滤条件进行筛选匹配,将符合条件的消息投递给消费者进行消费。\[1\] 在消费者订阅了某个主题后,RocketMQ会将该主题的所有消息投递给消费者。如果消费者只需要关注部分消息,可以通过设置过滤条件在服务端进行过滤,只获取到需要关注的消息子集,避免接收到大量无效的消息。这在一些应用场景非常有用。\[2\] 然而,有候会出现同一个tag分布在不同的队列,而消费者只分配到了部分队列。这可能导致某些消息被消费者过滤掉,但其他消费者却无法消费这些消息,从而造成消息丢失。为了解决这个问题,可以采取一些方案,例如使用广播模式,让所有消费者都能接收到所有的消息,或者使用消息过滤的方式,确保消息被正确地投递给对应的消费者。具体的解决方案可以根据实际情况进行选择和实施。\[3\] #### 引用[.reference_title] - *1* *2* *3* [rocketmq消息过滤](https://blog.csdn.net/xixingzhe2/article/details/128154159)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值