RabbitMQ(三)消息序列化

本文深入探讨了RabbitMQ中的消息序列化机制,包括MessageConvert接口的作用、SimpleMessageConverter与Jackson2JsonMessageConverter的区别,以及如何使用@RabbitListener和自定义MessageConverter进行消息处理。

RabbitMQ(三)消息序列化

MessageConvert

  • 涉及网络传输的应用序列化不可避免,发送端以某种规则将消息转成 byte 数组进行发送,接收端则以约定的规则进行 byte[] 数组的解析
  • RabbitMQ 的序列化是指 Message 的 body 属性,即我们真正需要传输的内容,RabbitMQ 抽象出一个 MessageConvert 接口处理消息的序列化,其实现有 SimpleMessageConverter(默认)、Jackson2JsonMessageConverter 等
  • 当调用了 convertAndSend 方法时会使用 MessageConvert 进行消息的序列化
  • SimpleMessageConverter 对于要发送的消息体 body 为 byte[] 时不进行处理,如果是 String 则转成字节数组,如果是 Java 对象,则使用 jdk 序列化将消息转成字节数组,转出来的结果较大,含class类名,类相应方法等信息。因此性能较差
  • 当使用 RabbitMQ 作为中间件时,数据量比较大,此时就要考虑使用类似 Jackson2JsonMessageConverter 等序列化形式以此提高性能

@RabbitListener 用法

  • 使用 @RabbitListener 注解标记方法,当监听到队列 debug 中有消息时则会进行接收并处理
@RabbitListener(queues = "test")
public void processMessage1(Message message) {
    System.out.println(new String(message));
}

注意

  • 消息处理方法参数是由 MessageConverter 转化,若使用自定义 MessageConverter则需要在 RabbitListenerContainerFactory实例中去设置(默认 Spring 使用的实现是 SimpleRabbitListenerContainerFactory
  • 消息的 content_type 属性表示消息 body 数据以什么数据格式存储,接收消息除了使用 Message 对象接收消息(包含消息属性等信息)之外,还可直接使用对应类型接收消息 body 内容,但若方法参数类型不正确会抛异常:
    • application/octet-stream:二进制字节数组存储,使用 byte[]
    • application/x-java-serialized-object:java 对象序列化格式存储,使用 Object、相应类型(反序列化时类型应该同包同名,否者会抛出找不到类异常)
    • text/plain:文本数据类型存储,使用 String
    • application/json:JSON 格式,使用 Object、相应类型

img

ZiVg2.png

@Payload 与 @Headers

  • 使用 @Payload@Headers注解可以消息中的 body 与 headers 信息
@RabbitListener(queues = "debug")
public void processMessage1(@Payload String body, @Headers Map<String,Object> headers) {
    System.out.println("body:"+body);
    System.out.println("Headers:"+headers);
}
  • 也可以获取单个 Header属性
@RabbitListener(queues = "debug")
public void processMessage1(@Payload String body, @Header String token) {
    System.out.println("body:"+body);
    System.out.println("token:"+token);
}

通过 @RabbitListener 注解声明 Binding

  • 通过 @RabbitListener 的 bindings 属性声明 Binding(若 RabbitMQ 中不存在该绑定所需要的 Queue、Exchange、RouteKey 则自动创建,若存在则抛出异常)
@RabbitListener(bindings = @QueueBinding(
        exchange = @Exchange(value = "topic.exchange",durable = "true",type = "topic"),
        value = @Queue(value = "consumer_queue",durable = "true"),
        key = "key.#"
))
public void processMessage1(Message message) {
    System.out.println(message);
}

@RabbitListener 和 @RabbitHandler 搭配使用

  • @RabbitListener 可以标注在类上面,需配合 @RabbitHandler 注解一起使用
  • @RabbitListener 标注在类上面表示当有收到消息的时候,就交给 @RabbitHandler 的方法处理,具体使用哪个方法处理,根据 MessageConverter 转换后的参数类型
@Component
@RabbitListener(queues = "consumer_queue")
public class Receiver {

    @RabbitHandler
    public void processMessage1(String message) {
        System.out.println(message);
    }

    @RabbitHandler
    public void processMessage2(byte[] message) {
        System.out.println(new String(message));
    }
    
}

Message 内容对象序列化与反序列化

使用 Java 序列化与反序列化

  • 默认的 SimpleMessageConverter 在发送消息时会将对象序列化成字节数组,若要反序列化对象,需要自定义 MessageConverter
@Configuration
public class RabbitMQConfig {

    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new MessageConverter() {
            @Override
            public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
                return null;
            }

            @Override
            public Object fromMessage(Message message) throws MessageConversionException {
                try(ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(message.getBody()))){
                    return (User)ois.readObject();
                }catch (Exception e){
                    e.printStackTrace();
                    return null;
                }
            }
        });

        return factory;
    }

}
@Component
@RabbitListener(queues = "consumer_queue")
public class Receiver {

    @RabbitHandler
    public void processMessage1(User user) {
        System.out.println(user.getName());
    }

}

使用 JSON 序列化与反序列化

  • RabbitMQ 提供了 Jackson2JsonMessageConverter 来支持消息内容 JSON 序列化与反序列化
  • 消息发送者在发送消息时应设置 MessageConverter 为 Jackson2JsonMessageConverter
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
  • 发送消息
User user = new User("linyuan");
rabbitTemplate.convertAndSend("topic.exchange","key.1",user);
  • 消息消费者也应配置 MessageConverter 为 Jackson2JsonMessageConverter
@Configuration
public class RabbitMQConfig {
    
    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }

}
  • 消费消息
@Component
@RabbitListener(queues = "consumer_queue")
public class Receiver {

    @RabbitHandler
    public void processMessage1(@Payload User user) {
        System.out.println(user.getName());
    }

}
  • 注意:被序列化对象应提供一个无参的构造函数,否则会抛出异常
RabbitMQ消息发送过程中,如果消息被多次序列化,会导致额外的性能开销,增加 CPU 使用率和网络传输负载。为避免这种情况,需优化消息序列化流程,确保只在必要环节进行一次序列化操作。 ### 优化 RabbitMQ 消息发送以避免多次序列化RabbitMQ 中,消息是以二进制数组形式传输的,因此如果消息是实体对象,需要将其序列化为字节数组[^1]。为避免重复序列化,应使用统一的消息转换机制,并避免在代码中手动进行 JSON 转换。 #### 使用 Jackson2JsonMessageConverter 实现自动序列化 Spring 提供了 `Jackson2JsonMessageConverter`,它可以在发送消息时自动将 Java 对象序列化为 JSON 格式,并转换为字节数组,从而避免手动调用 `JSON.toJSONString()` 等方法导致的重复操作。 ```java @Configuration public class RabbitMQConfig { @Bean public MessageConverter jackson2JsonMessageConverter() { return new Jackson2JsonMessageConverter(); } @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter()); return rabbitTemplate; } } ``` 通过上述配置,`RabbitTemplate` 在调用 `convertAndSend()` 时会自动将传入的对象序列化为 JSON 字节数组,无需再手动调用 `JSON.toJSONString()` 或 `getBytes()`。 #### 避免手动序列化 在原始代码中,`mqMessageBodyVo` 被两次调用 `JSON.toJSONString()`,一次用于日志输出,另一次用于构造 `Message` 对象。这会导致对象被序列化两次。应仅保留日志记录时的序列化,而在构建消息体时直接传递原始对象: ```java MqMessageBodyVo<EquityToSLKVo> mqMessageBodyVo = getMessageVo(item, equity, type); log.info("send to slk message:{}", JSON.toJSONString(mqMessageBodyVo, SerializerFeature.WriteMapNullValue)); rabbitTemplate.convertAndSend(EquityConstants.EQUITY_CHANGE_EXCHANGE, EquityConstants.EQUITY_CHANGE_ROUTING_KEY, mqMessageBodyVo); ``` 上述方式仅在日志中进行一次序列化消息发送由 `RabbitTemplate` 自动处理,避免了重复操作。 #### 确保消费者端使用相同的消息转换器 在消费端使用 `@RabbitListener` 时,也应配置相同的 `Jackson2JsonMessageConverter`,以确保反序列化过程一致,避免因格式不匹配导致的错误。 ```java @RabbitListener(queues = "equityChangeQueue", messageConverter = "jackson2JsonMessageConverter") public void handleEquityChange(MqMessageBodyVo<EquityToSLKVo> message) { // 处理逻辑 } ``` #### 使用通用 DTO 对象减少序列化复杂度 为了提升性能,建议使用结构清晰、字段精简的 DTO 对象作为消息体,避免在消息中传输复杂的业务对象,从而减少序列化与反序列化的开销。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值