RabbitMQ实战指南 - 笔记

Rabbitmq笔记+入门教程+示例 RabbitMQ环境搭建,介绍队列类型,spring-rabbit示例, 使用RabbitMQ完成商品数据的同步 立即下载
  1. RabbitMQ简介
    1. 什么是消息中间件

      1. 消息:

      2. 消息队列中间件:

        1. 传递模式:

          1. 点对点:基于队列

          2. 发布/订阅:基于内容节点

    2. 消息中间件的作用

      1. 解耦

      2. 冗余

      3. 扩展性

      4. 削峰

      5. 可恢复性

      6. 顺序保证

      7. 缓冲

      8. 异步通信

    3. RabbitMQ的起源

    4. RabbitMQ的安装及简单使用

      1. 安装Erlang

        sudo yum -y install erlang

         

      2. 安装RabbitMQ

      3. 运行RabbitMQ

        1. rabbitmq-server -detached

        2. rabbitmqctl status

        3. rabbitmqctl cluster_status

      4. 生产和消费消息

        1. com.rabbitmq.amqp-client

        2. 默认用户密码(只能通过本地网络访问):guest/guest

          1. 添加用户

            [root@Lime-CentOS ~]# rabbitmqctl add_user root root
            Creating user "root" ...
            ...done.
            

             

          2. 设置权限

            [root@Lime-CentOS ~]# rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
            Setting permissions for user "root" in vhost "/" ...
            ...done.
            

             

          3. 设置管理员角色

            [root@Lime-CentOS ~]# rabbitmqctl set_user_tags root administrator
            Setting tags for user "root" to [administrator] ...
            ...done.
            

             

        3. 生产者客户端代码

          package club.limeyu.rabbitmq;
          
          import com.rabbitmq.client.*;
          
          import java.io.IOException;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author: L
           * @Description:
           * @Date: create in 2020-12-24 21:17
           */
          public class RabbitProducer {
          
              private static final String EXCHANGE_NAME = "exchange_demo";
              private static final String ROUTING_KEY = "routingkey_demo";
          
              private static final String QUEUE_NAME = "queue_demo";
          
              private static final String IP_ADDRESS = "127.0.0.1";
          
              private static final int PORT = 5672; // RabbitMQ 服务端默认端口号为5672
          
              public static void main(String[] args) throws IOException, TimeoutException {
          
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setHost(IP_ADDRESS);
                  connectionFactory.setPort(PORT);
          
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  Connection connection = connectionFactory.newConnection();// 创建连接
                  Channel channel = connection.createChannel(); // 创建信道
          
                  // 创建一个type="direct"、持久化的、非自动删除的交换器
                  channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
          
                  // 创建一个持久化、非排他的、非自动删除的队列
                  channel.queueDeclare(QUEUE_NAME, true, false, false, null);
          
                  // 将交换器与队列通过路由键绑定
                  channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
          
                  // 发送一条持久化的消息
                  String message = "Hello World";
          
                  channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
          
                  channel.close();
                  connection.close();
          
                  System.out.println("Producer Over");
              }
          }
          

           

        4. 消费者客户端代码

          package club.limeyu.rabbitmq;
          
          
          import com.rabbitmq.client.*;
          
          import java.io.IOException;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author: L
           * @Description:
           * @Date: create in 2020-12-24 21:33
           */
          public class RabbitConsumer {
          
              private static final String QUEUE_NAME = "queue_demo";
          
              public static final String IP_ADDRESS = "127.0.0.1";
          
              private static final int PORT = 5672;
          
              public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                  Address[] addresses = {
                          new Address(IP_ADDRESS, PORT)
                  };
          
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  // 这里的连接方式与生产者的demo略有不同,注意辨别区别
                  Connection connection = connectionFactory.newConnection(addresses); // 创建连接
                  Channel channel = connection.createChannel(); // 创建信道
                  channel.basicQos(64); // 设置客户端最多接收未被ack的消息个数
          
                  DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                      @Override
                      public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                          System.out.println("recv message : " + new String(body));
                          try {
                              TimeUnit.SECONDS.sleep(1);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          channel.basicAck(envelope.getDeliveryTag(), false);
                      }
                  };
          
                  channel.basicConsume(QUEUE_NAME, defaultConsumer);
          
                  // 等待回调函数执行完毕之后,关闭资源
                  TimeUnit.SECONDS.sleep(5);
          
                  channel.close();
                  connection.close();
          
                  System.out.println("Consumer Over");
              }
          }
          

           

        5. 啦啦啦

  2. RabbitMQ入门

    1. 相关概念介绍

      RabbitMQ架构模型
      1. 生产者和消费者

        1. Producer:生产者,投递消息。

          1. 标签(label)

          2. 消息体(payload):

          3. 啦啦啦

        2. consumer:消费者,接收消息。

        3. Broker:消息中间件的服务节点。

        4. 生产者将消息存入RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程

        5. AMQP协议

          1. 深入理解AMQP协议

        6. 啦啦啦

      2. 队列

        1. Queue:队列,是RabbitMQ的内部对象,用于存储消息。

        2. 多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。

        3. RabbitMQ不支持队列层面的广播消费。

      3. 交换器、路由键、绑定

        1. 交换器类型

          1. 直连交换机(direct)

          2. 扇形交换机(fanout)

            1. 不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到扇形交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。 

            2. 任何发送到Fanout Exchange的消息都会被转发到与该Exchange绑定(Binding)的所有Queue上。

              1. 可以理解为路由表的模式

              2. 这种模式不需要RouteKey

              3. 这种模式需要提前将Exchange与Queue进行绑定,一个Exchange可以绑定多个Queue,一个Queue可以同多个Exchange进行绑定。

              4. 如果接受到消息的Exchange没有与任何Queue绑定,则消息会被抛弃。

            3. DEMO

              package club.limeyu.rabbitmq.tt.fanout;
              
              import com.rabbitmq.client.*;
              import lombok.extern.slf4j.Slf4j;
              
              import java.io.IOException;
              import java.util.concurrent.TimeoutException;
              
              /**
               * @Author liangmy
               * @Description:
               * @Create: 2021/2/20 上午11:44
               */
              @Slf4j
              public class RabbitProducer {
              
                  public static void main(String[] args) throws IOException, TimeoutException {
                      ConnectionFactory connectionFactory = new ConnectionFactory();
                      connectionFactory.setHost("www.limeyu.club");
                      connectionFactory.setPort(5672);
                      connectionFactory.setUsername("root");
                      connectionFactory.setPassword("root");
              
                      Connection connection = connectionFactory.newConnection();
                      Channel channel = connection.createChannel();
                      
                      channel.exchangeDeclare("exchange_fanout", BuiltinExchangeType.FANOUT, true);
              
                      channel.queueDeclare("queue_fanout_1", true, false, false, null);
              
                      channel.queueDeclare("queue_fanout_2", true, false, false, null);
              
                      channel.queueBind("queue_fanout_1", "exchange_fanout", "rk_fanout_1");
              
                      channel.queueBind("queue_fanout_2", "exchange_fanout", "rk_fanout_2");
              
                      log.info("扇形交换器不需要RouteKey");
                      channel.basicPublish("exchange_fanout", "", MessageProperties.TEXT_PLAIN, "this message is sent to exchange_fanout".getBytes());
              
                      channel.close();
                      connection.close();
              
              
                  }
              }
              
              
              
              
              package club.limeyu.rabbitmq.tt.fanout;
              
              import com.rabbitmq.client.*;
              import lombok.extern.slf4j.Slf4j;
              
              import java.io.IOException;
              import java.util.concurrent.TimeUnit;
              import java.util.concurrent.TimeoutException;
              
              /**
               * @Author liangmy
               * @Description:
               * @Create: 2021/2/20 上午11:53
               */
              @Slf4j
              public class RabbitConsumer1 {
                  public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                      ConnectionFactory connectionFactory = new ConnectionFactory();
                      connectionFactory.setHost("www.limeyu.club");
                      connectionFactory.setPort(5672);
                      connectionFactory.setUsername("root");
                      connectionFactory.setPassword("root");
              
                      Connection connection = connectionFactory.newConnection();
              
                      Channel channel = connection.createChannel();
              
                      DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                          @Override
                          public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                              log.info("Msg : {}", new String(body));
                              channel.basicAck(envelope.getDeliveryTag(), false);
                          }
                      };
              
                      channel.basicConsume("queue_fanout_1", false, defaultConsumer);
              
                      TimeUnit.SECONDS.sleep(3);
                      channel.close();
              
                      connection.close();
                  }
              }
              
              
              
              
              
              package club.limeyu.rabbitmq.tt.fanout;
              
              import com.rabbitmq.client.*;
              import lombok.extern.slf4j.Slf4j;
              
              import java.io.IOException;
              import java.util.concurrent.TimeUnit;
              import java.util.concurrent.TimeoutException;
              
              /**
               * @Author liangmy
               * @Description:
               * @Create: 2021/2/20 上午11:53
               */
              @Slf4j
              public class RabbitConsumer2 {
                  public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                      ConnectionFactory connectionFactory = new ConnectionFactory();
                      connectionFactory.setHost("www.limeyu.club");
                      connectionFactory.setPort(5672);
                      connectionFactory.setUsername("root");
                      connectionFactory.setPassword("root");
              
                      Connection connection = connectionFactory.newConnection();
              
                      Channel channel = connection.createChannel();
              
                      DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                          @Override
                          public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                              log.info("Msg : {}", new String(body));
                              channel.basicAck(envelope.getDeliveryTag(), false);
                          }
                      };
              
                      channel.basicConsume("queue_fanout_2", false, defaultConsumer);
              
                      TimeUnit.SECONDS.sleep(3);
                      channel.close();
              
                      connection.close();
                  }
              }
              

               

          3. 主题交换机(topic)

            1. # :替代零个或更多的单词

            2. * :代替一个单词

            3. mandatory : 

          4. 头交换机(headers)

        2. 路由键

        3. 绑定键

      4. RabbitMQ运转流程

    2. AMQP协议介绍

      1. Module Layer : 位于协议最高层,主要定义了一些供客户端调用的命令,客户端可以利用这些命令实现自己的业务逻辑。例如,客户端可以使用Queue.Declare命令声明一个队列或使用Basic.Consume订阅消费一个队列中的消息。

      2. Session Layer : 位于中间层,主要负责将客户端的命令发送给服务器,再将服务端的应答返回给客户端,主要为客户端与服务器之间的通信提供可靠性同步机制和错误处理。

      3. Transport Layer : 位于最底层,主要传输二进制数据流,提供帧的处理、信道服用、错误检测和数据表示等。

      4. AMQP生产者流转过程

      5. 啦啦啦

      6. AMQP消费者流转过程

      7. 啦啦啦

      8. AMQP命令概览

  3. 客户端开发向导

    1. 连接RabbitMQ

      1. ShutdownSignalException

    2. 使用交换器和队列

      1. exchangeDeclare方法详解

        1. Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException;

          1. autoDelete:设置是否自动删除。autoDelete设置为true则表示自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑。注意不能错误地把这个参数理解为:“当与此交换器连接的客户端都断开时,RabbitMQ会自动删除本交换器”。

          2. internal:设置是否是内置的。如果设置为true,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。

          3. argument:其他一些结构化参数。

        2. void exchangeDeclareNoWait(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException;

        3. Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException;

      2. queueDeclare方法详解

      3. queueBind方法详解

      4. exchangeBind方法详解

      5. 何时创建

    3. 发送消息

      1. void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException;

        1. props:消息的基本属性集(14个属性成员)。

          1. contentType

          2. contentEncoding

          3. headers(Map<String, Object>)

          4. deliveryMode

          5. priority

          6. correlationId:用来关联请求(request)和其调用RPC之后的回复(response)

          7. replyTo:通常用来设置一个回调队列

          8. expiration

          9. messageId

          10. timestamp

          11. type

          12. userId

          13. appId

          14. clusterId

        2. mandatory

        3. immediate

    4. 消费信息

      1. 消费模式

        1. 推模式(push):Basic.Consume

        2. 拉模式(pull):Basic.Get

    5. 消费端的确认与拒绝

      1. 消息确认机制(message acknowledgement)

      2. 等待投递给消费者的消息;

      3. 已经投递给消费者,但是还没有收到消费者确认信号的消息。

      4. 如果RabbitMQ一直没有收到消费者的确认信息,并且消费此信息的消费者已经断开链接,则RabbitMQ会安排该消息重新进入队列。

      5. rabbitmqctl list_queues name messages_ready messages_unacknowledged

      6. void basicReject(long deliveryTag, boolean requeue) throws IOException

        1. deliveryTag : 可以看作消息的编号,一个64位的长整型值。

        2. requeue:设置RabbitMQ是否需要重新将这条消息存入队列。

      7. void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException

        1. multiple:false则表示拒绝编号为deliveryTag的这一条消息。true则表示拒绝deliveryTag编号之前所有未被当前消费者确认的消息。

      8. Basic.Recover

        1. Basic.RecoverOk basicRecover() throws IOException

        2. Basic.RecoverOk basicRecover(boolean requeue) throws IOException

        3. 是否恢复消息到队列,参数是是否requeue,true则重新入队列,并且尽可能的将之前recover的消息投递给其他消费者消费,而不是自己再次消费。false则消息会重新被投递给自己。

    6. 关闭连接

      1. public void addShutdownListener(ShutdownListener listener);

      2. public void removeShutdownListener(ShutdownListener listener);

      3. ShutdownSignalException

        1. public boolean isHardError()

  4. RabbitMQ进阶

    1. 消息何去何从

      1. mandatory参数

        1. 当mandatory参数设为true时,交换器无法根据自身的类型和路由键找到一个符合条件的队列,那么RabbitMQ会调用Basic.Return命令将消息返回给生产者。当mandatory参数设置为false时,出现上述情形,则消息直接被丢弃。

        2. 那么生产者如何获取到没有被正确路由到合适队列的消息呢?这时候可以通过调用channel.addReturnListener来添加ReturnListener监听器实现。

        3. Demo

          package club.limeyu.rabbitmq.tt.mandatory;
          
          import com.rabbitmq.client.*;
          import lombok.extern.slf4j.Slf4j;
          
          import java.io.IOException;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/22 下午2:39
           */
          @Slf4j
          public class RabbitProducer {
              public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                  ConnectionFactory connectionFactory = new ConnectionFactory();
          
                  connectionFactory.setPassword("root");
                  connectionFactory.setUsername("root");
                  connectionFactory.setPort(5672);
                  connectionFactory.setHost("www.limeyu.club");
          
                  Connection connection = connectionFactory.newConnection();
          
                  Channel channel = connection.createChannel();
          
                  // 添加 ReturnListener 监听器, 如果消息发送时,mandatory = true,且没有被正确路由,RabbitMQ Server 调用 Basic.Return 返回此消息给发送者。此监听器负责接收 RabbitMQ Server 返回的消息并处理。
                  channel.addReturnListener(new ReturnListener() {
                      @Override
                      public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                          log.info("replyCode : {}, replyText : {}, exchange : {}, routingKey : {}, BasicProperties : {}, body : {}", replyCode, replyText, exchange, routingKey, properties, new String(body));
                      }
                  });
          
                  channel.exchangeDeclare("exchange_mandatory", BuiltinExchangeType.DIRECT, true, false, null);
          
                  channel.queueDeclare("queue_mandatory", true, false, false, null);
          
                  channel.queueBind("queue_mandatory", "exchange_mandatory", "rk_mandatory");
          
                  String mandatory_state = new String[]{"rk_mandatory_correct", "rk_mandatory_incorrect_has_rk", "rk_mandatory_incorrect_no_rk"}[2];
          
                  switch (mandatory_state) {
                      case "rk_mandatory_correct":
                          channel.basicPublish("exchange_mandatory", "rk_mandatory", true, MessageProperties.PERSISTENT_TEXT_PLAIN, "this message mandatory is true, if cannot correct routing, will return back sender.".getBytes());
                          break;
                      case "rk_mandatory_incorrect_has_rk":
                          channel.basicPublish("exchange_mandatory", "rk_mandatory_incorrect_has_rk", true, MessageProperties.PERSISTENT_TEXT_PLAIN, "this message mandatory is true, if cannot correct routing, will return back sender.".getBytes());
                          break;
                      default:
                          // 不指定路由键
                          channel.basicPublish("exchange_mandatory", "", true, MessageProperties.PERSISTENT_TEXT_PLAIN, "this message mandatory is true, if cannot correct routing, will return back sender.".getBytes());
                  }
          
                  TimeUnit.SECONDS.sleep(1);
          
                  channel.close();
                  connection.close();
          
          
              }
          }
          

           

      2. immediate参数

        1. 当immediate参数设为true时,如果交换器在将消息路由到队列时发现队列上并不存在任何消费者,那么这条消息将不会存入队列中。当与路由键匹配的所有队列都没有消费者时,该消息会通过Basic.Return返回至生产者。

      3. 备份交换器(Alternate Exchange简称AE)

        1. 生产者在发送消息的时候如果不设置mandatory参数,那么消息在未被路由的情况下将会丢失;如果设置了mandatory参数,那么需要添加ReturnListener的编辑逻辑,生产者的代码将变得复杂。如果既不想复杂化生产者的编程逻辑,又不想消息丢失,那么可以使用备份交换器,可以将未被路由的消息存储在RabbitMQ中,再在需要的时候去处理这些消息。

        2. 可以通过在声明交换器(调用channel.exchangeDeclare方法)的时候添加alternate-exchange参数来实现,也可以通过策略的方式实现。

          package club.limeyu.rabbitmq.tt.ae;
          
          import com.rabbitmq.client.*;
          
          import java.io.IOException;
          import java.util.HashMap;
          import java.util.Map;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/22 下午1:30
           */
          public class RabbitProducer {
              public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setHost("www.limeyu.club");
                  connectionFactory.setPort(5672);
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  Connection connection = connectionFactory.newConnection();
          
                  Channel channel = connection.createChannel();
          
          
                  channel.exchangeDeclare("exchange_ae", BuiltinExchangeType.DIRECT, true, false, null);
          
                  channel.queueDeclare("queue_ae_no_rk", true, false, false, null);
          
                  // 没有绑定路由键
                  channel.queueBind("queue_ae_no_rk", "exchange_ae", "");
          
          
                  channel.queueDeclare("queue_ae_rk_1", true, false, false, null);
          
                  // 绑定路由键 rk_queue_ae_rk_1
                  channel.queueBind("queue_ae_rk_1", "exchange_ae", "rk_queue_ae_rk_1");
          
          
                  channel.queueDeclare("queue_ae_rk_2", true, false, false, null);
          
                  // 绑定路由键 rk_queue_ae_rk_2
                  channel.queueBind("queue_ae_rk_2", "exchange_ae", "rk_queue_ae_rk_2");
          
                  Map<String, Object> arguments_exchange_normal = new HashMap<>();
                  arguments_exchange_normal.put("alternate-exchange", "exchange_ae");
                  
                  // 通过 alternate-exchange = exchange_ae(备份交换器名称)为 exchange_normal 交换器 绑定 exchange_ae 备份交换器
                  channel.exchangeDeclare("exchange_normal", BuiltinExchangeType.DIRECT, true, false, arguments_exchange_normal);
          
                  channel.queueDeclare("queue_normal", true, false, false, null);
          
                  channel.queueBind("queue_normal", "exchange_normal", "rk_normal");
          
                  String rk_state = new String[]{"normal", "no_rk", "rk_1", "rk_2", "have_a_nice_day"}[4];
          
                  switch (rk_state) {
                      case "normal":
                          // 正常情况。发送消息到 exchange_normal 交换器 通过 rk_normal 绑定的 queue_normal 消息队列中。
                          channel.basicPublish("exchange_normal", "rk_normal", MessageProperties.PERSISTENT_TEXT_PLAIN, "this message on rk_normal".getBytes());
                          break;
                      case "no_rk":
                          // 没有指定路由键。发送消息到 exchange_normal 交换器 通过 alternate-exchange = exchange_ae 绑定的 exchange_ae 备份交换器上。
                          // 由于没有指定路由键,消息被 exchange_ae 交换器 路由到 没有绑定路由键的 queue_ae_no_rk 消息队列中。
                          channel.basicPublish("exchange_normal", "", MessageProperties.PERSISTENT_TEXT_PLAIN, "this message no rk".getBytes());
                          break;
                      case "rk_1":
                          // 指定一个正常交换器不存在,备份交换器存在的路由键。
                          channel.basicPublish("exchange_normal", "rk_queue_ae_rk_1", MessageProperties.PERSISTENT_TEXT_PLAIN, "this message on rk_queue_ae_rk_1".getBytes());
                          break;
                      case "rk_2":
                          // 指定一个正常交换器不存在,备份交换器存在的路由键。
                          channel.basicPublish("exchange_normal", "rk_queue_ae_rk_2", MessageProperties.PERSISTENT_TEXT_PLAIN, "this message on rk_queue_ae_rk_2".getBytes());
                          break;
                      default:
                          // 指定一个正常交换器不存在,备份交换器也不存在的路由键。
                          channel.basicPublish("exchange_normal", "have_a_nice_day", MessageProperties.PERSISTENT_TEXT_PLAIN, "this message on have_a_nice_day".getBytes());
                          break;
                  }
          
          
                  TimeUnit.SECONDS.sleep(1);
          
                  channel.close();
                  connection.close();
              }
          }
          

           

        3. 啦啦啦

    2. 过期时间(TTL)

      1. 设置消息的TTL

        1. 通过队列声明设置

          1. x-message-ttl

          2. 毫秒

          3. 如果不设置TTL,则表示此消息不会过期;如果将TTL设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃。

                    Map<String, Object> arguments = new HashMap<>();
                    arguments.put("x-message-ttl", 6000);
                    
                    channel.queueDeclare("messageTtlQueue", true, false, false, arguments);
            

             

        2. 通过消息单独指定

          1. expiration

          2. 毫秒

                    AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
            
                    builder.deliveryMode(2); // 持久化消息
                    builder.expiration("6000"); // 设置TTL=6000ms
            
                    AMQP.BasicProperties basicProperties = builder.build();
            
                    channel.basicPublish("messageTtlExchange", "messageTtlRoutingKey", basicProperties, "messageTtlBody".getBytes());
            

             

      2. 设置队列的TTL

        1. x-expires

        2. 毫秒

        3. 通过channel.queueDeclare方法中的x-expires参数可以控制队列被自动删除前处于未使用状态的时间。未使用的意思是队列上没有任何的消费者,队列也没有被重新声明,并且在过期时间段内也未被调用过Basic.Get命令。

        4. RabbitMQ会确保在过期时间到达后将队列删除,但是不保障删除的动作有多及时。在RabbitMQ重启后,持久化的队列的过期时间会被重新计算。

                  Map<String, Object> arguments = new HashMap<>();
                  arguments.put("x-expires", 1000 * 10); // 创建一个过期时间10秒的队列(rabbitmqctl list_queues)
          
                  channel.queueDeclare("queueQueueTtl", false, false, false, arguments);
          

           

    3. 死信队列(Dead-Letter-Exchange)

      1. 当消息在一个队列中变成死信(dead message)之后,它能被重新被发送到另一个交换器中,这个交换器就是DLX,绑定DLX的队列就称之为死信队列。

      2. 消息变成死信一般是由于以下几种情况:

        1. 消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false;

        2. 消息过期;

        3. 队列达到最大长度;

      3. 通过在channel.queueDeclare方法中设置x-dead-letter-exchange参数来为这个队列添加DLX

        // 生产者
        package club.limeyu.rabbitmq.tt.dlx.reject;
        
        import com.rabbitmq.client.*;
        
        import java.io.IOException;
        import java.util.HashMap;
        import java.util.Map;
        import java.util.concurrent.TimeoutException;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/19 下午5:25
         */
        public class RabbitProducer {
        
            public static void main(String[] args) throws IOException, TimeoutException {
                ConnectionFactory connectionFactory = new ConnectionFactory();
        
                connectionFactory.setPassword("root");
                connectionFactory.setUsername("root");
        
                connectionFactory.setPort(5672);
        
                connectionFactory.setHost("www.limeyu.club");
        
                Connection connection = connectionFactory.newConnection();
                Channel channel = connection.createChannel();
        
                // 声明死信交换器
                channel.exchangeDeclare("exchange_dlx", BuiltinExchangeType.DIRECT, true);
        
                // 声明死信队列
                channel.queueDeclare("queue_dlx", true, false, false, null);
        
                // 通过路由键routingKey_dlx绑定交换器与队列
                channel.queueBind("queue_dlx", "exchange_dlx", "routingKey_dlx");
        
        
                // 声明正常交换器
                channel.exchangeDeclare("exchange_normal", BuiltinExchangeType.FANOUT, true);
        
                // 配置队列属性
                Map<String, Object> arguments_normal = new HashMap<>();
                // 设置死信后发送的交换器
                arguments_normal.put("x-dead-letter-exchange", "exchange_dlx");
                // 设置死信后发送的路由键。如果不指定,则使用原队列的路由键
                arguments_normal.put("x-dead-letter-routing-key", "routingKey_dlx");
        
                // 声明正常队列。设置队列属性
                channel.queueDeclare("queue_normal", true, false, false, arguments_normal);
        
                // 通过路由键routingKey_normal绑定交换器与队列
                channel.queueBind("queue_normal", "exchange_normal", "routingKey_normal");
        
                // 发送消息到MQ
                channel.basicPublish("exchange_normal", "routingKey_normal", MessageProperties.TEXT_PLAIN, "就把今夜留给今夜吧".getBytes());
        
                channel.close();
                connection.close();
            }
        }
        
        
        
        
        // 消费者
        package club.limeyu.rabbitmq.tt.dlx.reject;
        
        import com.rabbitmq.client.Channel;
        import com.rabbitmq.client.Connection;
        import com.rabbitmq.client.ConnectionFactory;
        import com.rabbitmq.client.GetResponse;
        
        import java.io.IOException;
        import java.util.concurrent.TimeUnit;
        import java.util.concurrent.TimeoutException;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/19 下午5:31
         */
        public class RabbitConsumer {
            public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                ConnectionFactory connectionFactory = new ConnectionFactory();
                connectionFactory.setUsername("root");
                connectionFactory.setPassword("root");
                connectionFactory.setHost("www.limeyu.club");
                connectionFactory.setPort(5672);
        
                Connection connection = connectionFactory.newConnection();
                Channel channel = connection.createChannel();
        
                // 从queue_normal队列中拉去消息
                GetResponse queue_normal = channel.basicGet("queue_normal", false);
        
                if (null != queue_normal) {
                    // 拒绝消息。触发MQ将消息转发到死信交换器中
                    channel.basicReject(queue_normal.getEnvelope().getDeliveryTag(), false);
                }
        
                TimeUnit.SECONDS.sleep(1);
        
                channel.close();
                connection.close();
            }
        }
        
        

         

    4. 延迟队列

      1. ttl + dlx

    5. 优先级队列

      1. 队列级别设置优先级

        1. x-max-priority

      2. 消息级别设置优先级

        1. priority()

    6. RPC实现

      1. replyTo:通常用来设置一个回调队列;

      2. correlationId:用来关联请求(request)和其调用RPC之后的回复(response);

      3. 一般在RabbitMQ中进行RPC时很简单的。客户端发送请求消息,服务端回复响应消息。为了接收响应的消息,我们需要在请求消息中发送一个回调队列(replyTo)。可以使用默认的队列。

                String callbackQueueName = channel.queueDeclare() .getQueue();
                AMQP.BasicProperties props = new AMQP.BasicProperties().builder().replyTo(callbackQueueName).build();
                channel.basicPublish("","rpc_queue",props,message.getBytes ());
                // then code to read a response message from the callback_queue...
        

         

      4. 如果像上面的代码中一样,为每个RPC请求创建一个回调队列,则是非常低效的。但是幸运的是这里有一个通用的解决方案——可以为每个客户端创建一个单一的回调队列。

      5. 这样就产生了一个新的问题,对于回调队列而言,在其接收到一条回复的消息之后,它并不知道这条消息应该和哪一个请求匹配。这里就用到correlationId这个属性了,我们应该为每一个请求设置一个唯一的correlationId。之后在回调队列接收到回复的消息时,可以根据这个属性匹配到相应的请求。如果回调队列接收到一条未知correlationId的回复消息,可以简单地将其丢弃。

      6. 你有可能会问,为什么要将回调队列中的未知信息丢弃而不是仅仅将其看作失败?这样可以针对这个失败做一些弥补措施。参考图4-7,考虑这样一种情况,RPC服务器可能在发送给回调队列(amq.gen-LhQzlgv3GhDOv8PIDabOXA)并且在确认接收到请求的消息(rpc_queue中的消息)之后挂掉了,那么只需重启下RPC服务器即可,RPC服务会重新消费rpc_queue队列中的请求,这样就不会出现RPC服务端未处理请求的情况。这里的回调队列可能会收到重复消息的情况,这需要客户端能够优雅地处理这种情况,并且RPC请求也需要保证其本身是幂等的。

        图4-7 RPC示意图
      7. 根据图4-7所示,RPC的处理流程如下:

        1. (1)当客户端启动时,创建一个匿名的回调队列(名称由RabbitMQ自动创建,如图4-7中的回调队列为amq.gen-LhQzlgv3GhDOv8PIDabOXA)。

        2. (2)客户端为RPC请求设置2个属性:replyTo用来告知RPC服务端回复请求时的目的队列,即回调队列;correlationId用来标记一个请求。

        3. (3)请求被发送到rpc_queue队列中。

        4. (4)RPC服务端监听rpc_queue队列中的请求,当请求到来时,服务端会处理并且把带有结果的消息发送给客户端。接收的队列就是replyTo设定的回调队列。

        5. (5)客户端监听回调队列,当有消息时,检查correlationId属性,如果与请求匹配,那就是结果了。

      8. 啦啦啦

        package club.limeyu.rabbitmq.tt.rpc;
        
        import com.rabbitmq.client.*;
        
        import java.io.IOException;
        
        /**
         * @Authorliangmy
         * @Description:
         * @Create:2021/2/19下午6:16
         */
        public class RPCServer {
            private static final String RPC_QUEUE_NAME = "rpc_queue";
        
            public static void main(String args[]) throws Exception {
                ConnectionFactory connectionFactory = new ConnectionFactory();
                connectionFactory.setUsername("root");
                connectionFactory.setPassword("root");
                connectionFactory.setHost("www.limeyu.club");
                connectionFactory.setPort(5672);
        
                Connection connection = connectionFactory.newConnection();
        
                Channel channel = connection.createChannel();
        
        
                channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
        
                channel.basicQos(1);
        
                System.out.println("[x] A waiting RPC requests");
        
                Consumer consumer = new DefaultConsumer(channel) {
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope,
                                               AMQP.BasicProperties properties,
                                               byte[] body) throws IOException {
        
                        AMQP.BasicProperties replyProps = new AMQP.BasicProperties
                                .Builder()
                                .correlationId(properties.getCorrelationId())
                                .build();
                        String response = "";
                        try {
                            String message = new String(body, "UTF-8");
                            int n = Integer.parseInt(message);
                            System.out.println("[.]fib(" + message + ")");
                            response += fib(n);
                        } catch (Exception e) {
                            System.out.println("[.]" + e.toString());
                        } finally {
                            channel.basicPublish("", properties.getReplyTo(),
                                    replyProps, response.getBytes("UTF-8"));
                            channel.basicAck(envelope.getDeliveryTag(), false);
                        }
                    }
                };
        
                channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
            }
        
            private static int fib(int n) {
                if (n < 2) {
                    return n;
                }
                return fib(n - 1) + fib(n - 2);
            }
        }
        
        
        
        
        package club.limeyu.rabbitmq.tt.rpc;
        
        import com.rabbitmq.client.*;
        
        import java.io.IOException;
        import java.util.UUID;
        import java.util.concurrent.TimeoutException;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/19 下午6:27
         */
        public class RPCClient {
            private Connection connection;
            private Channel channel;
            private String requestQueueName = "rpc_queue";
            private String replyQueueName;
            private QueueingConsumer consumer;
        
            private ConnectionFactory connectionFactory = new ConnectionFactory();
        
            {
                connectionFactory.setHost("www.limeyu.club");
                connectionFactory.setPort(5672);
                connectionFactory.setUsername("root");
                connectionFactory.setPassword("root");
                try {
                    connection = connectionFactory.newConnection();
                    channel = connection.createChannel();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
        
            }
        
        
            public RPCClient() throws IOException, TimeoutException {
                replyQueueName = channel.queueDeclare().getQueue();
                consumer = new QueueingConsumer(channel);
                channel.basicConsume(replyQueueName, true, consumer);
            }
        
            public String call(String message) throws
                    IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
                String response = null;
                String corrId = UUID.randomUUID().toString();
                AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
                        .correlationId(corrId)
                        .replyTo(replyQueueName)
                        .build();
                channel.basicPublish("", requestQueueName, props, message.getBytes());
                while (true) {
                    QueueingConsumer.Delivery delivery = consumer.nextDelivery();
                    if (delivery.getProperties().getCorrelationId().equals(corrId)) {
                        response = new String(delivery.getBody());
                        break;
                    }
                }
                return response;
            }
        
            public void close() throws Exception {
                connection.close();
            }
        
            public static void main(String args[]) throws Exception {
                RPCClient fibRpc = new RPCClient();
                System.out.println("[x] Requesting fib (30)");
                String response = fibRpc.call("2");
                System.out.println("[.] Got '" + response + "'");
                fibRpc.close();
            }
        }
        

         

    7. 持久化

      1. 交换器持久化

      2. 队列持久化

      3. 消息持久化

    8. 生产者确认(通过事务机制实现、通过发送方确认(publisher confirm)机制实现)

      1. 事务机制(降低消息吞吐量)

        1. channel.txSelect:将当前的信道设置成事务模式

        2. channel.txCommit:提交事务

        3. channel.txRolback:事务回滚

          package club.limeyu.rabbitmq.tt.tx;
          
          import com.rabbitmq.client.*;
          import lombok.extern.slf4j.Slf4j;
          
          import java.io.IOException;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/20 上午10:57
           */
          @Slf4j
          public class RabbitProducer {
              public static void main(String[] args) throws IOException, TimeoutException {
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setHost("www.limeyu.club");
                  connectionFactory.setPort(5672);
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  Connection connection = connectionFactory.newConnection();
          
                  Channel channel = connection.createChannel();
          
                  channel.exchangeDeclare("exchange_tx", BuiltinExchangeType.DIRECT, true);
          
                  channel.queueDeclare("queue_tx", true, false, false, null);
          
                  channel.queueBind("queue_tx", "exchange_tx", "routingKey_tx");
          
                  log.info("channel.txSelect() :: 设置信道为事务模式");
                  channel.txSelect();
                  try {
                      channel.basicPublish("exchange_tx", "routingKey_tx", MessageProperties.TEXT_PLAIN, "this message on tx mode".getBytes());
                      if (true) {
                          throw new RuntimeException("模拟发送消息发生异常");
                      }
                      channel.txCommit();
                      log.info("channel.txCommit() :: 消息发送成功,提交事务");
                  } catch (Exception e) {
                      channel.txRollback();
                      log.info("channel.txRollback() :: 消息发送异常,回滚事务");
                  }
                  channel.close();
                  connection.close();
              }
          }
          

           

      2. 发送方确认机制

        1. 生产者将信道设置成confirm(确认)模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID),这就使得生产者知晓消息已经正确到达目的地了。如果消息和队列是可持久化掉的,那么确认消息会在消息写入磁盘之后发出。RabbitMQ回传给生产者的确认消息中的deliveryTag包含了确认消息的序号,此外RabbitMQ也可以设置channel.basicAck方法中的multiple参数,表示到这个序号之前的所有消息都已经得到了处理。

        2. channel.confirmSelect

        3. channel.waitForConfirms

        4. channel.waitForConfirmsOrDie

        5. 批量confirm方法:每发送一批消息后,调用channel.waitForConfirms方法,等待服务器的确认返回。

          1. 定期或定量,亦或者两者结合起来调用channel.waitForConfirms来等待RabbitMQ的确认返回。

          2. 出现返回Basic.Nack或者超时情况时,客户端需要将这一批次的消息全部重发,这会带来明显的重复消息数量,并且当消息经常丢失时,批量confirm的性能应该是不升反降的。

          3. DEMO

            package club.limeyu.rabbitmq.tt.confirm;
            
            import com.rabbitmq.client.*;
            import lombok.extern.slf4j.Slf4j;
            
            import java.io.IOException;
            import java.util.concurrent.TimeoutException;
            
            /**
             * @Author liangmy
             * @Description:
             * @Create: 2021/2/20 下午1:33
             */
            @Slf4j
            public class RabbitProducer {
                public static void main(String[] args) throws IOException, TimeoutException {
                    ConnectionFactory connectionFactory = new ConnectionFactory();
                    connectionFactory.setHost("www.limeyu.club");
                    connectionFactory.setPort(5672);
                    connectionFactory.setUsername("root");
                    connectionFactory.setPassword("root");
            
                    Connection connection = connectionFactory.newConnection();
            
                    Channel channel = connection.createChannel();
            
                    channel.exchangeDeclare("exchange_confirm", BuiltinExchangeType.DIRECT, true);
            
                    channel.queueDeclare("queue_confirm", true, false, false, null);
            
                    channel.queueBind("queue_confirm", "exchange_confirm", "rk_confirm");
            
            
                    // 设置channel为发送方确认模式
                    channel.confirmSelect();
            
                    int msgCount = 0;
                    final int BATCH_COUNT = 100;
                    try {
                        while (true) {
                            channel.basicPublish("exchange_confirm", "rk_confirm", MessageProperties.TEXT_PLAIN, "this message on confirm mode".getBytes());
                            log.info("将发送出去的消息存入缓存中,缓存可以是一个ArrayList或者BlockingQueue之类的");
                            if (msgCount >= BATCH_COUNT) {
                                msgCount = 0;
                                try {
                                    if (!channel.waitForConfirms()) {
                                        log.info("将缓存中的消息清空");
                                    }
                                    log.info("将缓存中的消息重新发送");
                                } catch (InterruptedException e) {
                                    log.info("将缓存中的消息重新发送");
                                }
                            }
                        }
                    } finally {
                        channel.close();
                        connection.close();
                    }
                }
            }
            

             

        6. 异步confirm方法:提供一个回调方法,服务端确认了一条或多条消息后客户端会回调这个方法进行处理。

          1. 在客户端Channel接口中提供的addConfirmListener方法可以添加ConfirmListener这个回调接口,这个ConfirmListener接口包含两个方法:handleAck和handleNack,分别用来处理RabbitMQ回传的Basic.Ack和Basic.Nack。在这两个方法中都包含有一个参数deliveryTag(在publisher confirm模式下用来标记消息的唯一有序序号)。我们需要为每一个信道维护一个“unconfirm”的消息序号集合,每发送一条消息,集合中的元素加1。每当调用ConfirmListener中的handleAck方法时,“unconfirm”集合中删掉相应的一条(multiple设置为false)或者多条(multiple设置为true)记录。从程序运行效率上来看,这个“unconfirm”集合最好采用有序集合SortedSet的存储结构。事实上,Java客户端SDK中的waitForConfirms方法也是通过SortedSet维护消息序号的。

          2. DEMO

            package club.limeyu.rabbitmq.tt.confirm.sync;
            
            import com.rabbitmq.client.*;
            import lombok.extern.slf4j.Slf4j;
            
            import java.io.IOException;
            import java.util.HashMap;
            import java.util.Map;
            import java.util.SortedSet;
            import java.util.TreeSet;
            import java.util.concurrent.TimeUnit;
            import java.util.concurrent.TimeoutException;
            
            /**
             * @Author liangmy
             * @Description:
             * @Create: 2021/2/20 下午2:42
             */
            @Slf4j
            public class RabbitProducer {
                public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                    ConnectionFactory connectionFactory = new ConnectionFactory();
            
                    connectionFactory.setHost("www.limeyu.club");
                    connectionFactory.setPort(5672);
                    connectionFactory.setUsername("root");
                    connectionFactory.setPassword("root");
            
                    Connection connection = connectionFactory.newConnection();
                    Channel channel = connection.createChannel();
            
                    channel.exchangeDeclare("exchange_confirm_sync", BuiltinExchangeType.DIRECT, true);
            
                    channel.queueDeclare("queue_confirm_sync", true, false, false, null);
            
                    channel.queueBind("queue_confirm_sync", "exchange_confirm_sync", "rk_confirm_sync");
            
                    TreeSet<Long> confirmSet = new TreeSet<>();
            
                    channel.addConfirmListener(new ConfirmListener() {
                        @Override
                        public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                            log.info("Ack, SeqNO : {}, multiple : {}", deliveryTag, multiple);
                            if (multiple) {
                                confirmSet.headSet(deliveryTag, true).clear();
                            } else {
                                confirmSet.remove(deliveryTag);
                            }
                        }
            
                        @Override
                        public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                            log.info("dddddNack, SeqNO : {}, multiple : {}", deliveryTag, multiple);
                            if (multiple) {
                                confirmSet.headSet(deliveryTag).clear();
                            } else {
                                confirmSet.remove(deliveryTag);
                            }
                            log.info("注意这里需要添加处理消息重发的场景");
                        }
                    });
            
                    channel.confirmSelect();
            
                    for (int i = 0; i < 100; i++) {
                        long nextPublishSeqNo = channel.getNextPublishSeqNo();
                        channel.basicPublish("exchange_confirm_sync", "rk_confirm_sync", MessageProperties.PERSISTENT_TEXT_PLAIN, ("this message constom id is : " + i).getBytes());
                        confirmSet.add(nextPublishSeqNo);
                    }
            
                    TimeUnit.SECONDS.sleep(5);
            
                    for (int i = 0; i < 100; i++) {
                        long nextPublishSeqNo = channel.getNextPublishSeqNo();
                        channel.basicPublish("exchange_confirm_sync", "rk_confirm_sync", MessageProperties.PERSISTENT_TEXT_PLAIN, ("this message constom id is : " + i).getBytes());
                        confirmSet.add(nextPublishSeqNo);
                    }
            
            
                    TimeUnit.SECONDS.sleep(5);
            
                    channel.close();
                    connection.close();
            
                }
            }
            

             

          3. 奇怪:队列自动删除之后,依旧回复的是Ack。如何模拟Nack场景???

    9. 消费端要点介绍

      1. channel.basicAck

      2. channel.basicNack

      3. channel.basicReject

      4. 消息分发;

      5. 消息顺序性;

      6. 弃用QueueingConsumer

      7. 消息分发

        1. Channel.void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException

          1. prefetchSize:

          2. prefetchCount:设置客户端最多接收未被ack的消息个数。设置为0则表示没有上限

          3. global:消费者所能接收未确认消息的总体大小的上限,单位为B,设置为0则表示没有上限。

        2. 对于一个信道来说,它可以同时消费多个队列,当设置了prefetchCount大于0时,这个信道需要和各个队列协调以确保发送的消息都没有超过所限定的prefetchCount的值,这样会使RabbitMQ的性能降低,尤其是这些队列分散在集群中的多个Broker节点之中。

        3. RabbitMQ为了提升相关性能,在AMQP 0-9-1协议之上重新定义了global这个参数

          表 4-1 global参数的对比

          global参数

          AMQP 0-9-1RabbitMQ

          false

          信道上所有的消费者都需要遵从prefetchCount的限定值信道上新的消费者需要遵从prefetchCount的限定值

          true

          当前通信链路(Connection)上所有的消费者都需要遵从prefetchCount的限定值信道上所有的消费者都需要遵从prefetchCount的限定值

           

        4. RabbitMQ会保存一个消费者的列表,每发送一条消息都会为对应的消费者计数,如果达到了所设置的上限,那么RabbitMQ就不会向这个消费者再发送任何消息。直到消费者确认了某条消息之后,RabbitMQ将相应的计数减1,之后消费者可以继续接收消息,直到再次到达计数上限。

        5. DEMO(prefetchCount)

          package club.limeyu.rabbitmq.tt.qos;
          
          import com.rabbitmq.client.*;
          
          import java.io.IOException;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/20 下午5:35
           */
          public class RabbitProducer {
              public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setHost("www.limeyu.club");
                  connectionFactory.setPort(5672);
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  Connection connection = connectionFactory.newConnection();
                  Channel channel = connection.createChannel();
          
                  channel.exchangeDeclare("exchange_qos", BuiltinExchangeType.DIRECT, true);
          
                  channel.queueDeclare("queue_qos", true, false, false, null);
          
                  channel.queueBind("queue_qos", "exchange_qos", "rk_qos");
          
                  // 发送100条消息到队列
                  for (int i = 0; i < 100; i++) {
                      channel.basicPublish("exchange_qos", "rk_qos", MessageProperties.PERSISTENT_TEXT_PLAIN, ("this message no : " + i).getBytes());
                  }
          
                  TimeUnit.SECONDS.sleep(1);
          
                  channel.close();
                  connection.close();
              }
          }
          
          
          
          
          package club.limeyu.rabbitmq.tt.qos;
          
          import com.rabbitmq.client.*;
          import lombok.extern.slf4j.Slf4j;
          
          import java.io.IOException;
          import java.util.ArrayDeque;
          import java.util.PriorityQueue;
          import java.util.Queue;
          import java.util.TreeSet;
          import java.util.concurrent.PriorityBlockingQueue;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/20 下午6:21
           */
          @Slf4j
          public class RabbitConsumer {
              public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setHost("www.limeyu.club");
                  connectionFactory.setPort(5672);
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  Connection connection = connectionFactory.newConnection();
                  Channel channel = connection.createChannel();
          
                  // 设置客户端最多接收未被ack的消息个数
                  channel.basicQos(5);
          
                  Queue<Long> ackQueue = new PriorityQueue<>();
          
                  DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                      @Override
                      public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                          log.info("msg rev : {}", new String(body));
                          // 保存已接收消息的唯一标识
                          ackQueue.add(envelope.getDeliveryTag());
                      }
                  };
          
                  channel.basicConsume("queue_qos", false, defaultConsumer);
          
                  TimeUnit.SECONDS.sleep(2);
                  while (!ackQueue.isEmpty()) {
                      // 消费端确认消息已处理
                      channel.basicAck(ackQueue.remove(), false);
                      TimeUnit.SECONDS.sleep(1);
                  }
          
                  TimeUnit.SECONDS.sleep(2);
                  channel.close();
                  connection.close();
          
              }
          }
          
          

           

        6. 如果在订阅消息之前,即设置了global为true的限制,又设置了global为false的限制,那么那个会生效呢?RabbitMQ会确保两者都会生效。举例说明,当前有两个队列queue1和queue2:queue1有10条消息,分别为1到10;queue2也有10条消息,分别是11到20。有两个消费者分别消费这两个队列。那么这里每个消费者最多只能收到3个未确认的消息,两个消费者能收到的未确认的消息个数之和的上限为5。在未确认消息的情况下,如果consumer1收到了消息1、2和3,那么consumer2至多只能收到11和12.

        7. DEMO(global)

          package club.limeyu.rabbitmq.tt.qos.global;
          
          import com.rabbitmq.client.*;
          
          import java.io.IOException;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/20 下午7:23
           */
          public class RabbitProducer {
              public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setHost("www.limeyu.club");
                  connectionFactory.setPort(5672);
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  Connection connection = connectionFactory.newConnection();
                  Channel channel = connection.createChannel();
          
                  channel.exchangeDeclare("exchange_qos_global", BuiltinExchangeType.FANOUT, true);
          
                  channel.queueDeclare("queue_qos_global_1", true, false, false, null);
          
                  channel.queueDeclare("queue_qos_global_2", true, false, false, null);
          
                  channel.queueBind("queue_qos_global_1", "exchange_qos_global", "rk_qos_global_1");
          
                  channel.queueBind("queue_qos_global_2", "exchange_qos_global", "rk_qos_global_2");
          
                  // 发送10条消息到扇形交换器
                  for (int i = 0; i < 10; i++) {
                      channel.basicPublish("exchange_qos_global", "", MessageProperties.PERSISTENT_TEXT_PLAIN, ("this message no : " + i).getBytes());
                  }
          
                  TimeUnit.SECONDS.sleep(1);
          
                  channel.close();
                  connection.close();
              }
          }
          
          
          
          
          package club.limeyu.rabbitmq.tt.qos.global;
          
          import com.rabbitmq.client.*;
          import lombok.extern.slf4j.Slf4j;
          
          import java.io.IOException;
          import java.util.PriorityQueue;
          import java.util.Queue;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/20 下午7:27
           */
          @Slf4j
          public class RabbitConsumer {
              public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setHost("www.limeyu.club");
                  connectionFactory.setPort(5672);
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  Connection connection = connectionFactory.newConnection();
                  Channel channel = connection.createChannel();
          
                  // 每个消费者最多只能收到3个未确认的消息
                  channel.basicQos(3, false);
                  // 两个消费者能收到的未确认的消息个数之和的上限为5
                  channel.basicQos(5, true);
                  Queue<Long> ackQueue = new PriorityQueue<>();
                  DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                      @Override
                      public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                          log.info("msg rev : {}, consumerTag : {}", new String(body), consumerTag);
                          ackQueue.add(envelope.getDeliveryTag());
                      }
                  };
          
                  channel.basicConsume("queue_qos_global_1", false, defaultConsumer);
                  channel.basicConsume("queue_qos_global_2", false, defaultConsumer);
          
                  TimeUnit.SECONDS.sleep(2);
                  while (!ackQueue.isEmpty()) {
                      // 消费端确认消息已处理
                      channel.basicAck(ackQueue.remove(), false);
                      TimeUnit.SECONDS.sleep(1);
                  }
          
                  TimeUnit.SECONDS.sleep(2);
                  channel.close();
                  connection.close();
          
          
              }
          }
          
          

           

      8. 消息顺序性

        1. 消息的顺序性是指消费者消费到的消息和发送者发布的消息的顺序是一致的。

      9. 弃用QueueingConsumer

        1. 默认使用LinkedBlockingQueue缓存消息,造成内存溢出。

        2. QueueingConsumer会拖累一个Connection下的所有信道,使其性能降低

        3. 同步递归调用QueueingConsumer会产生死锁

        4. RabbitMQ的自动连接恢复机制(automatic connection recovery)不支持QueueingConsumer的这种形式

        5. QueueingConsumer不是事件驱动的。

      10. 消息传输保障

        1. At most once :最多一次。消息可能会丢失,但绝对不会重复传输。

        2. At least once:最少一次。消息绝不会丢失,但可能会重复传输。

        3. Exactly once:恰好一次。每条消息肯定会被传输一次且仅传输一次。

        4. “最少一次”投递实现需要考虑:

          1. 消息生产者需要开始事务机制或publisher confirm机制,以确保消息可以可靠地传输到RabbitMQ中。

          2. 消息生产者需要配合使用mandatory参数或者备份交换器来确保消息能够从交换器路由到队列中,进而能够保存下来而不会被丢弃。

          3. 消息和队列都需要进行持久化处理,以确保RabbitMQ服务器在遇到异常情况时不会造成消息丢失。

          4. 消费者在消费信息的同时需要将autoAck设置为false,然后通过手动确认的方式去确认已经正确消费的消息,以避免在消费端引起不必要的消息丢失。

  5. RabbitMQ管理

    1. 多租户与权限

      1. 虚拟主机(virtual host):每一个RabbitMQ服务器都能创建虚拟的消息服务器,简称为vhost。每一个vhost本质上是一个独立的小型RabbitMQ服务器,拥有自己独立的队列、交换器及绑定关系等,并且它拥有自己独立的权限。

      2. vhost时AMQP概念的基础,客户端在连接的时候必须制定一个vhost。RabbitMQ默认创建的vhost为“/”,使用默认的用户名guest和密码guest就可以访问。

      3. 创建vhost:rabbitmqctl add_vhost {vhost}

        [root@Lime-CentOS ~]# rabbitmqctl add_vhost vhost1
        Creating vhost "vhost1" ...
        ...done.
        

         

      4. 罗列当前vhost的相关信息:rabbitmqctl list_vhosts [vhostinfoitem...]

        1. name:表示vhost的名称。

        2. tracing:表示是否使用了RabbitMQ的trace功能。

          [root@Lime-CentOS ~]# rabbitmqctl list_vhosts name tracing
          Listing vhosts ...
          /	false
          vhost1	false
          ...done.
          

           

      5. 删除vhost:rabbitmqctl delete_vhost {vhost}

        [root@Lime-CentOS ~]# rabbitmqctl delete_vhost vhost1
        Deleting vhost "vhost1" ...
        ...done.

         

      6. 在RabbitMQ中,权限控制则是以vhost为单位的。当创建一个用户时,用户通常会被指派给至少一个vhost,并且只能访问被指派的vhost内的队列、交换器和绑定关系等。

      7. 授予权限:rabbitmqctl set_permissions [-p vhost] {user} {conf} {write} {read}

        1. vhost:授予用户访问权限的vhost名称,可以设置为默认值,即vhost为“/”。

        2. user:可以访问制定vhost的用户名

        3. conf:一个用于匹配用户在哪些资源上拥有可配置(队列和交换器的创建及删除之类的操作)权限的正则表达式。

        4. write:一个用于匹配用户在哪些资源上拥有可写(发布消息)权限的正则表达式。

        5. read:一个用于匹配用户在哪些资源上拥有可读(与消息有关的操作,包括读取消息及清空整个队列等)权限的正则表达式。

      8. 清除权限:rabbitmqctl clear_permissions [-p vhost] {username}

      9. 列举权限信息:

        1. 显示虚拟主机上的权限:rabbitmqctl list_permissions [-p vhost]

        2. 显示用户的权限:rabbitmqctl list_user_permissions {username}

      10. 标准语法:rabbitmqctl [-n node] [-t timeout] [-q] {command} [command options...]

        1. [-n node] : 默认节点是“rabbit@hostname”

        2. [-q] :使用-q标志来启用quiet模式,这样可以屏蔽一下消息的输出。默认不开启quiet模式。

        3. [-t timeout]:操作超时时间(秒为单位),只适用于“list_xxx”类型的命令,默认是无穷大。

    2. 用户管理

      1. 创建用户:rabbitmqctl add_user {username} {password}

      2. 修改密码:rabbitmqctl change_password {username} {newpassword}

      3. 清除密码:rabbitmqctl clear_password {username}

      4. 通过密码验证用户:rabbitmqctl authenticate_user {username} {password}

      5. 删除用户:rabbitmqctl delete_user {username}

      6. 罗列当前的所有用户(每个结果行都包含用户名称,,其后紧跟用户的角色(tags)):rabbitmqctl list_users

        1. 用户的角色分为5种类型

          1. none:

          2. management:

          3. policymaker:

          4. monitoring:

          5. administartor:

      7. 设置用户的角色:rabbitmqctl set_user_tags {username} {tag ...}

    3. Web端管理

      1. rabbitmq-plugins [-n node] {command} [command options...]

      2. 启用插件:rabbitmq-plugins enable [plugin-name]

      3. 关闭插件:rabbitmq-plugins disable [plugin-name]

      4. 查看当前插件的使用情况:rabbitmq-plugins list

        1. [E*]为显式启动,[e*]为隐式启动

    4. 应用与集群管理

      1. 应用管理

        1. rabbitmqctl stop [pid_file]

        2. rabbitmqctl shutdown

        3. rabbitmqctl stop_app

          1. 停止RabbitMQ服务应用,但是Erlang虚拟机还是处于运行状态。此命令的执行优先于其他管理操作(这些管理操作需要先停止RabbitMQ应用),比如rabbitmqctl reset。

        4. rabbitmqctl start_app

        5. rabbitmqctl wait [pid_file]

        6. rabbitmqctl reset

          1. 将RabbitMQ节点重置还原到最初状态。包括从原来所在的集群中删除此节点,从管理数据库中删除所有的配置数据,如已配置的用户、vhost等,以及删除所有的持久化消息。执行rabbitmqctl reset命令前必须停止RabbitMQ应用(比如先执行rabbitmqctl stop_app)。

        7. rabbitmqctl force_reset

          1. 强制将RabbitMQ节点重置还原到最初状态。不同于rabbitmqctl reset命令,rabbitmqctl force_reset命令不论当前管理数据库的状态和集群配置是什么,都会无条件地重置节点。它只能在数据库或集群配置已损坏的情况下使用。执行命令前必须先停止RabbitMQ应用。

        8. rabbitmqctl rotate_logs {suffix}:表示将日志文件的内容追加到新的日志文件中去,这个新的日志文件的文件名是原有的日志文件名加上命令中的 suffix,并且恢复日志到原来位置的新文件中。注意:如果新文件原先不存在,那么会新建一个;如果suffix为空,那么不会发生日志转移,只是重新打开了一次日志文件而已。

        9. rabbitmqctl hipe_compile {directory}

      2. 集群管理

        1. rabbitmqctl join_cluster {cluster_node} [--ram]

          1. 将节点加入制定集群中。在这个命令执行前需要停止RabbitMQ应用并重置节点。

        2. rabbitmqctl cluster_status

        3. rabbitmqctl change_cluster_node_type {disc|ram}

        4. rabbitmqctl forget_cluster_node [--offline]

          1. 将节点从集群中删除,运行离线执行。

        5. rabbitmqctl update_cluster_nodes {clusternode}

        6. rabbitmqctl force_boot

        7. rabbitmqctl sync_queue [-p vhost] {queue}

        8. rabbitmqctl cancel_sync_queue [-p vhost] {queue}

        9. rabbitmqctl set_cluster_name {name} :设置集群名称

    5. 服务端状态

      1. rabbitmqctl list_queues [-p vhost] [queueinfoitem ...]

      2. rabbitmqctl list_exchanges [-p vhost] [exchangeinfoitem ...]

      3. rabbitmqctl list_bindings [-p vhost] [bindinginfoitem ...]

      4. rabbitmqctl list_connections [connectioninfoitem ...]

      5. rabbitmqctl list_channels [channelinfoitem ...]

      6. rabbitmqctl list_consumers [-p vhost]

      7. rabbitmqctl status

      8. rabbitmqctl node_health_check

      9. rabbitmqctl environment

      10. rabbitmqctl report

      11. rabbitmqctl eval {expr}

      12. eval的扩展

    6. HTTP API 接口管理

    7. 啦啦啦

  6. RabbitMQ配置(环境变量、配置文件、运行时参数和策略)

    1. 环境变量

      1. RabbitMQ默认的安装目录:/usr/lib/rabbitmq/lib/rabbitmq_server-3.3.5/sbin

      2. RabbitMQ默认配置文件路径:/usr/lib/rabbitmq/lib/rabbitmq_server-3.3.5/sbin/rabbitmq-defaults

      3. RabbitMQ环境变量文件路径(默认不存在,需要手动创建):CONF_ENV_FILE=${SYS_PREFIX}/etc/rabbitmq/rabbitmq-env.conf

      4. RABBITMQ_NODE_IP_ADDRESS

      5. RABBITMQ_NODE_PORT:供客户端建立连接端口

      6. RABBITMQ_DIST_PORT

      7. RABBITMQ_NODENAME:配置RabbitMQ节点名称

      8. RABBITMQ_CONF_ENV_FILE:RabbitMQ环境变量的配置文件(rabbitmq-env.conf)的地址,默认值为$RABBITMQ_HOME/etc/rabbitmq/rabbitmq-env.conf

      9. RABBITMQ_USE_LONGNAME

      10. RABBITMQ_CONFIG_FILE:RabbitMQ配置文件(rabbitmq.confg)的路径,注意没有“.config”的后缀。默认值为$RABBITMQ_HOME/etc/rabbitmq/rabbitmq

      11. RABBITMQ_MNESIA_BASE:RABBITMQ_MNESIA_DIR的父目录。除非明确设置了RABBITMQ_MNESIA_DIR目录,否则每个节点都应该配置这个环境变量。默认值为$RABBITMQ_HOME/var/lib/rabbitmq/mnesia。注意对于RabbitMQ的操作用户来说,需要有对当前目录可读、可写、可创建文件及子目录的权限。

      12. RABBITMQ_MNESIA_DIR:包含RabbitMQ服务节点的数据库、数据存储及集群状态等目录,默认值为$RABBITMQ_MNESIA_BASE/$RABBITMQ_NODENAME

      13. RABBITMQ_LOG_BASE

      14. RABBITMQ_LOGS

      15. RABBITMQ_SASL_LOGS

      16. PLUGINS_DIR

      17. RABBITMQ_SERVER_ERL_ARGS:在调用RabbitMQ服务器时使用的erl命令的标准参数, 应该仅用于调试目的

      18. RABBITMQ_SERVER_START_ARGS:在调用RabbitMQ服务器时使用的erl命令的额外参数。这不会覆盖RABBITMQ_SERVER_ERL_ARGS.

    2. 配置文件

      1. RabbitMQ服务启动时,服务日志打印“config files(s)”为目前的配置文件所在的路径。

        [root@Lime-CentOS ~]# rabbitmq-server 
        
                      RabbitMQ 3.3.5. Copyright (C) 2007-2014 GoPivotal, Inc.
          ##  ##      Licensed under the MPL.  See http://www.rabbitmq.com/
          ##  ##
          ##########  Logs: /var/log/rabbitmq/rabbit@Lime-CentOS.log
          ######  ##        /var/log/rabbitmq/rabbit@Lime-CentOS-sasl.log
          ##########
                      Starting broker... completed with 6 plugins.
        
        
        
        
        
        
        [root@Lime-CentOS ~]# tail -f -n 100 /var/log/rabbitmq/rabbit@Lime-CentOS.log
        
        
        
        
        
        
        =INFO REPORT==== 23-Feb-2021::10:25:30 ===
        Starting RabbitMQ 3.3.5 on Erlang R16B03-1
        Copyright (C) 2007-2014 GoPivotal, Inc.
        Licensed under the MPL.  See http://www.rabbitmq.com/
        
        =INFO REPORT==== 23-Feb-2021::10:25:30 ===
        node           : rabbit@Lime-CentOS
        home dir       : /var/lib/rabbitmq
        config file(s) : /etc/rabbitmq/rabbitmq.config
        cookie hash    : WvUjrKLL8ksbutFi7KlPCA==
        log            : /var/log/rabbitmq/rabbit@Lime-CentOS.log
        sasl log       : /var/log/rabbitmq/rabbit@Lime-CentOS-sasl.log
        database dir   : /var/lib/rabbitmq/mnesia/rabbit@Lime-CentOS
        
        

         

        1. 配置项

          1. tcp_listeners

          2. num_tcp_acceptors

        2. 配置加密

        3. 优化网络配置

      2. 参数及策略

        1. 参数

          1. vhost级别的Parameter

            1. rabbitmqctl set_parameter [-p vhost] {component_name} {name} {value}

            2. rabbitmqctl list_parameters [-p vhost]

            3. rabbitmqctl clear_parameter [-p vhost] {component_name} {key}

          2. global级别的Parameter

            1. rabbitmqctl set_global_parameter name value

            2. rabbitmqctl list_global_parameters

            3. rabbitmqctl clear_global_parameter name

        2. 策略(特殊的Parameter用法,vhost级别)

          1. 一个Policy可以匹配一个或多个队列(或者交换器,或者两者兼有)

          2. Policy也可以支持动态地修改一些属性参数。

          3. rabbitmqctl set_policy [-p vhost] [--priority priority] [--apply-to apply-to] {name} {pattern} {definition}

            1. Virtual host

            2. Name

            3. Pattern

            4. Apply to

            5. Priority:定义优先级。如果有多个Policy作用于同一个交换器或者队列,那么Priority最大的那个Policy才会有用。

            6. Definition

          4. rabbitmqctl list_policies [-p vhost]

          5. rabbitmqctl clear_policy [-p vhost] {name}

          6. 如果多个Policy都作用到同一个交换器或者队列上,且这些Policy的优先级都是一样的,则参数项最多的Policy具有决定权。如果参数一样多,则最后添加的Policy具有决定权。

  7. RabbitMQ运维

    1. 集群搭建

      1. 元数据信息

        1. 队列元数据:队列的名称及属性

        2. 交换器:交换器的名称及属性

        3. 绑定关系元数据:交换器与队列或者交换器与交换器之间的绑定关系

        4. vhost元数据:为vhost内的队列、交换器和绑定提供命名空间及安全属性

      2. 在RabbitMQ集群中创建队列,集群只会在单个节点而不是在所有节点上创建队列的进程并包含完整的队列信息(元数据、状态、内容)。这样只有队列的宿主节点,即所有者节点知道队列的所有信息,所有其他非所有者节点只知道队列的元数据和指向该队列存在的那个节点的指针。因此当集群节点崩溃时,该节点的队列进程和关联的绑定都会消失。附加在那些队列上的消费者也会丢失其所订阅的信息,并且任何匹配该队列绑定信息的新消息也都会消失。

      3. 不同于队列那样拥有自己的进程,交换器其实只是一个名称和绑定列表。当消息发布到交换器时,实际上是由所连接的信道将消息上的路由键同交换器的绑定列表进行比较,然后再路由消息。当创建一个新的交换器时,RabbitMQ所要做的就是将绑定列表添加到集群中的左右节点上。这样,每个节点上的每条信道都可以访问新的交换器了。

      4. 多机多节点配置

        1. 第一步,配置各个节点的hosts文件

          vim /etc/hosts
          
          192.168.0.2 node1
          192.168.0.3 node2
          192.168.0.4 node3

           

        2. 第二步,编辑RabbitMQ的cookie文件,以确保各个节点的cookie文件使用的是同一个值。cookie相当于密钥令牌,集群中的RabbitMQ节点需要通过交换密钥令牌以获取相互认证。

          vim /var/lib/rabbitmq/.erlang.cookie

           

        3. 第三步,配置集群。配置集群有三种方式:通过rabbitmqctl工具配置;通过rabbitmq.config配置文件配置;通过rabbitmq-autocluster插件配置

          # 启动RabbitMQ服务
          rabbitmq-server -detached
          
          # 查看各节点RabbitMQ服务集群信息
          rabbitmqctl cluster_status
          
          # 停止node2、node3节点RabbitMQ服务
          rabbitmqctl stop_app
          
          # 重置node2、node3节点RabbitMQ服务
          rabbitmqctl reset
          
          # node2、node3节点加入node1节点的RabbitMQ服务
          rabbitmqctl join_cluster rabbit@node1
          
          # 开启node2、node3节点RabbitMQ服务
          rabbitmqctl start_app
          
          # 如果最后一个关闭的节点最终由于某些异常而无法启动,将当前节点剔出当前集群
          rabbitmqctl forget_cluster_node
          
          # 如果集群中的所有节点由于某些非正常因素,比如断电而关闭,那么集群中的节点都会认为还有其他节点在它后面关闭,此时需要强制启动一个节点,之后集群才能正常启动。
          rabbitmqctl force_boot

           

      5. 集群节点类型

        1. Disc:磁盘类型(默认)

        2. ram:内存类型(--ram)

          1. 将node2节点加入node1节点的时候指定node2节点的类型为内存节点

            rabbitmqctl join_cluster rabbit@node1 --ram

             

        3. 切换节点类型:rabbitmqctl change_cluster_node_type {disc,ram}

          rabbitmqctl stop_app
          
          rabbitmqctl change_cluster_node_type disc
          
          rabbitmqctl start_app
          
          rabbitmqctl cluster_status

           

      6. 剔除单个节点

        1. node2节点不再运行RabbitMQ的情况。node1或node3节点运行状态

          # perform operation on rabbit@node2
          # 关闭node2应用或节点
          rabbitmqctl stop_app
          rabbitmqctl stop
          
          
          # perform operation on rabbit@node1
          # 在node1或node3节点剔除node2节点
          rabbitmqctl forget_cluster_node rabbit@node2

           

        2. node1或node3节点未启动状态下,剔除node2节点

          rabbitmqctl forget_cluster_node rabbit@node1 -offline

           

        3. 在node2上执行退出集群

          # perform operation on rabbit@node2
          # rabbit@node2节点恢复为独立节点
          # node1或node3节点查看集群已没有node2节点信息
          
          rabbitmqctl stop_app
          rabbitmqctl reset
          rabbitmqctl start_app
          

           

      7. 集群节点的升级

        1. 确保元节点的Mnesia中的数据不被变更。(Mnesia数据库是Erlang内置的一个DBMS,可以直接存储Erlang的各种数据结构。)

          1. (1)关闭所有节点的服务,注意采用rabbitmqctl stop命令关闭

          2. (2)保存各个节点的Mnesia数据

          3. (3)解压新版本的RabbitMQ到指定的目录

          4. (4)指定新版本的Mnesia路径为步骤2中保存的Mnesia数据路径。(RABBITMQ_MNESIA_BASE=/opt/mnesia)

          5. (5)启动新版本的服务,注意先重启原版本中最后关闭的那个节点。

        2. 配置和数据不可丢弃

          1. (1)保存元数据

          2. (2)关闭所有生产者

          3. (3)等待消费者消费完队列中的所有数据

          4. (4)关闭所有消费者

          5. (5)重新安装RabbitMQ

          6. (6)重建元数据

        3. 集群迁移

      8. 单机多节点配置(在一台机器上部署多个RabbitMQ服务节点,需要确保每个节点都有独立的名称、数据存储位置、端口号(包括插件的端口号)等。)

        1. 安装Erlang

          curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash
          
          yum -y -install erlang

           

        2. 安装RabbitMQ

          curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash
          
          yum -y install rabbit-server

           

        3. 为每个RabbitMQ服务节点设置不同的端口号和节点名称来启动相应的服务。

          RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit1 rabbitmq-server -detached
          
          RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit2 rabbitmq-server -detached
          
          RABBITMQ_NODE_PORT=5674 RABBITMQ_NODENAME=rabbit3 rabbitmq-server -detached
          

           

        4. 在启动rabbit1@node1节点的服务之后,继续启动rabbit2@node1 和 rabbit@node1 服务节点会遇到失败的情况。这种情况大多数是由于配置发生了冲突而造成后面的节点启动失败,需要进一步确认是否开启了某些功能,比如RabbitMQ Management插件。如果开启了RabbitMQ Management插件,就需要为每个服务节点配置一个对应的插件端口号

          RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit1 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15672}]" rabbitmq-server -detached
          
          RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit2 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" rabbitmq-server -detached
          
          RABBITMQ_NODE_PORT=5674 RABBITMQ_NODENAME=rabbit3 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15674}]" rabbitmq-server -detached
          

           

        5. 将rabbit2@node1节点加入rabbit1@node1的集群之中

          rabbitmqctl -n rabbit2@node1 stop_app
          rabbitmqctl -n rabbit2@node1 reset
          
          rabbitmqctl -n rabbit2@node1 join_cluster rabbit1@node1
          
          rabbitmqctl -n rabbit2@node1 start_app
          
          rabbitmqctl -n rabbit2@node1 cluster_status

           

        6. 将rabbit2@node1节点加入rabbit1@node1的集群之中

          rabbitmqctl -n rabbit3@node1 stop_app
          rabbitmqctl -n rabbit3@node1 reset
          
          rabbitmqctl -n rabbit3@node1 join_cluster rabbit1@node1
          
          rabbitmqctl -n rabbit3@node1 start_app
          
          rabbitmqctl -n rabbit3@node1 cluster_status

           

    2. 查看服务日志

      1. 默认地址$RABBITMQ_HOME/var/log/rabbitmq

        1. RABBITMQ_NODENAME.log

          1. 如果想查看RabbitMQ应用服务的日志,则需要查询RABBITMQ_NODENAME.log这个文件,所谓的RabbitMQ服务日志指的就是这个文件。

        2. RABBITMQ_NODENAME-sasl.log

          1. SASL(System Application Support Libraries,系统应用程序支持库)是库的集合,作为Erlang-OTP发行版的一部分。它们帮助开发者在开发Erlang应用程序时提供一系列标准,其中之一是日志记录格式。所以当RabbitMQ记录Erlang相关信息时,它会将日志写入文件RABBITMQ_NODENAME-sasl.log中。举例来说,可以在这个文件中找到Erlang的崩溃报告,有助于调试无法启动的RabbitMQ节点。

      2. 日志级别node、error、warning、info、debug

      3. 日志级别可以通过rabbitmq.config配置文件中的log_levels参数来进行配置,默认为[{connection,info}]

      4. 常见日志摘要

        1. 启动RabbitMQ服务 - 日志

          1. RabbitMQ版本号、Erlang版本号、RabbitMQ服务节点名称、cookie的hash值、RabbitMQ配置文件地址、内存限制、磁盘限制、默认账户guest的创建及权限配置等。

          2. 如果开启了RabbitMQ Management插件,则在启动RabbitMQ的时候会打印插件启动日志

          3. 统计值信息的初始化日志

            1. aggr_queue_stats_fine_stats

            2. aggr_queue_stats_deliver_get

            3. aggr_queue_stats_queue_msg_counts

        2. 关闭RabbitMQ服务 - 日志

          1. rabbitmqctl stop_app(只关闭RabbitMQ服务)

          2. rabbitmqctl stop(关闭RabbitMQ服务 和 Erlang虚拟机)

        3. 建立集群 - 日志

        4. 客户端与RabbitMQ建立连接 - 日志

        5. 客户端强制中断连接 - 日志

      5. amq.rabbitmq.log(类型为topic的默认交换器amq.rabbitmq.log就是用来手机RabbitMQ日志的)

        1. 通过amq.rabbitmq.log收集日志

          1. 创建4个日志队列queue.debug、queue.info、queue.warning和queue.error,分别采用debug、info、warning和error这4个路由键来绑定amq.rabbitmq.log。如果要使用一个队列来收集所有级别的日志,可以使用“#”这个路由键。

        2. DEMO(没有找到amq.rabbitmq.log呀!!!版本:RabbitMQ 3.8.12 on Erlang 23.2.5)

          package club.limeyu.rabbitmq.tt.log;
          
          import com.rabbitmq.client.*;
          
          import java.io.IOException;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.TimeoutException;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/25 下午4:07
           */
          public class RabbitConsumer {
              public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
                  // 创建4个日志队列queue.debug、queue.info、queue.warning和queue.error,
                  // 分别采用debug、info、warning和error这4个路由键来绑定amq.rabbitmq.log。
                  // 如果要使用一个队列来收集所有级别的日志,可以使用“#”这个路由键。
          
                  ConnectionFactory connectionFactory = new ConnectionFactory();
                  connectionFactory.setHost("node1.limeyu.club");
                  connectionFactory.setPort(5672);
                  connectionFactory.setUsername("root");
                  connectionFactory.setPassword("root");
          
                  Connection connection = connectionFactory.newConnection();
                  Channel channel = connection.createChannel();
          
                  channel.queueDeclare("queue.debug", true, false, false, null);
          
                  channel.queueDeclare("queue.info", true, false, false, null);
          
                  channel.queueDeclare("queue.warning", true, false, false, null);
          
                  channel.queueDeclare("queue.error", true, false, false, null);
          
                  channel.queueBind("queue.debug", "amq.rabbitmq.trace", "debug");
                  channel.queueBind("queue.info", "amq.rabbitmq.trace", "info");
                  channel.queueBind("queue.warning", "amq.rabbitmq.trace", "warning");
                  channel.queueBind("queue.error", "amq.rabbitmq.trace", "error");
          
          
                  DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                      @Override
                      public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                          System.out.println(new String(body));
                          channel.basicAck(envelope.getDeliveryTag(), false);
                      }
                  };
          
                  channel.basicConsume("queue.debug", false, defaultConsumer);
                  channel.basicConsume("queue.info", false, defaultConsumer);
                  channel.basicConsume("queue.warning", false, defaultConsumer);
                  channel.basicConsume("queue.error", false, defaultConsumer);
          
                  TimeUnit.SECONDS.sleep(30);
          
                  channel.close();
                  connection.close();
              }
          }
          

           

    3. 单节点故障恢复(机器硬件故障、机器掉电、网络异常、服务进程异常)

      1. 啦啦啦

    4. 集群迁移

      1. 元数据重建

      2. 数据迁移和客户端连接的切换

      3. 自动化迁移

    5. 集群监控

      1. 通过HTTP API接口提供监控数据

      2. 通过客户端提供监控数据

      3. 检测RabbitMQ服务是否健康

      4. 元数据管理与监控

  8. 跨越集群的界限

    1. Federation

      1. 联邦交换器

      2. 联邦队列

      3. Federation的使用

        1. (1)需要配置一个或多个upstream,每个upstream均定义了到其他节点的Federation link。这个配置可以通过设置运行时的参数(Runtim Parameter)来完成,也可以通过federation management插件来完成。
        2. (2)需要定义匹配交换器或队列的一种/多种策略(Policy)。

        3. rabbitmq-plugins enable rabbitmq_federation

        4. rabbitmq-plugins enable rabbitmq_federation_management

        5. 在Federation中存在3种级别的配置

          1. (1)Upstreams:每个upstream用于定义与其他Broker建立连接的信息。

          2. (2)Upstream sets:每个upstream set用于对一系列使用Federation功能的upstream进行分组。

          3. (3)Policies:每个Policy会选定出一组交换器,或者队列,亦或者两者皆有而进行限定,进而作用于一个单独的upstream或upstream set 之上。

    2. Shovel

      1. Shovel 的原理

        1. add_forward_headers

        2. reconnect_delay

      2. Shovel的使用

        1. rabbitmq-plugins enable rabbitmq_shovel

        2. rabbitmq-plugins enable rabbitmq_shovel_management

        3. 静态方式部署

        4. 动态方式部署

      3. 案例:消息堆积的治理

        1. 通过Shovel将队列中的消息移交给另一个集群

  9. RabbitMQ高阶

    1. 存储机制

      1. 队列索引(rabbit_queue_index):维护队列中落盘消息的信息,包括消息的存储地点、是否已被交付给消费者、是否已被消费者ack等。

      2. 消息存储(rabbit_msg_store):以键值对的形式存储消息,它被所有队列共享,在每个节点中有且只有一个。

        1. msg_store_persistent:负责持久化消息的持久化,重启后消息不会丢失

        2. msg_store_transient:负责非持久化消息的持久化,重启后消息会丢失。

      3. 消息(包括消息体、属性和headers)可以直接存储在rabbit_queue_index中,也可以被保存在rabbit_msg_store中。(queue_index_embed_msgs_below)

      4. 默认$RABBITMQ_HOME/var/lib/mnesia/rabbit@$HOSTNAME/路径下包含queues、msg_store_peristent、msg_store_transient这3个文件夹

      5. rabbit_queue_index中以顺序(文件名从0开始累加)的段文件来进行存储,后缀为“.idx”,每个段文件中包含固定的SEGMENT_ENTRY_COUNT条记录,SEGMENT_ENTRY_COUNT默认值为16384。

      6. 经过rabbit_msg_store处理的所有消息都会以追加的方式写入到文件中,当一个文件的大小超过指定的限制(file_size_limit)后,关闭这个文件再创建一个新的文件以供新的消息写入。文件名(文件后缀是“.rdq”)从0开始进行累加,因此文件名最小的文件也是最老的文件。在进行消息的存储时,RabbitMQ会在ETS(Erlang Term Storage)表中记录消息在文件中的位置映射(Index)和文件的相关信息(FileSummary)。

      7. 在读取消息的时候,先根据消息的ID(msg_id)找到对应存储的文件,如果文件存在并且未被锁住,则直接打开文件,从指定位置读取消息的内容。如果文件不存在或者被锁住了,则发送请求由rabbit_msg_store进行处理。

      8. 消息的删除只是从ETS表删除指定消息的相关信息,同时更新消息对应的存储文件的相关信息。执行消息删除操作时,并不立即对在文件中的消息进行删除,也就是说消息依然在文件中,仅仅是标记为垃圾数据而已。

      9. 队列的结构

        1. rabbit_amqqueue_process:协议相关的消息处理,即接收生产者发布的消息、向消费者交付消息、处理消息的确认(包括生产端的confirm和消费端的ack)等。

        2. backing_queue:消息存储的具体形式和引擎,并向rabbit_amqqueue_process提供相关的接口以供调用。

        3. 消息的4中状态:

          1. alpha:消息内容(包括消息体、属性和headers)和消息索引都存储在内存中。

          2. beta:消息内容保存在磁盘中,消息索引保存在内存中。

          3. gamma:消息内容保存在磁盘中,消息索引在磁盘和内存中都有。

          4. delta:消息内容和索引都在磁盘中。

        4. 对于普通的没有设置优先级和镜像的队列来说,backing_queue的默认实现是rabbit_variable_queue,其内部通过5个子队列Q1(alpha)、Q2(beta)、Delta(delta)、Q3(gamma)和Q4(alpha)来实现消息的各个状态。

        5. 整个队列包括rabbit_amqqueue_process和backing_queue的各个子队列。

        6. 消费者获取消息也会引起消息的状态转换。当消费者获取消息时,首先会从Q4中获取消息,如果获取成功则返回。如果Q4为空,则尝试从Q3中获取消息,系统首先会判断Q3是否为空,如果为空则返回队列为空,即此时队列中无消息。如果Q3不为空,则取出Q3中的消息,进而再判断此时Q3和Delta中的长度,如果都为空,则可以认为Q2、Delta、Q3、Q4全部为空,此时将Q1中的消息直接转移至Q4,下次直接从Q4中获取消息。如果Q3为空,Delta不为空,则将Delta的消息转移至Q3中,下次可以直接从Q3中获取消息。在将消息从Delta转移到Q3的过程中,是按照索引分段读取的,首先读取某一段,然后判断读取的消息的个数与Delta中消息的个数是否相等,如果相等,则可以判定此时Delta中已无消息,则直接将Q2和刚读到的消息一并放入到Q3中;如果不相等,仅将此次读取到的消息转移到Q3。

        7. 通常在负载正常时,如果消息被消费的速度不小于接收新消息的数据,对于不需要保证可靠不丢失的消息来说,极有可能只会处于alpha状态。对于durable属性设置为true的消息,它一定会进入gamma状态,并且在开启publisher confirm机制时,只有到了gamma状态时才会确认该消息已被接收,若消息消费数据足够快、内存也充足,这些消息也不会继续走到下一个状态。

      10. 惰性队列

        1. 惰性队列会将接收到的消息直接存入文件系统中,而不管是持久化的或者是非持久化的,这样可以减少了内存的消耗,但是会增加I/O的使用,如果消息是持久化的,那么这样的I/O操作不可避免,惰性队列和持久化的消息可谓是“最佳拍档”。注意如果惰性队列中存储的是非持久化的消息,内存的使用率会一直很稳定,但是重启之后消息一样会丢失。

        2. channel.queueDeclare x-queue-mode:{lazy|default}

    2. 内存及磁盘告警

      1. 被阻塞的Connection的状态要么是blocking,要么是blocked。前者对应于并不试图发送消息的Connection,比如消费者关联的Connection,这种状态下的Connection可以继续运行。而后者对应于一直有消息发送的Connection,这种状态下的Connection会被停止发送消息。注意在一个集群中,如果一个Broker节点的内存或者磁盘受限,都会引起整个集群中所有的Connection被阻塞。
      2. BlockedListener监听相应连接的阻塞信息。

      3. 内存告警

        1. rabbitmqctl set_vm_memory_high_watermark {fraction}(重启失效)

        2. rabbitmqctl set_vm_memory_high_watermark absolute {memory_limit}(重启失效)

        3. 配置文件(重启生效)

  10. 网络分区

  11. RabbitMQ扩展

  12. 附录A 集群元数据信息示例

  13. SpringBoot 整合

    1. Maven

    2. yml

      spring:
        rabbitmq:
          host: rabbit.limeyu.club
          port: 5672
          username: rabbit
          password: rabbit
      

       

    3. 直连交换器

      1. 配置交换器、队列、绑定(DirectExchangeConfig)

        package club.limeyu.sprbtrbtmq.config;
        
        import org.springframework.amqp.core.Binding;
        import org.springframework.amqp.core.BindingBuilder;
        import org.springframework.amqp.core.DirectExchange;
        import org.springframework.amqp.core.Queue;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 上午10:58
         */
        @Configuration
        public class DirectExchangeConfig {
        
        
            // 声明接收接车单ID的队列
            @Bean
            public Queue declareQueue() {
                Queue queue = new Queue("queue.receiveBaseId", true, false, false, null);
                return queue;
            }
        
        
            // 声明直连交换器
            @Bean
            public DirectExchange declareDirectExchange() {
                DirectExchange directExchange = new DirectExchange("exchange.direct.receiveBaseId", true, false, null);
                return directExchange;
            }
        
            // 绑定交换器与队列
            @Bean
            public Binding BindQueue() {
                Binding binding = BindingBuilder.bind(declareQueue()).to(declareDirectExchange()).with("rk.receiveBaseId");
                return binding;
            }
        }
        

         

      2. 发送消息到RabbitMQ Server(SaveReceiveBaseListener)

        package club.limeyu.sprbtrbtmq.event.listener;
        
        import club.limeyu.sprbtrbtmq.event.SaveReceiveBaseEvent;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.amqp.rabbit.core.RabbitTemplate;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.ApplicationListener;
        import org.springframework.stereotype.Component;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 上午11:19
         */
        @Slf4j
        @Component
        public class SaveReceiveBaseListener implements ApplicationListener<SaveReceiveBaseEvent> {
        
            @Autowired
            private RabbitTemplate rabbitTemplate;
        
            @Override
            public void onApplicationEvent(SaveReceiveBaseEvent saveReceiveBaseEvent) {
        
                rabbitTemplate.convertAndSend("exchange.direct.receiveBaseId", "rk.receiveBaseId", saveReceiveBaseEvent.getReceiveBaseId());
        
            }
        }
        

         

      3. 消费端监听消费队列(多个消费端轮询消费)

        1. 消费端负载 1 (ConsumerReceiveBaseId1)

          package club.limeyu.sprbtrbtmq.rabbitmq;
          
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.amqp.rabbit.annotation.RabbitHandler;
          import org.springframework.amqp.rabbit.annotation.RabbitListener;
          import org.springframework.stereotype.Component;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/26 上午11:43
           */
          @Slf4j
          @Component
          @RabbitListener(queues = "queue.receiveBaseId")
          public class ConsumerReceiveBaseId1 {
          
          
              @RabbitHandler
              public void consumerReceiveBaseId(Long receiveBaseId) {
                  log.info("负载消费1::接收到接车单:{}", receiveBaseId);
              }
          }
          

           

        2. 消费端负载 2 (ConsumerReceiveBaseId2)

          package club.limeyu.sprbtrbtmq.rabbitmq;
          
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.amqp.rabbit.annotation.RabbitHandler;
          import org.springframework.amqp.rabbit.annotation.RabbitListener;
          import org.springframework.stereotype.Component;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/26 上午11:43
           */
          @Slf4j
          @Component
          @RabbitListener(queues = "queue.receiveBaseId")
          public class ConsumerReceiveBaseId2 {
          
          
              @RabbitHandler
              public void consumerReceiveBaseId(Long receiveBaseId) {
                  log.info("负载消费2 :: 接收到接车单:{}", receiveBaseId);
              }
          }
          

           

    4. 扇形交换器

      1. 配置交换器、队列、绑定

        package club.limeyu.sprbtrbtmq.config;
        
        import org.springframework.amqp.core.Binding;
        import org.springframework.amqp.core.BindingBuilder;
        import org.springframework.amqp.core.FanoutExchange;
        import org.springframework.amqp.core.Queue;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 下午1:16
         */
        @Configuration
        public class FanoutExchangeConfig {
        
            @Bean
            public FanoutExchange declareFanoutExchange() {
                FanoutExchange fanoutExchange = new FanoutExchange("exchange.fanout.receiveBaseId", true, false, null);
                return fanoutExchange;
            }
        
            @Bean
            public Queue declareQueueHave() {
                Queue queue = new Queue("queue.have", true, false, false, null);
                return queue;
            }
        
            @Bean
            public Queue declareQueueNice() {
                Queue queue = new Queue("queue.nice", true, false, false, null);
                return queue;
            }
        
            @Bean
            public Queue declareQueueDay() {
                Queue queue = new Queue("queue.day", true, false, false, null);
                return queue;
            }
        
            @Bean
            public Binding bindingQueueHave() {
                Binding binding = BindingBuilder.bind(declareQueueHave()).to(declareFanoutExchange());
                return binding;
            }
        
            @Bean
            public Binding bindingQueueNice() {
                Binding binding = BindingBuilder.bind(declareQueueNice()).to(declareFanoutExchange());
                return binding;
            }
        
            @Bean
            public Binding bindingQueueDay() {
                Binding binding = BindingBuilder.bind(declareQueueDay()).to(declareFanoutExchange());
                return binding;
            }
        }
        

         

      2. 发送消息到RabbitMQ Server

        package club.limeyu.sprbtrbtmq.event.listener;
        
        import club.limeyu.sprbtrbtmq.event.SaveReceiveBaseEvent;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.amqp.rabbit.core.RabbitTemplate;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.ApplicationListener;
        import org.springframework.stereotype.Component;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 上午11:19
         */
        @Slf4j
        @Component
        public class SaveReceiveBaseListener implements ApplicationListener<SaveReceiveBaseEvent> {
        
            @Autowired
            private RabbitTemplate rabbitTemplate;
        
            @Override
            public void onApplicationEvent(SaveReceiveBaseEvent saveReceiveBaseEvent) {
                
                rabbitTemplate.convertAndSend("exchange.fanout.receiveBaseId", "", saveReceiveBaseEvent.getReceiveBaseId());
        
            }
        }
        

         

      3. 消费端监听消费队列

        1. 监听queue.have队列

          package club.limeyu.sprbtrbtmq.rabbitmq;
          
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.amqp.rabbit.annotation.RabbitHandler;
          import org.springframework.amqp.rabbit.annotation.RabbitListener;
          import org.springframework.stereotype.Component;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/26 下午1:27
           */
          @Slf4j
          @Component
          @RabbitListener(queues = "queue.have")
          public class ConsumerFanoutHave {
          
              @RabbitHandler
              public void consumerQueueHave(Long receiveBaseId) {
                  log.info("queue.have 消费 :{}", receiveBaseId);
              }
          }
          

           

        2. 监听queue.nice队列

          package club.limeyu.sprbtrbtmq.rabbitmq;
          
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.amqp.rabbit.annotation.RabbitHandler;
          import org.springframework.amqp.rabbit.annotation.RabbitListener;
          import org.springframework.stereotype.Component;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/26 下午1:27
           */
          @Slf4j
          @Component
          @RabbitListener(queues = "queue.nice")
          public class ConsumerFanoutNice {
          
              @RabbitHandler
              public void consumerQueueNice(Long receiveBaseId) {
                  log.info("queue.nice 消费 :{}", receiveBaseId);
              }
          }
          

           

        3. 监听queue.day队列

          package club.limeyu.sprbtrbtmq.rabbitmq;
          
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.amqp.rabbit.annotation.RabbitHandler;
          import org.springframework.amqp.rabbit.annotation.RabbitListener;
          import org.springframework.stereotype.Component;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/26 下午1:27
           */
          @Slf4j
          @Component
          @RabbitListener(queues = "queue.day")
          public class ConsumerFanoutDay {
          
              @RabbitHandler
              public void consumerQueueDay(Long receiveBaseId) {
                  log.info("queue.day 消费 :{}", receiveBaseId);
              }
          }
          

           

    5. 主题交换器

      1. 设置交换器、队列、绑定关系

        package club.limeyu.sprbtrbtmq.config;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.amqp.core.Binding;
        import org.springframework.amqp.core.BindingBuilder;
        import org.springframework.amqp.core.Queue;
        import org.springframework.amqp.core.TopicExchange;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 上午11:50
         */
        @Slf4j
        @Configuration
        public class TopicExchangeConfig {
        
            @Bean
            public TopicExchange declareTopicExchange() {
                TopicExchange topicExchange = new TopicExchange("exchange.topic.receiveBaseId", true, false, null);
                return topicExchange;
            }
        
            @Bean
            public Queue declareQueueNatural() {
                Queue queue = new Queue("queue.natural.receiveBaseId", true, false, false, null);
                return queue;
            }
        
            @Bean
            public Queue declareQueueOrange() {
                Queue queue = new Queue("queue.orange.receiveBaseId", true, false, false, null);
                return queue;
            }
        
            @Bean
            public Queue declareQueueFresh() {
                Queue queue = new Queue("queue.fresh.receiveBaseId", true, false, false, null);
                return queue;
            }
        
            @Bean
            public Binding bindingQueueNatural() {
                Binding binding = BindingBuilder.bind(declareQueueNatural()).to(declareTopicExchange()).with("rk.natural.receiveBaseId");
                return binding;
            }
        
            @Bean
            public Binding bindingQueueOrange() {
                Binding binding = BindingBuilder.bind(declareQueueOrange()).to(declareTopicExchange()).with("rk.#");
                return binding;
            }
        
            @Bean
            public Binding bindingQueueFresh() {
                Binding binding = BindingBuilder.bind(declareQueueFresh()).to(declareTopicExchange()).with("rk.fresh.*");
                return binding;
            }
        }
        

         

      2. 发送消息到RabbitMQ Server

        package club.limeyu.sprbtrbtmq.event.listener;
        
        import club.limeyu.sprbtrbtmq.event.SaveReceiveBaseEvent;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.amqp.rabbit.core.RabbitTemplate;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.ApplicationListener;
        import org.springframework.stereotype.Component;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 上午11:19
         */
        @Slf4j
        @Component
        public class SaveReceiveBaseListener implements ApplicationListener<SaveReceiveBaseEvent> {
        
            @Autowired
            private RabbitTemplate rabbitTemplate;
        
            @Override
            public void onApplicationEvent(SaveReceiveBaseEvent saveReceiveBaseEvent) {
        
                // 匹配 rk.natural.receiveBaseId
                // 匹配 rk.#
        //        rabbitTemplate.convertAndSend("exchange.topic.receiveBaseId", "rk.natural.receiveBaseId", saveReceiveBaseEvent.getReceiveBaseId());
        
                // 匹配 rk.#
        //        rabbitTemplate.convertAndSend("exchange.topic.receiveBaseId", "rk.topic", saveReceiveBaseEvent.getReceiveBaseId());
        
                // 匹配 rk.#
        //        rabbitTemplate.convertAndSend("exchange.topic.receiveBaseId", "rk.fresh", saveReceiveBaseEvent.getReceiveBaseId());
        
                // 匹配 rk.#
                // 匹配 rk.fresh.receiveBaseId
                rabbitTemplate.convertAndSend("exchange.topic.receiveBaseId", "rk.fresh.morning", saveReceiveBaseEvent.getReceiveBaseId());
        
            }
        }
        

         

      3. 消费端监听消费队列

        1. 监听queue.natural.receiveBaseId队列

          package club.limeyu.sprbtrbtmq.rabbitmq;
          
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.amqp.rabbit.annotation.RabbitHandler;
          import org.springframework.amqp.rabbit.annotation.RabbitListener;
          import org.springframework.stereotype.Component;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/26 下午12:04
           */
          @Slf4j
          @Component
          @RabbitListener(queues = "queue.natural.receiveBaseId")
          public class ConsumerTopicNatural {
          
              @RabbitHandler
              public void consumerQueueNatural(Long receiveBaseId) {
                  log.info("queue.natural.receiveBaseId 接收到消息 : {}", receiveBaseId);
              }
          }
          

           

        2. 监听queue.orange.receiveBaseId队列

          package club.limeyu.sprbtrbtmq.rabbitmq;
          
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.amqp.rabbit.annotation.RabbitHandler;
          import org.springframework.amqp.rabbit.annotation.RabbitListener;
          import org.springframework.stereotype.Component;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/26 下午12:04
           */
          @Slf4j
          @Component
          @RabbitListener(queues = "queue.orange.receiveBaseId")
          public class ConsumerTopicOrange {
          
              @RabbitHandler
              public void consumerQueueOrange(Long receiveBaseId) {
                  log.info("queue.orange.receiveBaseId 接收到消息 : {}", receiveBaseId);
              }
          }
          

           

        3. 监听queue.fresh.receiveBaseId队列

          package club.limeyu.sprbtrbtmq.rabbitmq;
          
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.amqp.rabbit.annotation.RabbitHandler;
          import org.springframework.amqp.rabbit.annotation.RabbitListener;
          import org.springframework.stereotype.Component;
          
          /**
           * @Author liangmy
           * @Description:
           * @Create: 2021/2/26 下午12:04
           */
          @Slf4j
          @Component
          @RabbitListener(queues = "queue.fresh.receiveBaseId")
          public class ConsumerTopicFresh {
          
              @RabbitHandler
              public void consumerQueueFresh(Long receiveBaseId) {
                  log.info("queue.fresh.receiveBaseId 接收到消息 : {}", receiveBaseId);
              }
          }
          

           

    6. 头交换器

    7. 生产方确认机制(事务机制、发送方确认机制)

      1. yml

        spring:
          rabbitmq:
            # 确认消息已发送到交换机(Exchange)
            publisher-confirms: true
            # 确认消息已发送到队列(Queue)
            publisher-returns: true
        

         

      2. 设置RabbitTemplate,配置ConfirmCallback、ReturnCallback监听器

        package club.limeyu.sprbtrbtmq.config;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.amqp.core.Message;
        import org.springframework.amqp.core.MessageProperties;
        import org.springframework.amqp.rabbit.connection.ConnectionFactory;
        import org.springframework.amqp.rabbit.core.RabbitTemplate;
        import org.springframework.amqp.rabbit.support.CorrelationData;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        import java.util.Map;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 下午1:46
         */
        @Slf4j
        @Configuration
        public class RabbitConfig {
        
            @Bean
            public RabbitTemplate initRabbitTemplate(ConnectionFactory connectionFactory) {
                RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
                rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
                    @Override
                    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                        log.info("ConfirmCallback :: 请求唯一标志 : {}", null != correlationData ? correlationData.getId() : null);
                        log.info("ConfirmCallback :: 确认情况 : {}", ack);
                        log.info("ConfirmCallback :: 原因 : {}", cause);
                    }
                });
        
                // 当mandatory参数设为true时,交换器无法根据自身的类型和路由键找到一个符合条件的队列,
                // 那么RabbitMQ会调用Basic.Return命令将消息返回给生产者。
                // 当mandatory参数设置为false时,出现上述情形,则消息直接被丢弃。
                rabbitTemplate.setMandatory(true);
        
                rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
                    @Override
                    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                        log.info("ReturnCallback :: message.getBody() : {}", message.getBody());
                        MessageProperties messageProperties = message.getMessageProperties();
                        if (null != messageProperties) {
                            log.info("ReturnCallback :: messageProperties.getAppId() : {}", messageProperties.getAppId());
                            log.info("ReturnCallback :: messageProperties.getCorrelationIdString() : {}", messageProperties.getCorrelationIdString());
                            log.info("ReturnCallback :: messageProperties.getContentType() : {}", messageProperties.getContentType());
                            log.info("ReturnCallback :: messageProperties.getContentLength() : {}", messageProperties.getContentLength());
                            log.info("ReturnCallback :: messageProperties.getContentEncoding() : {}", messageProperties.getContentEncoding());
                            log.info("ReturnCallback :: messageProperties.getConsumerTag() : {}", messageProperties.getConsumerTag());
                            log.info("ReturnCallback :: messageProperties.getConsumerQueue() : {}", messageProperties.getConsumerQueue());
                            log.info("ReturnCallback :: messageProperties.getClusterId() : {}", messageProperties.getClusterId());
                            log.info("ReturnCallback :: messageProperties.getDeliveryTag() : {}", messageProperties.getDeliveryTag());
                            log.info("ReturnCallback :: messageProperties.getDeliveryMode() : {}", messageProperties.getDeliveryMode());
                            log.info("ReturnCallback :: messageProperties.getDelay() : {}", messageProperties.getDelay());
                            log.info("ReturnCallback :: messageProperties.getExpiration() : {}", messageProperties.getExpiration());
                            Map<String, Object> headers = messageProperties.getHeaders();
                            for (Map.Entry entry : headers.entrySet()) {
                                log.info("ReturnCallback :: headers : key : {}, value : {}", entry.getKey(), entry.getValue());
                            }
                            log.info("ReturnCallback :: messageProperties.getInferredArgumentType() : {}", messageProperties.getInferredArgumentType());
                            log.info("ReturnCallback :: messageProperties.getMessageId() : {}", messageProperties.getMessageId());
                            log.info("ReturnCallback :: messageProperties.getMessageCount() : {}", messageProperties.getMessageCount());
                            log.info("ReturnCallback :: messageProperties.getPriority() : {}", messageProperties.getPriority());
                            log.info("ReturnCallback :: messageProperties.getReceivedDelay() : {}", messageProperties.getReceivedDelay());
                            log.info("ReturnCallback :: messageProperties.getReceivedDeliveryMode() : {}", messageProperties.getReceivedDeliveryMode());
                            log.info("ReturnCallback :: messageProperties.getReplyToAddress() : {}", messageProperties.getReplyToAddress());
                            log.info("ReturnCallback :: messageProperties.getReplyTo() : {}", messageProperties.getReplyTo());
                            log.info("ReturnCallback :: messageProperties.getRedelivered() : {}", messageProperties.getRedelivered());
                            log.info("ReturnCallback :: messageProperties.getReceivedUserId() : {}", messageProperties.getReceivedUserId());
                            log.info("ReturnCallback :: messageProperties.getReceivedRoutingKey() : {}", messageProperties.getReceivedRoutingKey());
                            log.info("ReturnCallback :: messageProperties.getReceivedExchange() : {}", messageProperties.getReceivedExchange());
                            log.info("ReturnCallback :: messageProperties.getTargetBean() : {}", messageProperties.getTargetBean());
                            log.info("ReturnCallback :: messageProperties.getTargetMethod() : {}", messageProperties.getTargetMethod());
                            log.info("ReturnCallback :: messageProperties.getTimestamp() : {}", messageProperties.getTimestamp());
                            log.info("ReturnCallback :: messageProperties.getType() : {}", messageProperties.getType());
                            log.info("ReturnCallback :: messageProperties.getUserId() : {}", messageProperties.getUserId());
                        }
                        log.info("ReturnCallback :: replyCode : {}", replyCode);
                        log.info("ReturnCallback :: replyText : {}", replyText);
                        log.info("ReturnCallback :: exchange : {}", exchange);
                        log.info("ReturnCallback :: routingKey : {}", routingKey);
                    }
                });
                return rabbitTemplate;
            }
        }
        

         

      3. 配置交换器、队列、绑定关系

        package club.limeyu.sprbtrbtmq.config;
        
        import org.springframework.amqp.core.Binding;
        import org.springframework.amqp.core.BindingBuilder;
        import org.springframework.amqp.core.DirectExchange;
        import org.springframework.amqp.core.Queue;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.stereotype.Component;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 下午3:43
         */
        @Component
        @Configuration
        public class DirectExchangePublisherConfirmConfig {
        
        
            // 声明交换器
            @Bean
            public DirectExchange declareExchangeDirectConfirm() {
                DirectExchange directExchange = new DirectExchange("exchange.direct.publisher.confirm", true, false, null);
                return directExchange;
            }
        
        
            // 声明队列
            @Bean
            public Queue declareQueueConfirm() {
                Queue queue = new Queue("queue.publisher.confirm");
                return queue;
            }
        
            // 绑定关系
            @Bean
            public Binding bindingQueue() {
                Binding binding = BindingBuilder.bind(declareQueueConfirm()).to(declareExchangeDirectConfirm()).with("rk.publisher.confirm");
                return binding;
            }
        
        }
        

         

      4. 发送消息到RabbitMQ Server

        package club.limeyu.sprbtrbtmq.event.listener;
        
        import club.limeyu.sprbtrbtmq.event.SaveReceiveBaseEvent;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.amqp.rabbit.core.RabbitTemplate;
        import org.springframework.amqp.rabbit.support.CorrelationData;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.ApplicationListener;
        import org.springframework.stereotype.Component;
        
        import java.util.UUID;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 上午11:19
         */
        @Slf4j
        @Component
        public class SaveReceiveBaseListener implements ApplicationListener<SaveReceiveBaseEvent> {
        
            @Autowired
            private RabbitTemplate rabbitTemplate;
        
            @Override
            public void onApplicationEvent(SaveReceiveBaseEvent saveReceiveBaseEvent) {
        
                // 交换器不存在(ConfirmCallback 服务端确认一条或多条消息后,发送方会回调这个方法进行处理)
        //        rabbitTemplate.convertAndSend("exchange.direct.publisher.confirm.not.exist", "rk.publisher.confirm", saveReceiveBaseEvent.getReceiveBaseId(), new CorrelationData(UUID.randomUUID().toString()));
        
                // 交换器存在,路由键不匹配或队列不存在(ConfirmCallback 服务端确认一条或多条消息后,发送方会回调这个方法进行处理,ReturnCallback 监听获取到没有被正确路由到合适队列的消息)
        //        rabbitTemplate.convertAndSend("exchange.direct.publisher.confirm", "rk.publisher.confirm.not.exist", saveReceiveBaseEvent.getReceiveBaseId(), new CorrelationData(UUID.randomUUID().toString()));
        
                // 交换器存在,队列存在,正常发送消息(ConfirmCallback 服务端确认一条或多条消息后,发送方会回调这个方法进行处理)
                rabbitTemplate.convertAndSend("exchange.direct.publisher.confirm", "rk.publisher.confirm", saveReceiveBaseEvent.getReceiveBaseId(), new CorrelationData(UUID.randomUUID().toString()));
        
            }
        }
        

         

    8. 消费方确认机制

      1. 配置简单消息监听容器(SimpleMessageListenerContainer)

        package club.limeyu.sprbtrbtmq.config;
        
        import club.limeyu.sprbtrbtmq.rabbitmq.ConsumerConfirmManual;
        import org.springframework.amqp.core.AcknowledgeMode;
        import org.springframework.amqp.rabbit.connection.ConnectionFactory;
        import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.stereotype.Component;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 下午3:18
         */
        @Component
        @Configuration
        public class ConsumerConfirmConfig {
        
            @Autowired
            private ConsumerConfirmManual consumerConfirmManual;
        
            @Bean
            public SimpleMessageListenerContainer initSimpleMessageListenerContainer(ConnectionFactory connectionFactory) {
                SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
        
                // 设置多个并发消费者一起消费
                simpleMessageListenerContainer.setConcurrentConsumers(1);
                // 设置最多的并发消费者
                simpleMessageListenerContainer.setMaxConcurrentConsumers(1);
        
                // AcknowledgeMode.NONE : 默认推送的所有消息都已经消费成功
                // AcknowledgeMode.MANUAL : 需要人为地获取到channel之后调用方法向server发送ack(或消费失败时的nack)信息
                // AcknowledgeMode.AUTO : 由spring-rabbit依据消息处理逻辑是否抛出异常自动发送ack(无异常)或nack(异常)到server端
                simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        
                // 同时监听多个队列
                simpleMessageListenerContainer.addQueueNames("queue.consumer.confirm.manual");
        
                // 设置消息监听
                simpleMessageListenerContainer.setMessageListener(consumerConfirmManual);
        
                return simpleMessageListenerContainer;
            }
        }
        

         

      2. 获取消息,处理消息。(实现ChannelAwareMessageListener接口)

        package club.limeyu.sprbtrbtmq.rabbitmq;
        
        import com.rabbitmq.client.Channel;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.amqp.core.Message;
        import org.springframework.amqp.core.MessageProperties;
        import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
        import org.springframework.stereotype.Component;
        
        import java.util.Map;
        
        /**
         * @Author liangmy
         * @Description:
         * @Create: 2021/2/26 下午3:31
         */
        @Slf4j
        @Component
        public class ConsumerConfirmManual implements ChannelAwareMessageListener {
        
        
            @Override
            public void onMessage(Message message, Channel channel) throws Exception {
                log.info("message.getBody() : {}", new String(message.getBody()));
                MessageProperties messageProperties = message.getMessageProperties();
                if (null != messageProperties) {
                    log.info("messageProperties.getAppId() : {}", messageProperties.getAppId());
                    log.info("messageProperties.getClusterId() : {}", messageProperties.getClusterId());
                    log.info("messageProperties.getConsumerQueue() : {}", messageProperties.getConsumerQueue());
                    log.info("messageProperties.getConsumerTag() : {}", messageProperties.getConsumerTag());
                    log.info("messageProperties.getContentEncoding() : {}", messageProperties.getContentEncoding());
                    log.info("messageProperties.getContentLength() : {}", messageProperties.getContentLength());
                    log.info("messageProperties.getContentType() : {}", messageProperties.getContentType());
                    log.info("messageProperties.getCorrelationIdString() : {}", messageProperties.getCorrelationIdString());
                    log.info("messageProperties.getDelay() : {}", messageProperties.getDelay());
                    log.info("messageProperties.getDeliveryMode() : {}", messageProperties.getDeliveryMode());
                    log.info("messageProperties.getDeliveryTag() : {}", messageProperties.getDeliveryTag());
                    log.info("messageProperties.getExpiration() : {}", messageProperties.getExpiration());
                    Map<String, Object> headers = messageProperties.getHeaders();
                    for (Map.Entry entry : headers.entrySet()) {
                        log.info("messageProperties.getHeaders() :: key : {}, value : {}", entry.getKey(), entry.getValue());
                    }
                    log.info("messageProperties.getInferredArgumentType() : {}", messageProperties.getInferredArgumentType());
                    log.info("messageProperties.getMessageCount() : {}", messageProperties.getMessageCount());
                    log.info("messageProperties.getMessageId() : {}", messageProperties.getMessageId());
                    log.info("messageProperties.getPriority() : {}", messageProperties.getPriority());
                    log.info("messageProperties.getReceivedDelay() : {}", messageProperties.getReceivedDelay());
                    log.info("messageProperties.getReceivedDeliveryMode() : {}", messageProperties.getReceivedDeliveryMode());
                    log.info("messageProperties.getReceivedExchange() : {}", messageProperties.getReceivedExchange());
                    log.info("messageProperties.getReceivedRoutingKey() : {}", messageProperties.getReceivedRoutingKey());
                    log.info("messageProperties.getReceivedUserId() : {}", messageProperties.getReceivedUserId());
                    log.info("messageProperties.getRedelivered() : {}", messageProperties.getRedelivered());
                    log.info("messageProperties.getReplyTo() : {}", messageProperties.getReplyTo());
                    log.info("messageProperties.getReplyToAddress() : {}", messageProperties.getReplyToAddress());
                    log.info("messageProperties.getTargetBean() : {}", messageProperties.getTargetBean());
                    log.info("messageProperties.getTargetMethod() : {}", messageProperties.getTargetMethod());
                    log.info("messageProperties.getTimestamp() : {}", messageProperties.getTimestamp());
                    log.info("messageProperties.getType() : {}", messageProperties.getType());
                    log.info("messageProperties.getUserId() : {}", messageProperties.getUserId());
                }
        
                // 确认单条消息已接收
        //        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                
                // 确认单条消息不接收,并重新加入队列
        //        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
                
                // 确认单条消息不接收,并不重新加入队列
        //        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
                
                // 拒绝消息,重新加入队列
        //        channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
                
                // 是否恢复消息到队列,参数是requeue。
                // true则重新入队列,并且尽可能的将之前recover的消息投递给其他消费者消费,而不是自己再次消费。
                // false则消息会重新被投递给自己。
                channel.basicRecover(true);
        
            }
        }
        

         

    9. RPC实现

    10. 延迟队列

    11. 备份交换器

    12. 啦啦啦

  14. 业务架构

ChatGPT Web转API工具:原理、部署与集成实战 在AI应用开发中,API(应用程序编程接口)是实现系统间通信和数据交换的核心技术,它通过标准化的请求响应机制,将复杂功能封装为可编程的服务。其工作原理基于HTTP协议,通过定义清晰的端点、方法和数据格式,使不同平台的应用能够高效集成AI能力。这一技术价值在于极大降低了开发门槛,促进了自动化流程和智能功能的快速落地,广泛应用于智能客服、内容生成和创意辅助等场景。本文聚焦于如何通过浏览器自动化技术,将ChatGPT的Web界面能力转化为标准API服务,其中涉及Playwright无头浏览器操作和会话Cookie 阅读详情

相关推荐

深入解析Apollo Lattice Planner:自动驾驶轨迹规划的核心算法与实践

本文深入解析了百度Apollo自动驾驶平台中的核心轨迹规划算法——Lattice Planner。文章详细阐述了其作为车辆“即时决策者”的工作原理,通过“采样-评估”的六步流程(Frenet坐标转换、状态采样、轨迹生成、硬约束过滤、代价函数打分及决策平滑),在复杂路况下实时规划出安全、舒适、高效的行驶轨迹。该算法是自动驾驶实现智能决策与精准控制的关键技术。

weixin_29090677的博客 826

php操作rabbitmq(5)

官方示例地址 交互机交换方式topic和direct在代码上没有任何区别,区别的是direct绑定key是全等才会匹配,而topic可以模糊匹配,在场景不确定的情况下建议使用topic。 <?php /** * 生产者 * emit_log_topic.php */ require_once __DIR__ . '/vendor/autoload.php'; use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Messa

qq_42488996的博客 203

基础矩阵、本质矩阵、单应矩阵

基础矩阵是一个 3×3 的秩为2的矩阵,用于描述两个不同视图(或称为图像)之间对应点的几何约束。更具体地说,如果你有两幅图像,分别从两个不同的相机或者相机位置拍摄,那么基础矩阵可以用于找到一个图像中的点在另一个图像中的对应点。本质矩阵也是一个 3×3 的秩为2的矩阵,与基础矩阵非常类似。当两个视图(图像)之间的场景是一个平面或者两个视图之间仅有旋转和平移时,可以使用单应矩阵来关联这两个视图。这三个矩阵都是在计算机视觉中广泛应用的基础工具,但它们适用于不同类型的视图和场景。给定两个图像中的一对对应点。

Gloamirror的博客 1326

【学习笔记】《RabbitMQ实战指南》笔记

本文摘录总结自《RabbitMQ实战指南》。 一、消息中间件 消息队列中间件(MessageQueueMiddleware,简称为MQ)是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。它一般有两种传递模式:点对点(P2P,Point-to-Point)模式和发布/订阅(Pub/Sub)模式。 消息中间件的作用如下: 解耦:消息中间件在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,这允许你独立地扩展或修改两边的处理过程,只要

zuzhiang的博客 790

DeepSeek-OCR 本地部署使用教程(附完整环境配置与推理示例)

DeepSeek-OCR是由DeepSeek AI推出的开源OCR模型,支持高精度文档识别与结构化输出(如Markdown转换)。本文提供了详细的本地部署指南,包括系统要求(推荐Linux+A100 GPU)、环境配置(Python 3.12.9+PyTorch 2.6.0+CUDA 11.8)、模型下载及两种推理方式:高性能的vLLM(适合批量处理)和灵活的Transformers(适合调试)。支持多种分辨率模式和提示词模板,适用于合同识别、PDF转换等场景。常见问题包括显存优化和性能调优建议。

qq_58607032的博客 6711

手把手教你学Simulink——基于Simulink的变换器老化预测与数字孪生(Digital Twin)

这不仅是技术的演进,更是从“事后救火”到“事前预言”的降维打击。:通过本文的实战演练,你不仅掌握了功率变换器“电气-热-老化”多物理场耦合的建模哲学,更深刻领悟了如何将高深的随机过程(粒子滤波)降维成可落地、可量产的工程工具;:起初,由于数据量少,粒子滤波器预测的剩余寿命(RUL)置信区间(95%)非常宽泛(如 1000~2000小时)。在工业4.0与智能制造的浪潮下,功率变换器(如光伏逆变器、车载OBC、服务器电源)不再是孤立的硬件,而是具备“自我感知”与“寿命预知”的智能节点。

MHD0815的博客 293

RabbitMQ多租户与权限

每一个RabbitMQ服务器都能创建虚拟的消息服务器,我们称之为虚拟主机(virtual host),简称vhost. 每一个vhost本质上都是一个独立的小型RabbitMQ服务器,拥有独立的队列,交换器以及绑定关系等,并且拥有独立的权限。vhost就像是虚拟机与物理服务器一样,它们在各个实例之间提供逻辑上的分离。 vhost是绝对隔离的,无法将vhost1中的交换器与vh...

qq_34579060的博客 955

手把手教你驱动6脚三位一体数码管:从引脚分析到C语言扫描代码实战

本文详细解析了如何逆向工程并驱动6脚三位一体数码管的全过程,从引脚分析到C语言扫描代码实战。通过系统化的测试和优化,作者总结出一套高效的驱动架构和调试方法,适用于工业设备中的非标准数码管应用。

weixin_42632434的博客 211

RabbitMQ实战指南笔记

目录 第1章 RabbitMQ简介 1.1 什么是消息中间件 1.2 消息中间件的作用 1.3 RabbitMQ起源 1.4 RabbitMQ的安装及简单实用 第2章 RabbitMQ入门 2.1 概念介绍 2.2 AMQP协议介绍 第3章 客户端开发向导(php-amqplib) 3.1 连接RabbitMQ 3.2 使用交换器和队列 3.3 发送消息 3.4 消...

青鬆下的ミ坚躯 的个人博客 1879

YOLOv11改进,YOLOv11添加ASFF检测头,并添加小目标检测层(四头检测),适合目标检测、分割等任务,全网首发

YOLOv11改进,YOLOv11添加ASFF检测头,并添加小目标检测层(四头检测),适合目标检测、分割等任务,全网首发

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 4734

SAP_MM_配额安排_PA认证总结

配额安排是企业进行供应商管理的一种方式。通过配额管理,企业可以与多家供应商保持业务往来,以保证某些紧缺原材料的正常供货。此外,企业也可以通过不同的配额比例,提高供应商优化服务、降低价格的积极性。

java_zhong1990的专栏 1020

RabbitMQ实战指南学习笔记

花了接近2周时间看了一下RabbitMQ,感觉收货很多,顺便也晒一下这2周的劳动成果 00 RabbitMQ概述 01 消息中间件的概述 02 RabbitMQ环境搭建:centos7+RabbitMQ-3.6.9 03 RabbitMQ的简单使用,及整合Spring Boot 04 RabbitMQ的简单使用-相关概念 05 RabbitMQ开发-连接 06 RabbitMQ开发-交换器,队列 ...

brusion的博客 1056

Debezium系列之:使用指定值替代指定字段的值

Debezium系列之:使用指定值替代指定字段的值

zhengzaifeidelushang的博客 306

《RabbitMQ实战指南》读书笔记

第一章 RabbitMQ简介 1.1 什么是消息中间件 消息(message)是指在应用间传递的数据。 消息队列中间件(Message Queue MidMiddleware,简称MQ),是指提供平台无关的、高效可靠的消息传递机制的中间件。 MQ通常又两种传递模式: 点对点模式(P2P, Point-To-Point):即一对一模式。该模式是基于队列的,消息生产者发送消息到队列,消息消费者从队列中接收消息,队列的存在使得消息的异步传输成为可能。 发布/订阅模式(Pub/Sub):即一对多模式。发布订阅模式

m0_37979033的博客 846

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

《RabbitMQ实战指南》读书笔记——高级特性

高级特性部分偏运维,讲了很多配置和命令,简单了解,重点了解一下集群和负载均衡。 1、RabbitMQ管理 (1)多租户与权限 每一个RabbitMQ服务器能创建虚拟的消息服务器(vhost),拥有独立的Queue、Exchange、Bindings、权限等,提供了逻辑上的分离,默认创建的vhost为“/”,权限控制以vhost为单位。 rabbitmqctl是用来管理RabbitMQ...

weixin_39420024的博客 878

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

RabbitMQ实战指南

RabbitMQ实战指南

baidu_38634017的博客 1865

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

RabbitMQ实战教程笔记

什么是MQ MQ就是消息队列,通过典型的生产者和消费者模型,生产者不停的向消息队列中生产消息,消费者不停的从消息队列中获取消息。因为消息的生产与消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,轻松实现系统间解耦合。 不同MQ的特点 **ActiveMQ:**是Apache出品的,它是一个完全支持JMS规范的消息中间件,丰富的API,多种集群架构模式让ActiveMQ成为业界老牌的消息中间件,在中小型企业颇受欢迎。但是它的吞吐量低,性能不高。 **Kafka:**也是Apzche出品的,主

zyh201314zyh的博客 1365

《RabbitMQ实战指南》读书笔记——基础篇(Chapter 1~3)

1、什么是MQ MQ有点对点(P2P,Point-to-Point)和发布/订阅(Pub/Sub)两种模式。RabbitMQ属于P2P模式,但也可以通过将消息写入多个队列来实现发布订阅,如图所示。 MQ的作用概括如下:解耦(两边处理过程独立)、冗余(存储)、扩展性、削峰、可恢复性、顺序保证(一定程度上)、缓冲(控制数据流速度)、异步通信。 RabbitMQ是Erl...

weixin_39420024的博客 391

RabbitMQ实战笔记

RabbitMQ实战笔记,保姆级别笔记,各种示例详细记录,企业实战场景,哪来急用,满足你关于MQ的所有需要!!!

ctflq的博客 2336

《rabbitmq 实战指南》- 读书笔记

rabbitmq的主要作用有:异步,解耦,削峰填谷rabbitmq的生命周期包含连接、创建、生产、消费,关闭。rabbitmq的消费模式有两种,push和pull,推模式采用basic.consume消费,拉模式则调用basic.get进行消费rabbitmq提供了消息确认机制,目的是确保消息一定能够被消费者消费掉,当消费者订阅队列的时候,可以指定autoAck参数,为false的时候rabbit...

weixin_34273479的博客 846

笔记-《RabbitMQ实战指南》

读书笔记

qq_38477936的博客 437

RabbitMq实战指南阅读笔记

基础 RoutingKey、BindingKey RoutingKey是生产者发消息给消费者的时候指定的,BindingKey是交换器和队列绑定时设置的,两者匹配,就会进行消息传递 队列声明 queueDeclare (String queue , boolean durable , boolean exclusive , boolean autoDelete , Map<Str ng Object> arguments) throws IOException; autoDelete:自动删

DB19981999的博客 1116

rabbitmq入门_「读书笔记」RabbitMQ 实战指南前四章笔记

这一个月读了《RabbitMQ 实战指南》,自觉对 RabbitMQ 的认识比以前更深了一点,更系统了一点。这篇文章主要记录了我在阅读前四章中整理的思维脑图。前四章的知识点,涵盖了 RabbitMQ 日常应用的大部分知识点,有必要系统的梳理一遍。01 RabbitMQ 简介第一章简单介绍了相关概念,例如消息中间件,RabbitMQ 的含义。这是入门性质的概念,读多几遍,思考这种技术提出来要应对的问...

weixin_39618597的博客 101

RabbitMQ实战-笔记

第二章 理解消息通信 2.1消费者与生产者的概念 信道是建立在tcp上的虚拟连接,可以减少tcp连接的开销 2.2 amqp消息路由必须有三部分: 交换器,队列和绑定, 生产者把消息发布到交换器上,消息最终到达队列,绑定决定了消息如何从路由器路由到特定队列. 2.2.1通过amqp的consume命令可以将信道设置为接收模式,直到取消对队列的订阅为止.消费者在消费了最近接收的那条消息后就可...

manwea的专栏 532

《RabbitMQ实战》读书笔记

第一章 天降神兵 RabbitMQ是一个开源的AMQP实现,用Erlang语言编写,支持多种开发语言,用于在分布式系统中存储转发消息。 第二章 理解消息通信 生产者:创建消息并设置标签 消费者:接收并消费消息 信道:生产者。消费者与RabbitMQ建立连接,即创建一条AMQP信道,每个信道有唯一的Id,一个TCP连接可以创建多个信道 AMQP消息路由必须包含三部分:交换器、队列和绑定。 消费者接收消息,单个消息:basic.get命令,持续接收:basic.consume命令,消息到达队列自动接收。.

白夜行 435
上一篇: Nginx-负载均衡-不停机部署
下一篇: 第7章 虚拟机类加载机制
limeOracle
博客等级 码龄10年 11粉丝 566原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值