史上最全的RabbitMq的知识

目录

1、如何安装RabbitMq

1、下载erlang环境

   2、安装RabbitMq

2、SpringBoot使用RabbitMq

     1、引入pom依赖

   2、对application.properties进行配置

    3、创建发送代码

   4、对应的实体类为Order

        5、调用示例:

      6、消费者端的application.yml配置信息

    7、 消费者端的实体类

  8、接收代码

3、配置详解

RabbitMq 组件

RabbitMq的消息不丢失

生产者

消费者

RabbitMq的重试

方式2实现:

消息的幂等性

消息的格式

Mq 的序列化与反序列化


1、如何安装RabbitMq

  

1、下载erlang环境

访问https://erlang.org/download/ 下载对应的对应的 erlang 版本

这里使用的是 otp_win32_24.0.1.exe

   2、安装RabbitMq

     访问 Installing on Windows — RabbitMQ

     

下载对应的版本

然后切换到你安装的RabbitMq路径的sbin文件夹下,进入Cmd窗口

输入

rabbitmq-plugins enable rabbitmq_management

 初始账号和密码为: guest guest

2、SpringBoot使用RabbitMq

     1、引入pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

   2、对application.properties进行配置

      生产者端:

spring:
  rabbitmq:
    host: localhost
    username: guest
    password: guest
    virtual-host: /
    connection-timeout: 1500
    publisher-returns: true
    template:
      mandatory: true
    publisher-confirm-type: correlated
server:
  port: 8085

其中Spring.rabbitmq.host 表示mq服务所在的地址

Spring.rabbitmq.username: guest

Spring.rabbitmq.password: guest

分别表示使用服务的账号和密码

Spring.rabbitmq.virtual-host: / 表示使用的虚拟主机为/

Spring.rabbitmq.connection-timeout 表示连接到mq服务的超时时间

Spring.rabbitmq.publisher-returns 表示当消息无法被路由到对应的queue的队列的时候,会触发生产者的回调函数,消费成功的时候也会被触发回调

    3、创建发送代码

    

@Component
public class RabbitOrderSender {
    @Resource 
    private RabbitTemplate rabbitTemplate;
    
    final RabbitTemplate.ConfirmCallBack confirmCallback = new RabbitTemplate.ConfirmCallback() {
        
        @OVerride
        public void confirm(CorrelationData correlationData, boolean ack,String s) {
             System.out.println("CorrelationData:" + correlationData );
             if(ack) {
                 System.out.println("更新");
             } else {
                 // 失败后则进行具体的后续操作,重试或者进行补偿等手段
                 System.out.println("异常处理...");
             }
        }
    };
    
       /**
     * 将订单消息发送到mq中
     *
     * @param order
     * @throws Exception
     */
   public void sendOrder(Order order) throws Exception {
       CorrelationData correlationData = new CorrelationData();
       correlationData.setId(order.getMessageId +"");
       rabbitTemplate.setConfirmCallback(confirmCallback);
       rabbitTemplate.convertAndSend("order-exchange","order.demo",JSON.toJSONString(order),correlationData);
   }
}

   4、对应的实体类为Order

package com.example.demo.entity;

import lombok.Data;

import java.io.Serializable;

/**
 * @author zhangyang
 * @version 1.0
 * @Date 2022/7/23 13:36
 * @Description
 */
@Data
public class Order implements Serializable {

    private Long id;

    private String name;

    /**
     * 存储消息发送的唯一标识
     */
    private Long messageId;



}

        5、调用示例:

   

@SpringBootTest
class RabbitmqApplicationTests {
    
    @Autowired
    private RabbitOrderSender rabbitOrderSender;
    
    @Test
    void contextLoads() {
        
    }
    
    @Test
    public void testSend1() throws Exception {
        Order order = new Order();
        order.setId(1L);
        order.setName("测试订单1");
        order.setMessageId(1L);
        rabbitOrderSender.sendOrder(order);
    }
}

      6、消费者端的application.yml配置信息

  

spring:
  rabbitmq:
    addresses: localhost
    username: guest
    password: guest
    virtual-host: /
    connection-timeout: 15000
    listener:
      simple:
        concurrency: 5  #  消费者使用几个线程去监听队列中的消息
        max-concurrency: 15  # 最大的监听队列
        acknowledge-mode: manual  # 消息的确认模式
        prefetch: 1  # 每次取出1个消息
        default-requeue-rejected: false
        retry:
          max-attempts: 3
          max-interval: 1000ms
    template:
      mandatory: true
    publisher-confirm-type: correlated

    7、 消费者端的实体类

@Data
public class Order implements Serializable {
    
    private Long id;
    
   private String name;
   /**
   * 存储消息发送的唯一标识
   */
   private Long messageId;
}

  8、接收代码

@Component
public class OrderReceiver {
    
   @RabbitListener(bindings = 
   @QueueBinding(value = @Queue(value= "order-queue",durable="true"),
      exchange = @Exchange(name ="order-exchange",durable="true",type="topic"),
      key = "order.*"))
   @RabbitHandler
    public void onOrderMessage (@Payload String orderString,Channel channel,
                                @Headers Map<String,Object> headers) throws Exception {
        Order order = JSON.parseObject(orderString,Order.class);
        System.out.println("收到消息开始消费");
        System.out.println("订单的ID"+ order.getId());
        System.out.println("订单的名称:"+ order.getName());
        Long deliverTag = (Long ) headers.get(AmqHeaders.DELIVERY_TAG);
        // 手动进行应答
        channel.basicAck(deliveryTag,false);
        
        
    
    }
                    
}

9、调用实例

运行其消费者程序,然后运行生产者的程序, 我们就会看在界面看到如下消息

这样一个简单的rabbitmq的接收和发送就演示完成了

3、配置详解

  

1、spring.rabbitmq.publisher-confirm-type: correlated

是发布消息成功到交换机后会触发回调的方法

而这个配置有三种设置,分别为none,correlated,simple

  none 值是禁用发布确认模式,是默认值

   correlatied 是发布消息成功到交换机会触发回调的方法

   simple 值经测试有两种效果,其一效果和correlated 值一样会触发回调方法,其二在发布消息成功后使用

rabbitTemplate调用waitForConfirms或者waitForConfirmsOrDie方法等待broker 节点返回发送结果

根据返回结果来判定下一步的逻辑, 要注意的点waitForConfirmsOrDie 方法如果返回false则会关闭channel

则接下来无法发送消息到broker

2、spring.rabbitmq.publisher-returns: true

   表示消息没有路由到指定的queue 时将消息返回。

(注意: spring.rabbitmq.template.mandatory 表示消息没有被队列接收的时候是否强行退回,它的优先级要 publisher-returns 要高)

3、spring.rabbitmq.listener.simple.retry.enabled: true # 是否开启重试

spring.rabbitmq.listener.simple.retry.max-attempts: 3 # 重试最大次数,默认3条

spring.rabbitmq.listener.simple.retry.max-interval: 1000ms # 重试的最大时间间隔

        这个属性只会对消费者生效,表示 是否进行重试操作。有时候在给消费者发送消息,由于消费者服务调用第三方服务异常导致消费失败,无法进行ack确认则该条异常的消息就会重新入队,然后等到重试时间间隔后就会重新发送给消费者。 如果达到最大重试次数之后,还是没有消费成功,则会抛出异常消息,并且不会再入队,或者如果配置了死信队列的话就会被丢入到死信队列。 (死信队列后面再说)

4、Spring.rabbitmq.listener.simple.concurrency: 5 # 消费者使用几个线程去监听队列中的消息

        Spring.rabbitmq.listener.simple.max-concurrency: 15

表示可有多个消息并行处理,这种情况下,消息就不会顺序处理了

5、simple.rabbitmq.listener.simple.prefetch : 250

Spring AMQP给消费者的缓冲大小进行了限制,大小设置了250 , 如果一个消费者有250个未确认的消息后就不再给他继续投递消息了,而是给其他处理快的消费者

6、Spring.rabbitmq.virtual-host: /

     

      vhost 本质上是一个mini版的RabbitMq的服务器,拥有自己的队列、绑定、交换器和权限控制;

vhost 通过在各个实例间提供逻辑上的分离,允许你为不同的应用程序安全 保密的运行数据

vhost 是AMQP的概念基础,必须在连接时进行指定,RabbitMq 包含了默认的vhost : "/"

当在RabbitMq中创建一个用户的时候,用户通常会被指派给至少一个host,并且只能访问被指派的vhost 内的队列、交换器

vhost 可以理解为虚拟的broker,即mini-RabbitMq Server 其内部均含有独立的queue 、bind 、exchange等,最重要的是拥有的独立的权限系统,可以做到vhost范围内的用户控制,当然,从RabbitMq全局角度,vhost 可以作为不同的权限隔离的手段

7、spring.rabbitmq.listener.simple.acknowledge-mode

该配置项是用来表示消息确认方式,其有三种配置方式,分别为none 、manual 和 auto

none 意味着没有任何的应答会被发送(一旦接收成功就不管了)

manual 意味着监听者必须通过Channel.basicAck() 来告知所有的消息(

auto 意味着容器会自动应答,除非MessageListener抛出异常 这是默认的配置方式  (也就是你的监听器抛出异常了就会重新入队,然后如果你此时没有配置spring的重试机制的话,那么就会循环)

8、 spring.rabbitmq.listener.simple.default-requeue-rejected: false

该配置项是决定由于监听器抛出异常而拒绝的消息是否被重新放入队列,默认为true

分析: 关于对7、8点的 结合的分析是否入队

① 当spring.rabbitmq.listener.simple.acknowledge: none 的时候,无论defult-requeue-reject 设置的为true还是false都不会被触发入队操作 ( 即一旦接收成功后就不管了,但是没有接收成功即消息内容转对象则 会根据default-requeue-reject 来判断是否入队重试)

 

② spring.rabbitmq.listener.simple.acknowledge: auto 当default-rejected-requeue: false

此时 并不会重新入队

③ spring.rabbitmq.listener.simple.acknowledge: auto default-rejected-requeue: true

消息处理异常的时候会被重新入队,然后重新发送出来,然后很有可能会造成死循环

④ spring.rabbitmq.listener.sample.acknowledge: manual 和 default-rejected-requeue : false

这时候会被重新入队

 

⑤ spring.rabbitmq.listener.sample.acknowledge: manual 和 default-rejected-requeue : true

这时候也会被重新入队

也就是说我只要获取到了ack 就不会入队,如果没有执行到channel.basicAck 就会入队。

总结: 就是 default-requeue-rejected 默认是true, acknowledge-mode 默认是auto ,即如果消费异常了就会重新入队,再次消费。acknowledge-mode 为none的时候,只要发送过就删除队列中的消息。acknowledge-mode 为 manual 只要手动确认过了就删除,否则就返回重新入队变成unacked 状态,并且不会被重试,服务重启后会被重试消费。

所以 default-requeue-rejected 只与acknowledge-mode auto 相关

再次说明, 入不入队列跟重试没有一点关系

注意要点:

如果接收的时候使用的是对象接收 那么就需要注意发送的对象和接收的对象要一致

按照上面的要求我们就可以简单的去使用rabbitmq来发送消息,那么接下来就介绍一个RabbitMQ的各个组成部分,以及作用机制

RabbitMq 组件

         Connection是RabbitMq的socket连接,它封装了Socket协议相关部分逻辑,ConnectionFactory是Connection的制造工厂,Channel是我们的信息管道, 大部分业务操作都是在Channle 接口中完成的, 包括定义Queue 、定义Exchange、绑定Queue 以及Exchange、发布消息等

Exchange :

     消息交换机,它指定的消息按什么规则,路由到哪个队列

Queue:

    消息队列,每个消息都会被投入到一个或者多个队列中,可以看成一个有序的数组

Binding :

       绑定,是一个操作,它的作用就是把exchange 和queue 按照路由顺序绑定起来,RabbitMq 中通过绑定以及路由键作为桥梁将Exchange与Queue关联起来(Exchange---->Routing Key ---->Queue) 这样RabbitmQ 就知道如何正确的将消息路由到指定的队列了

Routing Key

      路由的关键字,exchange根据这个关键字进行消息投递,Routing key 需要将ExchangeType 以及Bind Key 联合使用才能最终生效。RabbitMq 为Routing Key 设定的长度限制为255 bytes

vhost:

        虚拟主机,一个broker 里面可以开设多个host 用作不同用户的权限分离

producer:

       消费生产者,就是投递消息的程序

consumer:

      消息的消费者,就是接受消息的程序

channel:

       消息通道,在客户端的每个连接里,可建立多个channel ,每个channel 代表一个会话任务

RabbitMq的消息不丢失

我们使用了RabbitMq作为消息传递的中间件的话,那么就一定要保证传递的消息不会丢失,这样才能保证

整个系统的数据安全性,不然用它干啥.

对于生产者与消费者可以理解他们都有ack确认机制

生产者: 保证消息到达交换机、消息再从交换机到队列

消费者: 消息正确从队列中被消费

生产者

1、confirm-callback回调

confirm-callback 回调是检验消息是否到达交换机的一个机制

rabbitTemplate.setConfirmCallback((corrlationData,ack,cause) -> {
    if(ack) {
        log.info("消息确认到达exchange");
        RedisUtils.del(correlationData.getId());
    } else {
        log.error("消息推送 MQ Exchange失败,请检查MQ是否异常,消息ID:{}",correlationData.getId())
    }
})

Return-CallBack 回调

return-CallBack 是检验消息是否到达队列的一个机制

rabbitTemplate.setReturnCallBack((message,replyCode,replyText,exchange,routingKey) -> {
   log.error("消息推送到MQ Queue 失败, 请检查路由键准建, 交换机:{},路由键:{}";
     
    RabbitMQUtils.sendMessageToMQ(exchange,replyCode,replyText,echange,routingKey);
           
});

消费者

生产者保证消息无误到达队列后,那么它的使命已经完成,而消费者要做的就是如何去正确的消费它。

1、拒绝消息: 让其重回队列,等待下一个消费者消费(可能会阻塞队列)

2、抛出异常,启用重试机制、到达重试阈值后,消息转到其他队列(利用RepublishMessageRecove类)

3、拒绝无法处理的消息转移到死信队列中进行特殊处理(需要配置死信队列)

4、拒绝消息、不重入队列,记录日志后续处理

拒绝消息 channel.basicNack(long deliverTag, boolean multiple, boolean requeue)

/**
* @param long deliverTag  消息的标志
* @param boolean multiple  是否批量处理
* @param boolean requeue  是否重入队列
*/
channel.basicNack(long deliverTag,boolean multiple,boolean requeue)

接受消息 basicAck(long deliveryTag, boolean multiple )

/**
* @ param long deliverTag 消息的标志
* @ param boolean multiple 是否批量处理
*/
basicAck(long deliverTag,boolean multiple)

RabbitMq的重试

  

       Mq的重试是针对消费者的, 并不是说消息重新进入队列,然后被消费。二者没有因果关系、没有因果关系、没有因果关系

就比如这里配置:

spring:
    rabbitmq:
        listener:
            simple:
                default-request-rejected: false
                retry:
                    max-attempts: 3   # 重试的最大次数,默认为3条
                    max-interval: 1000ms  #重试的最大时间间隔
                    enabled: true   # 是否开启重试机制

如果消费消息的时候抛出的了异常(如上面所演示的除零操作), 消息将不会被重新投递到队列中,但是会触发重试机制(这里重试3次之后该消息,将会被投递到死信队列中,如果没有配置死信队列则将会被丢弃)。所以重新投递到队列中是为了有机会让其他消费者所消费,或者保存下来异常的数据。

是消费者抛出异常后的一种重试机制,想要触发异常需要把异常抛出来

# 开启重试
spring.rabbitmq.listener.simple.retry.enable=true
# 重试次数,默认为3次
 spring.rabbitmq.listener.simple.retry.max-attemts=5 

如果使用了spring.rabbitmq.listener.simple.acknowledge-mode=manual 那么就必须调用 chanel.basicNack(tag,false,false),这样消息才会进入死信队列.

如果重试超过了阈值,则该消息就会被丢失,那么就可以将消息转移到一个特定的队列中

    1.1、消息被basic.reject() or basic.nack()  并且  spring.rabbitmq.listener.default-reqeue-rejected = false 即消息被消息者拒绝签收,并且重新入队的为false(也就是说被拒绝了,而且又回不去了,只能去往死信队列中)

      1.1.1 有一种场景需要注意下: 消费者设置了自动Ack,当重复投递次数到达设置的最大次数retry 次数之后, 消息也会投递到死信队列,但是内部原理还是调用了nack/reject

  12、消息过期,过了TTL存活时间

比如: 在消费者端设置队列的时候,设置其过期时间

@Bean
public Queue businessQueue() {
    Map<String,Object> args = new HashMap<>(3);
    args.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
    args.put("x-dead-letter-routing-key",DEAD_LETTER_ROUTING_KEY);
    args.put("x-message-ttl",5000);
    return Queue.durable(ORDER_QUEUE).withArgument(args).build()
} 

然后此队列中没有被消费的消息则过5000后就会被转移到死信队列中

     1.3、队列设置了x-max-length 最大消息的数量且当前的队列中的消息已经达到这个数量,再次投递,消息将会被挤掉, 被挤掉的是最靠近被消费那一端的消息

2 、 自定义回收器、转发到自定义到业务队列中

   下面去介绍两种方式

   方式1实现:  

package com.example.demo.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author zhangyang
 * @version 1.0
 * @Date 2022/7/30 11:17
 * @Description
 */
@Configuration
public class MyConfig {

    public static final String DEAD_LETTER_QUEUE = "rec-queue";

    public static final String DEAD_LETTER_EXCHANGE = "rec-exchange";

    public static final String DEAD_LETTER_ROUTING_KEY = "routing-key";

    public static final String ORDER_QUEUE = "order-queue";

    public static final String ORDER_EXCHANGE = "order-exchange";

    public static final String ORDER_ROUTING_KEY = "order.demo";

    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    /**
     * 创建回收队列
     *
     * @return
     */
    @Bean
    public DirectExchange errorExchange() {
        return new DirectExchange(DEAD_LETTER_EXCHANGE, true, false);
    }

    @Bean
    public Queue errorQueue() {
        return new Queue(DEAD_LETTER_QUEUE, true);
    }

    @Bean
    public Binding errorBinding(Queue errorQueue, DirectExchange errorExchange) {
        return BindingBuilder.bind(errorQueue).to(errorExchange).with(DEAD_LETTER_ROUTING_KEY);
    }


   /* @Bean
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate, DEAD_LETTER_EXCHANGE, DEAD_LETTER_ROUTING_KEY);
    }*/



    /**
     * 业务交换器
     *
     * @return
     */
    @Bean
    public DirectExchange businessExchange() {
        return new DirectExchange(ORDER_EXCHANGE);
    }

    /**
     * 业务队列
     *
     * @return
     */
    @Bean
    public Queue businessQueue() {
        Map<String, Object> args = new HashMap<>(2);
        // 配置当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // 配置当前队列的死信队列路由key 如果不设置为默认为当前队列的路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_ROUTING_KEY);
        return QueueBuilder.durable(ORDER_QUEUE).withArguments(args).build();
    }

    /**
     * 业务队列绑定
     *
     * @return
     */
    @Bean
    public Binding queueBinding() {
        return BindingBuilder.bind(businessQueue())
                .to(businessExchange())
                .with(ORDER_ROUTING_KEY);
    }


}

        消费者:

package com.example.demo.consumer;

import com.alibaba.fastjson.JSON;
import com.example.demo.config.MyConfig;
import com.example.demo.entity.Order;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;

/**
 * @author zhangyang
 * @version 1.0
 * @Date 2022/7/23 14:00
 * @Description
 */
@Slf4j
@Component
public class OrderReceiver {

    @RabbitListener(queues = MyConfig.ORDER_QUEUE)
    public void onOrderMessage
            (@Payload String orderString, Channel channel, @Headers Map<String, Object> headers) throws IOException {
        Order order = JSON.parseObject(orderString, Order.class);
        // 消费者操作
        System.out.println("收到消息开始消费");
        System.out.println("订单ID:" + order.getId());
        System.out.println("订单名称:" + order.getName());
        Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
        //手动必须
        int i = 1/0;
        // channel.basicAck(deliveryTag, false);
    }


    @RabbitListener(queues = MyConfig.DEAD_LETTER_QUEUE)
    public void deadLetterMsgReceive(Message message, Channel channel) throws IOException {
        log.info("收到死信消息:" + new String(message.getBody()));
    }
}

  这里主要的配置是


 

@Bean
public Queue businessQueue() {
    Map<String, Object> args = new HashMapL<>(2);
    // 配置当前的队列绑定的死信交换机
    args.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
    // 配置当前队列的死信队列的路由key,如果不设置则为默认的当前队列的路由key
    args.put("x-dead-letter-routing-key",DEAD_LETTER_ROUTING_KEY);
    return QueueBuilder.durable(ORDER_QUEUE).withArgsuments(args).build();
}

方式2实现:

建立一个回收队列和回收交换机

/**
* 创建回收队列
*/
@Bean
public DirectExchange errorExchange() {
    return new DirectExchange("rec-exchange",true,false);
}

@Bean
public Queue errorQueue() {
    return new Queue("rec-queue",true);
}

@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorExchange) {
    return BindingBuilder.bind(errorQueue).to(errorExchange).with("routing-key");
}

       建完回收队列,那么如果让重试后到消息回收改到队列呢? 那么下面了解一个接口: MessageConveter ,名字就是消息回收器,主要是当所有尝试都失败时进行回调

public interface MessageRecoverter {
    
    // 回调被消耗但所有重试尝试都失败的消息
    void recover(Message message,Throwable cause);
   
}

他有两个实现类: RepublishMessageRecoveter 和 RejectAndDontRequeueRecoverer(默认使用该回收器),只要创建RepublishMessageRecoveter 类到容器中即可,目的是接收重试之后无法处理的消息

/**
*  消息回收器
*   @param rabbitTemplate
*   @Return
*/
@Bean
public MessagRecover messageRecoverter(RabbitTemplate rabbitTemplate) {
     return new RepublishMessageRecoverter(rabbitTemplate,"rec-exchange","routing-key");
}

消息的幂等性

上面我们讲了RabbitMq 为了保证消息的不丢失,

1、 可以在生产者端设置return-callback 以及confirm-callback , 保证投递到交换机以及队列 中间不丢失

2、将交换机以及队列持久化,防止rabbitmq 服务重启的时候数据丢失

3、使用ackonwledge-mode 中的auto(默认) 以及 manual 配合重试来实现消费者消费消息不丢失

那么如何保证消息不会被重复消费,就比如 在扣除余额的时候如果已经扣过了再扣一遍就会导致数据异常。

1、解决方法:

使用全局MessageID 来消费方使用同一个,解决幂等性问题

使用业务逻辑保证唯一( 比如订单号码)

生产者

@Test
public void demo_09_Producer() {
    String exchange = "rabbit_work_exchange";
    String routingKey = "a";
    String msg = "携带唯一标识的一条消息";
    rabbitTemplate.convertAndSend(exchange,routingKey,msg,new CorrelationData(UUID.randomUUID().toString())));
}

如上,消费者端通过

@RabbitListener(queues = MyConfig.ORDER_QUEUE)
public void onOrderMessage(@Payload String orderString,Channel channel, @Headers Map<String,Object> headers) throws IOException {
   String randomID = (String) header.get(AmqpHeaders.DELIVERY_TAg);    
   
}

   

1、可以将ID 存储至redis中,下次再次调用的时候,先去redis 判断是否存在该ID了,如果存在表明已经消费国了则直接返回,不在消费,否则消费,然后将记录存储值redis

2、利用数据库主键去重

消息的格式

Message 类型是用来表示消息的,它是由byte[] 和 MessageProperties 类型的成员变量组成, 作用类似于HTTP 响应报文中的响应体和首部, convertAndSend 方法传递一个Object对象进去(一般是你的业务对象) 然后转化为Message对象, 这个转换操作需要借助MessageConverter 来完成。RabbitTemplate 会有一个默认的SimpleMessageConverter 然后会将byte[] 、String 和 Serizlizable 类型的对象处理成Message对象

虽然Message的内容是 byte[] 但是具体的什么格式可以根据自己项目的需要自定义,JackJsonMessageConveter 来将业务对象转化成JSON, 只需要将其对象放入到容器中即可,SpringBoot 会自动将其配置给RabbitTemplate

@Bean
public MessageConverter rabbitJsonConverter(ObjectMapper objectMapper) {
    return new Jackson2JsonMessageConverter(objectMapper);
}

对于复杂的业务,一次要声明很多队列和交换机的时候,代码会显的很冗长,可以把他们都创建在一个@Bean方法里,然后返回一个合并了的Declarables 对象,其实这些声明操作最后都是有RabbitAdmin(实现了AmqpAdmin) 完成的

Mq 的序列化与反序列化

RabbitMq 发送的时候需要将对象序列化为json 接受的时候反序列化对象

1、序列化

在任意配置类下提供RabbitTemplate的bean 覆盖SpringBoot的自动化配置,将消息转移器设置为SpringBoot 提供的JackJson 转移器

@Bean
public RabbitTemplate jacksonRabbitTemplate(ConnectionFactory connectionFactory) {
    RabbitTemplate rabbitTemplate  = new RabbitTemplate(connectionFactory);
    rabbitTemplate.setMessageConverter(new Jack2JsonMessageConverter());
    return rabbitTemplate;
}

2、反序列化

实现RabbiListenerConfigurer接口,配置反序列化转移器(可以将序列化转移器的bean 也放在这里面)

@Configuration
public class RabbitMqConfig implements RabbitListemerConfigurer {
    
    // 可以将json 反序列化为对象
    @Override
    public void configureRabbitListener(RabbitListenerEndpointRegistrar rabbitListenerEndRegistrar) {
       rabbitlistenerEndponitRegistrar.setMessageHandlerMethodFactory(messageHandleMethodFactory()); 
       
    }
    
    
   @Bean
    MessageHandlerMethodFactory messageHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
        messageHandlerMethodFactory.setMessageConverter(mappingJackson2MessageConverter());
        return messageHandlerMethodFactory;
    }
    
    @Bean
    public MappingJackson2MesageConverter mappingJackson2MessageConverter() {
        return new MappingJackson2MesageConverter();
    }
    
    // 提供自定义RabbitTemplate 将对象序列化为json串
    @Bean
    public RabbitTemplate jacksonRabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate  rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMesageConverter(new Jackson2JsonMessageConveter());
        
    }
    
   
}

rabbit 基本上总结完了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值