消息队列-rabbitmq入门 springboot整合rabbitmq

 rabitmq官方网站:RabbitMQ Tutorials — RabbitMQ

一.基本概念

基于AMPQ协议:高级消息队列协议 Advanced Message Queue Protocol

生产者(Publisher):发消息到某个交换机

消费者(Consumer):从某个队列取消息

交换机(Exchange):负责把消息转发到对应的队列 

队列(Queue):存储消息

路由(Routes):转发,就是怎么把消息从一个地方转发到另一个地方

二.基本准备与操作

①安装erlang并配置环境变量,因为rabbitmq依赖erlang

25.3.2 - Erlang (及其erlang的其他版本)

②安装rabbitmq Installing on Windows — RabbitMQ

③安装二者后,检查rabbitmq是否启动

command + r 打开 services.msc (服务菜单)

---若安装rabbitmq出现如下图情况,检查erlang版本是否和rabbitmq版本对应

我的安装版本

erlang v-26.1              rabbitmq v-3.12.5

④安装rabbitmq监控面板

在rabbitmq的安装目录sbin下执行如下脚本

rabbitmq-plugins.bat enable rabbitmq_ management

⑤访问http://localhost:15672

初始账户密码都是guest

注意,rabbitmq占用两个端口

15672:webUI

5672:程序连接的端口

-----------------------------------------------基本准备完成-------------------------------------------------------------- 

三.快速入门

RabbitMQ Tutorials — RabbitMQ

rabbitmq相关依赖

 <dependency>
     <groupId>com.rabbitmq</groupId>
     <artifactId>amqp-client</artifactId>
     <version>5.18.0</version>
 </dependency>

 1. 单向发送

basicPublish()

RabbitMQ 中,basicPublish 方法是用于发布消息到指定队列的。该方法接受四个参数:

  • exchange:指定消息被发布到的交换机(exchange)的名称。如果没有指定交换机,消息将发布到默认交换机(default_exchange)。
  • routing_key:指定消息将被路由到的队列(queue)的名称。如果消息未指定路由键,将发送到队列中名为 amq.gen-{int} 的临时队列。
  • body:指定要发布的消息的字节数组。
  • properties:指定消息的属性,如消息类型、优先级、TTL 等。

其中,exchange 和 routing_key 参数用于指定消息的路由信息,body 参数用于指定要发布的消息内容,而 properties 参数用于指定消息的其他属性。

queueDeclare()

channel.queueDeclare() 是用于声明一个队列(queue)的状态。该方法的参数包括:

  1. queueName:队列的名称。注意:同名称的队列只能用同样的参数创建一次

  2. durable:一个布尔值,表示队列是否持久化。如果为true,那么这个队列会保留在服务器关闭或重启后,不会丢失队列中的消息。

  3. exclusive:一个布尔值,表示这个队列是否是唯一的。如果为true,那么这个队列只属于当前连接的客户端。

  4. autoDelete:一个布尔值,表示当队列不再被使用时,是否自动删除。如果为true,那么当队列不再被使用时,服务器会自动删除它。

  5. arguments:一个对象,可以包含一些额外的参数。

这些参数可以帮助你控制队列的属性,如持久化、唯一性、自动删除等。这些属性可以根据你的需求进行调整,以适应不同的场景。

basicConsume ()

 RabbitMQ 中,basicConsume 方法用于从指定的队列中消费消息。该方法的参数如下:

  • String queueName:这是要消费消息的队列的名称。
  • boolean autoAck:如果为 true,消息将自动确认并被消费。如果为 false,则需要在调用 basicConsume 方法时显式确认消息。
  • Consumer callback:这是一个回调函数,用于处理接收到的消息。
  • Array args:这是一个可选参数数组,可以用于传递额外的参数。

其中,queueName 参数指定要消费消息的队列的名称,autoAck 参数决定是否在接收消息时自动确认消息,callback 参数是一个回调函数,用于处理接收到的消息,而 args 参数是一个可选的参数数组,可以用于传递额外的参数。这些参数共同决定了如何从队列中消费消息。

 -------------------------------------------------生产者代码--------------------------------------------------------------

public class SingleSend {
    //声明队列的名称
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {

        //创建连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             //channel是操作消息队列的client
             Channel channel = connection.createChannel()) {


            //声明队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            String message = "Hello World!";
            //发布消息
            channel.basicPublish("", QUEUE_NAME, null, 
                                 message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

-------------------------------------------------消费者代码--------------------------------------------------------------

public class SingleRecv {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        //创建连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //创建队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        //定义如何处理消息
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        //消费消息,持续堵塞
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

生产者向“hello”的QUEUE队列发送消息,消费者从“hello”的QUEUE队列取消息

监控面板auto ack有峰值,消费者成功取出消息并确认(basicConsume ()的boolean autoAck参数为true,消息自动确认并消费)了!

2. 多消费者

队列持久化

参数durable:true

channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);

消息持久化

MessageProperties.PERSISTENT_TEXT_PLAIN 是 RabbitMQ 中消息属性的一部分,用于指定消息的持久化类型。当消息被设置为 PERSISTENT_TEXT_PLAIN 类型时,这意味着消息将被存储在磁盘上,即使在服务器关闭或重启后也不会丢失。这对于需要长期存储的消息非常有用,例如日志或事件记录。

参数BasicProperties:MessageProperties.PERSISTENT_TEXT_PLAIN

channel.basicPublish("", TASK_QUEUE_NAME,
        MessageProperties.PERSISTENT_TEXT_PLAIN,
        message.getBytes("UTF-8"));

basicQos()

channel.basicQos(int number)用于设置通道的 QoS(服务质量)。这个方法将指定的消息数量设置为通道的 QoS,意味着这个通道将生产并传输特定数量的消息

消息确认机制

为了保证消息被成功消费,rabbitmq提供了消息确认机制,当消费者成功接收到消息,要给反馈

  • ack:消费成功
  • nack:消费失败
  • reject:拒绝

消费者告诉rabbitmq服务器消费成功,服务器才会正确移除消息

  • 如果配置autoacktrue,则消费者接收到消息则会自动确认
  • 建议手动确认消息消费成功
  • channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> {
    });

①指定确定某条消息,第二个参数multiple批量确认

channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);

②指定消息接收失败,第二个参数multiple批量确认,第三个参数requeue是否丢弃该消息

channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false,false);

③指定拒绝某条消息,第二个参数requeue是否丢弃该消息

channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);

-------------------------------------------------生产者代码--------------------------------------------------------------

public class MultiProducer {

  private static final String TASK_QUEUE_NAME = "multi_queue";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    try (Connection connection = factory.newConnection();
         Channel channel = connection.createChannel()) {
        //开启队列持久化
        channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            //手动控制发消息的内容
            String message = scanner.nextLine();
            //开启消息持久化
            channel.basicPublish("", TASK_QUEUE_NAME,
                    MessageProperties.PERSISTENT_TEXT_PLAIN,
                    message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
  }

------------------------------------------------消费者代码--------------------------------------------------------------

public class MultiConsumer {

  private static final String TASK_QUEUE_NAME = "multi_queue";

  public static void main(String[] argv) throws Exception {
    //获取连接
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    final Connection connection = factory.newConnection();
    //声明队列 两个消费者
      for (int i = 0; i < 2; i++) {
          final Channel channel = connection.createChannel();
          channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
          System.out.println(" 编号:[*] Waiting for messages. To exit press CTRL+C");
          //控制单个消费者的处理任务挤压数
          channel.basicQos(1);

          //定义如何处理消息
          int finalI = i;

          DeliverCallback deliverCallback = (consumerTag, delivery) -> {
              String message = new String(delivery.getBody(), "UTF-8");

                  //处理工作
                  System.out.println(" [x] Received '" + "编号:" + finalI + ":" + message + "'");
                  channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                  System.out.println("[x] done");

          };
          //开启消费监听
          channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> {
          });
      }
  }


 控制台测试结果

 

3.交换机

 一个生产者给多个队列发消息

交换机(X)作用:提供消息转发功能

交换机-队列绑定规则

channel1.queueBind(queueName, EXCHANGE_NAME, "");
  • queueName:要绑定的队列名称。

  • exchangeName:要绑定的交换器名称。这个交换器必须已经存在并且具有绑定该队列的键。

  • routingKey:用于将消息路由到交换器的键。当有消息发送到绑定到该队列和交换器的路由键时,消息将被路由到队列中。

交换机类别

fanout,direct,topic,headers

Fanout交换机

特点:消息会被转发到所有绑定该交换机的队列

适用场景:发布订阅场景

 -----------------------------------------------生产者代码-----------------------------------------------------------------

生产者与交换机绑定

channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
  • exchange:要使用的交换器的名称。如果为空字符串,消息将发送到绑定到队列的默认交换器。
  • routingKey:消息的路由键,用于将消息路由到交换器。
  • properties:消息的属性,可以包含有关消息的元数据。这些属性可以包括消息的优先级、TTL(生存时间)等
  • body:消息的主体,包含要发送的实际数据。
public class FanoutProducer {

    private static final String EXCHANGE_NAME = "fanout_queue";

    public static void main(String[] argv) throws Exception {
        //创建连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            //交换机的声明-名称及类别
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNext()){
                String message = scanner.nextLine();
                //消息的发布及绑定交换机
                channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
                System.out.println(" [x] Sent '" + message + "'");
            }
        }
    }

}

  -----------------------------------------------消费者代码-----------------------------------------------------------------

public class FanoutConsumer {
    private static final String EXCHANGE_NAME = "fanout_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel1 = connection.createChannel();
        Channel channel2 = connection.createChannel();

        //声明交换机(名称及类别)
        channel1.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        //声明“xiaowang_queue”的消息队列
        String queueName = "xiaowang_queue";
        channel1.queueDeclare(queueName,true,false,false,null);
        //“xiaowang_queue”的消息队列与交换机绑定
        channel1.queueBind(queueName, EXCHANGE_NAME, "");

        //声明“xiaoli_queue”的消息队列
        String queueName2 = "xiaoli_queue";
        channel2.queueDeclare(queueName2,true,false,false,null);
        //“xiaoli_queue”的消息队列与交换机绑定
        channel2.queueBind(queueName2, EXCHANGE_NAME, "");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //“小王”消费者的处理逻辑
        DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [小王] Received '" + message + "'");
        };

        //“小李”消费者的处理逻辑
        DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [小李] Received '" + message + "'");
        };

        channel1.basicConsume(queueName, true, deliverCallback1, consumerTag -> { });
        channel2.basicConsume(queueName2, true, deliverCallback2, consumerTag -> { });
    }
}

注意:生产者和消费者要绑定同一个交换机

           先有队列,才可以绑定交换机

效果:生产者发送消息,消费者都可以接收到消息

Direct交换机 

交换机把xx消息发送给指定队列

routingKey:路由键,控制交换机把某类消息发送给指定队列

路由键绑定关系:完全匹配字符串

示例场景: 

channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));

routingKey交换机发送至指定队列的路由键

--------------------------------------------------生产者代码--------------------------------------------------------------- 

public class DirectProducer {

  private static final String EXCHANGE_NAME = "direct_queue";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    try (Connection connection = factory.newConnection();
         Channel channel = connection.createChannel()) {
        //声明direct类别交换机
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String userInput = scanner.nextLine();

            String[] strings = userInput.split(" ");
            if (strings.length < 1){
                continue;
            }
            String message = strings[0];
            String routingKey = strings[1];


            channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "',routingKey:" + routingKey);
        }

    }
  }

}

 --------------------------------------------------消费者代码---------------------------------------------------------------

public class DirectConsumer {

    private static final String EXCHANGE_NAME = "direct_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");


        String queueName1 = "xiaowang_direct_queue";
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueBind(queueName1, EXCHANGE_NAME, "xiaowang");

        String queueName2 = "xiaoli_direct_queue";
        channel.queueDeclare(queueName2, true, false, false, null);
        channel.queueBind(queueName2, EXCHANGE_NAME, "xiaoli");


        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [小王direct队列] Received '" +
                    delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
        DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [小李direct队列] Received '" +
                    delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
        channel.basicConsume(queueName1, true, deliverCallback1, consumerTag -> {
        });
        channel.basicConsume(queueName2, true, deliverCallback2, consumerTag -> {
        });
    }
}

 控制台测试:

 

 路由键与队列相匹配

 

若发送的消息与之匹配的路由键,情况如何?

 

---无消费者应答 

 

Topic交换机 

特点:消息根据模糊的路由键转发到相应队列

绑定规则:单词间“.”隔开

  • *:表示一个单词(*.apple则a.apple,b.apple则可以匹配)
  • #:表示0个或者多个单词(#.apple则a.b.apple,apple则可以匹配)--

 --案例 ---

-----------------------------------------------------生产者 ---------------------------------------------------------------- 

public class TopicProducer {

  private static final String EXCHANGE_NAME = "topic_exchange";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    try (Connection connection = factory.newConnection();
         Channel channel = connection.createChannel()) {

        channel.exchangeDeclare(EXCHANGE_NAME, "topic");

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String userInput = scanner.nextLine();

            String[] strings = userInput.split(" ");
            if (strings.length < 1){
                continue;
            }
            String message = strings[0];
            String routingKey = strings[1];

            channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "',routingKey:" + routingKey);
        }
    }
  }
}

 -----------------------------------------------------消费者 ---------------------------------------------------------------- 

public class TopicConsumer {

    private static final String EXCHANGE_NAME = "topic_exchange";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, "topic");

        String queueName1 = "front_topic_queue";
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueBind(queueName1, EXCHANGE_NAME, "#.前端.#");

        String queueName2 = "backend_topic_queue";
        channel.queueDeclare(queueName2, true, false, false, null);
        channel.queueBind(queueName2, EXCHANGE_NAME, "#.后端.#");

        String queueName3 = "product_topic_queue";
        channel.queueDeclare(queueName3, true, false, false, null);
        channel.queueBind(queueName3, EXCHANGE_NAME, "#.产品.#");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [前端] Received '" +
                    "'message:'" + message + "'," + "routingKey:" + delivery.getEnvelope().getRoutingKey() );
        };
        DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [后端] Received '" +
                     "'message:'" + message + "'," + "routingKey:" + delivery.getEnvelope().getRoutingKey() );
        };
        DeliverCallback deliverCallback3 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [产品] Received '" +
                    "'message:'" + message + "'," + "routingKey:" + delivery.getEnvelope().getRoutingKey() );
        };

        channel.basicConsume(queueName1, true, deliverCallback1, consumerTag -> {
        });
        channel.basicConsume(queueName2, true, deliverCallback2, consumerTag -> {
        });  channel.basicConsume(queueName3, true, deliverCallback3, consumerTag -> {
        });

    }
}

---控制台测试

 

Header交换机

 链接:RabbitMQ tutorial - Remote procedure call (RPC) — RabbitMQ

四.核心特性 

4.1消息过期机制

4.1.1队列中消息过期机制

官方文档:Time-To-Live and Expiration — RabbitMQ

给队列的每一个消息指定过期时间,消息在该时间内未被消费则过期

注意:RabbitMQ中设置队列过期时间的参数键(key)是固定的。对于声明式队列设置,可以使用x-message-ttl来设定消息的过期时间。

 //声明队列消息的过期时间
   Map<String,Object> args = new HashMap<>();
   args.put("x-message-ttl",5000);       
   channel.queueDeclare(QUEUE_NAME, false, false, false, args);

4.1.2单条消息过期机制

//单条消息的过期时间设定 
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                    .expiration("5000")
                    .build();
channel.basicPublish("", QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));

4.2消息确认机制

见上文 三 .快速入门  ---> 2.多消费者 --->消息确认机制

4.3死信队列

在RabbitMQ中,死信队列是一种特殊的队列,用于处理不能被正常处理的消息。当消息在队列中满足以下任一情况时,它会被转移到死信队列:

  • 消息被拒绝(basic.reject)或被 nack(basic.nack),并且 requeue = false
  • 消息的 TTL(Time to Live)过期
  • 队列达到最大长度

死信队列的用途主要是作为一个备用机制,以确保当消息在主队列中无法被正确处理时,它们能够被安全地存储并可供后续处理。

如果配置了死信队列,当消息在主队列中变成死信后,它会被重新publish到与主队列关联的死信交换机(Dead Letter Exchange,简称DLX)。死信交换机将死信投递到一个队列上,这个队列就是死信队列。

通过使用死信队列和死信交换机,可以实现消息的可靠传递和处理,提高系统的健壮性和容错能力。

给失败后容错的消息 绑定 死信交换机

 Map<String, Object> args1 = new HashMap<String, Object>()
  //指定死信队列绑定到哪一个死信交换机
 args1.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
  //指定死信队列绑定到哪一个死信队列
 args1.put("x-dead-letter-routing-key","boss");


        String queueName1 = "xiaowang_work_direct_queue";
        channel.queueDeclare(queueName1, true, false, false, args1);
        channel.queueBind(queueName1, WORK_EXCHANGE_NAME, "xiaowang");

---案例---

---生产者代码 

public class DeadLetterDirectProducer {

    private static final String WORK_EXCHANGE_NAME = "work_direct_queue";
    private static final String DEAD_EXCHANGE_NAME = "dead_direct_queue";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    try (Connection connection = factory.newConnection();
         Channel channel = connection.createChannel()) {

        channel.exchangeDeclare(WORK_EXCHANGE_NAME, "direct");
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, "direct");

        //创建死信队列
        String queueName1 = "boss_dead_direct_queue";
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueBind(queueName1, DEAD_EXCHANGE_NAME, "boss");

        String queueName2 = "waibao_dead_direct_queue";
        channel.queueDeclare(queueName2, true, false, false, null);
        channel.queueBind(queueName2, DEAD_EXCHANGE_NAME, "waibao");

        DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [boss老板] Received '" + ",RoutingKey:" +
                    delivery.getEnvelope().getRoutingKey() + "',message'" + message + "'");
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [waibao外包] Received '" + ",RoutingKey:" +
                    delivery.getEnvelope().getRoutingKey() + "',message'" + message + "'");
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        channel.basicConsume(queueName1, false, deliverCallback1, consumerTag -> {
        });
        channel.basicConsume(queueName2, false, deliverCallback2, consumerTag -> {
        });

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String userInput = scanner.nextLine();

            String[] strings = userInput.split(" ");
            if (strings.length < 1){
                continue;
            }
            String message = strings[0];
            String routingKey = strings[1];

            channel.basicPublish(WORK_EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "',routingKey:" + routingKey);
        }

    }
  }

}

---消费者代码 

public class DeadLetterDirectConsumer {

    private static final String WORK_EXCHANGE_NAME = "work_direct_queue";
    private static final String DEAD_EXCHANGE_NAME = "dead_direct_queue";


    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(WORK_EXCHANGE_NAME, "direct");
        //声明死信交换机
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, "direct");

        Map<String, Object> args1 = new HashMap<String, Object>();
        //指定死信队列绑定到哪一个死信交换机
        args1.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
        //指定死信队列绑定到哪一个死信队列
        args1.put("x-dead-letter-routing-key","boss");

        String queueName1 = "xiaowang_work_direct_queue";
        channel.queueDeclare(queueName1, true, false, false, args1);
        channel.queueBind(queueName1, WORK_EXCHANGE_NAME, "xiaowang");


        Map<String, Object> args2 = new HashMap<String, Object>();
        args2.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
        args2.put("x-dead-letter-routing-key","waibao");

        String queueName2 = "xiaoli_direct_work_queue";
        channel.queueDeclare(queueName2, true, false, false, args2);
        channel.queueBind(queueName2, WORK_EXCHANGE_NAME, "xiaoli");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            //拒绝消息
            channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,false);
            System.out.println(" [小王direct队列] Received '" +
                    delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
        DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            //拒绝消息
            channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,false);
            System.out.println(" [小李direct队列] Received '" +
                    delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
        channel.basicConsume(queueName1, false, deliverCallback1, consumerTag -> {
        });
        channel.basicConsume(queueName2, false, deliverCallback2, consumerTag -> {
        });
    }
}

 交换机绑定关系

死信交换机绑定关系 

 

---测试 

 

 

五.项目中使用rabbitmq

  • 使用官方客户端
  • 使用springboot整合的框架 Spring Boot RabbitMQ Start

 Spring Boot RabbitMQ Start

5.1引入依赖

注意:Spring Boot RabbitMQ Start 版本 要和spring boot 版本一致

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

5.2配置RabbitMQ

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

5.3 创建交换机和队列

项目启动前创建一次即可!

public class InitQueueMain {
    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            String EXCHANGE_NAME = "code_exchange";
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");

            String QUEUE_NAME = "code_queue";
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"myRoutingKey");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

5.4生产者代码

@Configuration
@Slf4j
public class MyMessageProducer {

    @Resource
    private RabbitTemplate rabbitTemplate;

    void sendMessage(String exchange_name,String routing_key,String message){
        rabbitTemplate.convertAndSend(exchange_name,routing_key,message);
    }
}

 5.5消费者代码

@Component
@Slf4j
public class MyMessageConsumer {

    @SneakyThrows
    @RabbitListener(queues = {"code_queue"},ackMode = "MANUAL")
    public void receiveMessage(String message, Channel channel ,@Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag){
        log.info("receiveMessage,message:" + message);
        channel.basicAck(deliveryTag,false);
    }
}

5.6单元测试执行 

@SpringBootTest
class MyMessageProducerTest {

    @Resource
    private MyMessageProducer myMessageProducer;

    @Test
    void sendMessage() {
        myMessageProducer.sendMessage("code_exchange","myRoutingKey","你好,springboot客户端整合rabbitmq的test");
    }
}

结果如下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值