RabbitMQ自学笔记

RabbitMQ笔记总结

常见概念

Broker:消息服务器,类似于Mysql服务器。

Channel:连接中的一个虚拟通道,消息队列发送或者接收消息时,都是通过信道进行的。

Virtual host:类似于Mysql数据中的数据库,库与库之间的是独立的,虚拟主机与虚拟主机也是类似。

Queue:就类似于数据库中的一张表。

Message:就类似于表中的一行数据。

​ 只是一种类比便于理解,不能说一模一样,仅供参考。

工作模型

image-20230603131118768

交换机(Exchange)

Fanout(扇形交换机)

​ 没有路由规则,只要将消息发送到了Fanout中,那么就会发送到绑定了当前Fanout的队列中

image-20230602215240282

Direct(直连交换机)

​ 有指定的路由规则精准匹配,如果匹配成功则会路由到绑定对应的队列中

image-20230602215254408

Topic(主题交换机)

image-20230602215306261

​ 有指定的路由规则进行模糊匹配,如果匹配成功则会路由到绑定对应的队列中

​ 但是如果多个模糊匹配绑定了一个队列并且都成功那么只有一个消息会成功发送到该队列中

​ *:匹配一个单词(有且只有一个)

​ #:匹配多个单词

​ 举例子:boy.*=boy.a=boy.b

​ boy.#=boy.a.b=boy.c.b.d

Headers(头部交换机)

image-20230602215323483

​ 会根据消息的头部信息进行匹配路由到绑定的队列中

​ 设置头部交换机与队列的绑定规则

@Bean
public Binding bindingA(HeadersExchange headersExchange,Queue queueA){
    Map<String, Object> headerValues=new HashMap<>();
    
    headerValues.put("type","m");
    headerValues.put("status",1);
    return BindingBuilder.bind(queueA).to(headersExchange).whereAll(headerValues).match();
}
@Bean
public Binding bindingB(HeadersExchange headersExchange,Queue queueB){
    Map<String, Object> headerValues=new HashMap<>();
    //设置消息头匹配规则
    headerValues.put("type","s");
    headerValues.put("status",0);
    return BindingBuilder.bind(queueB).to(headersExchange).whereAll(headerValues).match();
}

​ 消息发送到头部交换机会根据匹配规则路由到b队列

public void sendMsg(){
    //消息属性
    MessageProperties messageProperties=new MessageProperties();
    Map<String, Object> headers=new HashMap<>();
    headers.put("type","s");
    headers.put("status",0);
    //设置消息头
    messageProperties.setHeaders(headers);
    //添加了消息属性
    Message message= MessageBuilder.withBody("hello world".getBytes())
            .andProperties(messageProperties).build();
    //头部交换机,路由key无所谓
    rabbitTemplate.convertAndSend(exchangeName,"",message);
    log.info("消息发送完毕!!!");
}

DLX(死信交换机)

image-20230602222859479

​ 死信交换机,全称为Dead-Letters-Exchanges,那么消息从正常队列投递到死信交换机的几种情况如下:

  1. 消息本身设置了TTL然后过期。
  2. 消息队列设置了TTL然后里面消息过期。
  3. 正常队列达到了设定的最大长度,那么最先入队的先被投递到DLX
  4. 消费者出现消费不确认
  5. 消费者拒绝消费(与4情况类似)

​ 那么在设置死信队列时不同的设置区别就在于设置正常队列要指定属性。

/**
     * 正常队列
     * @return
     */
    @Bean
    public Queue normalQueue(){
        Map<String, Object> arguments =new HashMap<>();
        //重点:设置这两个参数
        //设置对列的死信交换机
        arguments.put("x-dead-letter-exchange",exchangeDlxName);
        //设置死信路由key,要和死信交换机和死信队列绑定key一模一样,因为死信交换机是直连交换机
        arguments.put("x-dead-letter-routing-key","error");
        return QueueBuilder.durable(queueNormalName)
                .withArguments(arguments) // 设置对列的参数
                .build();
    }

TTL(Time to Live)

​ 有两种方式对消息进行设置过期时间:

​ 一种是对消息本身设置过期时间,在没有任何消息队列对该消息进行消费,则达到TTL则删除。

public void sendMsg(){
    MessageProperties messageProperties=new MessageProperties();
    messageProperties.setExpiration("35000"); //在发送消息时指定过期的毫秒
    Message message= MessageBuilder.withBody("hello world".getBytes())
            .andProperties(messageProperties).build();
    rabbitTemplate.convertAndSend("exchange.ttl.a","info",message);
    log.info("消息发送完毕,发送时间为:{}",new Date());
}

​ 一种是对消息队列进行设置过期时间,在消息队列中的消息在没有消费者进行消费,则达到TTL则删除,如果设置了死信队列则进入到死信队列中。

@Bean
public Queue directQueue() {
    Map<String, Object> arguments = new HashMap<>();
    arguments.put("x-message-ttl", 10000);
    return new Queue(DIRECT_QUEUE, true, false, false, arguments);
}

​ 如果消息和对列都设置过期时间,则消息的TTL以两者之间较小的那个数值为准。

消息的可靠性传递

​ 为了确保能将生产者发送的消息准确无误地发送到消费者成功进行消费,RabbitMQ为了确保消息的可靠性传递,设置了多个消息确认机制。

​ 生产者------>交换机----->队列----->消费者

Confirm模式

​ Confirm模式就是解决生产者发送的消息到交换机出现消息传递问题时进行确认的机制。

​ 创建一个类去实现RabbitTemplate.ConfirmCallback类中的抽象方法confirm方法。

public class MyConfirmCallBack implements RabbitTemplate.ConfirmCallback {
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.info("关联id为:{}",correlationData.getId()+"");
        if (ack){
            log.info("消息正确的达到交换机");
            return;
        }
        //ack =false 没有到达交换机
        log.error("消息没有到达交换机,原因为:{}",cause);
    }
}
@PostConstruct //构造方法后执行它,相当于初始化作用
 public void init(){
      rabbitTemplate.setConfirmCallback(confirmCallBack);
}

​ 简化写法可以使用Lambda表达式直接一步到位。

Return模式

​ Return模式就是解决从交换机到队列时出现消息传递问题时进行确认的机制,例如没有匹配到对应的路由规则,那么这些消息就需要我们去记录到日志中去。

@Component
@Slf4j
public class MyReturnCallBack implements RabbitTemplate.ReturnsCallback {
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}",returnedMessage.getReplyText());
    }
}
@PostConstruct
public void init(){
    rabbitTemplate.setReturnsCallback(myReturnCallBack); //设置回调
}

​ 简化写法可以使用Lambda表达式直接一步到位。

备用交换机

​ 如果不设置备用交换机,在消息通过交换机进行路由没有匹配到对应的队列时会将此消息删除,如果设置了备用交换机,那么就会将正常交换机与备用交换机绑定,在路由匹配失败时会进入到路由交换机后进入备用队列,这样我们就可以写一个程序或者一个消费者监听备用消息队列的消息进行一个日志记录等。

​ 备用交换机需要用扇形交换机

@Bean //备用交换机
public FanoutExchange alternateExchange(){
    return ExchangeBuilder.fanoutExchange(exchangeAlternateName).build();
}

​ 然后正常交换机在使用建造者模式时要使用alternate()指定备用交换机。

@Bean
public DirectExchange normalExchange(){
    return ExchangeBuilder // 默认为持久化的,默认不自动删除
            .directExchange(exchangeNormalName) // 交换机的名字
            .alternate(exchangeAlternateName) //设置备用交换机 alternate-exchange
            .build();
}

​ 其他的操作是一样的。

消息手动确认和拒绝

​ 这个是解决消息从队列中到消费者出现消息传递问题时的消息确认机制,如果消息到了消费者,但是此时出现了网络异常或者业务异常,消息还没有被消费者消费,那么就会进行消息的一个手动不确认或者消息拒绝;如果消息完全被消费者消费了没有出现异常,那么进行手动确认。

​ RabbitMq默认开启自动确认机制,需要我们在yml文件中配置:acknowledge-mode: manual

rabbitmq:
  listener:
    simple:
      acknowledge-mode: manual # 开启消费者的手动确认

@RabbitListener(queues = {"queue.normal.4"})
public void receiveMsg(Message message, Channel channel) {
    //获取消息属性
    MessageProperties messageProperties = message.getMessageProperties();
    //获取消息的唯一标识,类似身份证或者学号
    long deliveryTag = messageProperties.getDeliveryTag();

    try {
        byte[] body = message.getBody();
        String str = new String(body);
        log.info("接收到的消息为:{},接收时间为:{}", str, new Date());
        //TODO 业务逻辑处理 比如插入数据库
        int a = 1 / 0; //设置一个异常让消息不能被手动确认
        channel.basicAck(deliveryTag, false);
    } catch (Exception e) {
        log.error("接收着出现问题:{}", e.getMessage());
        try {
            channel.basicNack(deliveryTag, false, false);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        throw new RuntimeException(e);
    }
}
  1. 获取消息的唯一标识getDeliveryTag()
  2. 消费者手动确认basicAck第二个参数设置为false代表只确认当前消息,为true代表当前消息之前的消息都批量确认。
  3. 消费者手动不确认basicNack,第二个参数false通basicAck方法一样,第三个参数false代表不重新入原队列,就会变成死信,如果该队列绑定了死信交换机,则会传递到死信交换机再根据路由规则路由到死信队列。

实战

​ 将某某点评中使用Redis的stream消息队列更改为RabbitMQ的死信队列进行异步下单的操作

posted @ 2023-06-06 11:41  stepForward-  阅读(46)  评论(0)    收藏  举报
@format