RabbitMQ study notes (message release confirmation, dead letter queue, cluster, switch, persistence, producer, consumer)

1. Basic introduction to MQ

MQ (message queue): It is essentially a queue that follows the FIFO principle. The queue stores messages. It is a cross-process communication mechanism used to transmit messages upstream and downstream. MQ provides "logical decoupling + physical decoupling" message communication services. After using MQ, message sending upstream only needs to rely on MQ and does not need to rely on other services.

Function 1: Traffic peak reduction

Function 2: Application decoupling

Function 3: Asynchronous processing

Classification of MQ:

1.Kafka

2.RabbitMQ

RabbitMQ concepts:

Four core concepts:

switch:

Queue: 

 Six core modes:

1. Simple mode. 2. Working mode. 3. Publish and subscribe model. 4. Routing mode. 5. Theme mode. 6. Release confirmation mode.

How RabbitMQ works:

Channel: Channel, a channel for sending messages.

2. MQ download and installation

download:

1. Official website address: https://www.rabbitmq.com/download.html. The reference download address is as follows:Installing RabbitMQ under Linux_rabbitmq download_fragmentary memory blog-CSDN blog

2. Install the Erlang environment

yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz tcp_wrappers

3. Download Erlang, method 1: Find the following URL and download the rpm file in the URL:

el/7/erlang-22.3.4.12-1.el7.x86_64.rpm - rabbitmq/erlang · packagecloud

Or directly enter the following command to download the rpm file: 

wget --content-disposition https://packagecloud.io/rabbitmq/erlang/packages/el/7/erlang-22.3.4.12-1.el7.x86_64.rpm/download.rpm

Then enter the following command to install the downloaded installation package:

yum localinstall erlang-22.3.4.12-1.el7.x86_64.rpm

4. Download RabbitMQ and enter the download below

wget --content-disposition https://packagecloud.io/rabbitmq/rabbitmq-server/packages/el/7/rabbitmq-server-3.8.13-1.el7.noarch.rpm/download.rpm

 Enter the following command to install locally:

yum localinstall rabbitmq-server-3.8.13-1.el7.noarch.rpm

5. Download socat and check whether it has been downloaded:

yum install socat -y

Note that the following operations must be viewed in the /usr/local/software directory: 

6. Add to start the RabbitMQ service at boot: chkconfig rabbitmq-server on. Start rabbitmq /sbin/service rabbitmq-server start.

7. Check the service status /sbin/service rabbitmq-server status

8. Stop the service /sbin/service rabbitmq-server stop. Check the service status again.

10. Open the web management interface sudo rabbitmq-plugins enable rabbitmq_management

11.View the firewall status: systemctl status firewalld. Turn off the firewall: systemctl stop firewalld. Close the rabbit server input: sudo rabbitmqctl stop. To open the rabbit server, enter: sudo rabbitmq-server -detached.

12. Enter the address in the browser: Linux server IP address: 15672 to access the web management interface.

13. The username is guest and the password is default, but you cannot log in and have no permissions.

14.rabbitmqctl list_users view users. Create an account rabbitmqctl add_user admin 123. Set the user role to administrator rabbitmqctl set_user_tags admin administrator. Set user permissions rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*".

15. You can log in again after trying again:

3. Create a Java development environment

1. Create a new project, name it atguigu-rabbitmq, and then create the module Module. GroupId can be filled in: com.atguigu.rabbitmq, ArtifactId can be filled in rabbitmq-hello, select quickstart:

Import dependencies as follows:

  <dependencies>
    <!--rabbitmq依赖客户端-->
    <dependency>
      <groupId>com.rabbitmq</groupId>
      <artifactId>amqp-client</artifactId>
      <version>5.8.0</version>
    </dependency>
    <!--操作文件流的依赖-->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
    </dependency>
      <dependency>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version> <!-- 根据你的需求指定版本号 -->
      </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

In the figure below, P is the producer and C is the consumer. The middle box is a queue - a buffer of messages retained by RabbitMQ on behalf of the consumer.

producer code

public class producer {
    //队列名称
    public static final String QUEUE_NAME = "hello";
    //发消息
    public static void main( String[] args ) throws IOException, TimeoutException {
        //第1步:创建一个连接工程
        ConnectionFactory factory = new ConnectionFactory();
        //第2步:输入工厂IP,用户名和密码——连接RabbitMQd队列
        factory.setHost("192.168.182.136");
        factory.setUsername("admin");
        factory.setPassword("123");
        //第3步:创建连接
        Connection connection = factory.newConnection();
        //第4步:获取信道
        Channel channel = connection.createChannel();
        //第5步:生成一个队列(队列名称,是否持久化,是否排他,自动删除,队列参数)
        //持久化:是否存储入磁盘,默认是将消息存储在内存中
        //排他:队列是否只供一个消费者消费,是否进行消息共享,true可以供多个消费者消费
        //自动删除:最后一个消费者断开连接后,该队列是否自动删除
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //第6步:发消息,(交换机,路由key本次是队列名,参数,发送的消息)
        String message = "hello world";
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        System.out.println("消息发送成功!!!");
    }
}

consumer code

public class consumer {
    public static final String QUEUE_NAME = "hello";

    public static void main(String [] args) throws IOException, TimeoutException {
    //第1步:创建一个连接工程
    ConnectionFactory factory = new ConnectionFactory();
    //第2步:输入工厂IP,用户名和密码——连接RabbitMQd队列
        factory.setHost("192.168.182.136");
        factory.setUsername("admin");
        factory.setPassword("123");
    //第3步:创建连接
    Connection connection = factory.newConnection();
    //第4步:获取信道
    Channel channel = connection.createChannel();
    //第5步:声明,接收消息
    DeliverCallback deliverCallback = (consumerTag,message)->{
        System.out.println(new String(message.getBody()));
    };
    //第6步:取消消息时的回调
        CancelCallback cancelCallback = consumerTag->{
            System.out.println("消息消费被中断");
        };
    //第7步:接收,(队列名,自动or手动,接收消息,回调)
    //1.消费哪个队列;2.消费成功后是否要自动应答true代表自动应答,false表示手动应答
    //3.消费者未成功消费的回调
    channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);

    }
}

Note a few points: 1. Make sure rabbitmq is turned on (see above for how to turn it on) 2. It is best to turn off the firewall 3. It is best to start it through the switch button on the left side of the method, and make sure to select Current File for startup.

 

4. Work queue

Work queue: Also known as task queue, the main idea is to avoid executing resource-intensive tasks immediately and having to wait for it to complete. Instead we schedule the task to be executed later. We encapsulate the task as a message and send it to the queue. A worker process running in the background will pop out the tasks and eventually execute the job. When there are multiple worker threads, these worker threads will process these tasks together.

Situation: The producer distributes a large number of messages to the queue, the worker thread receives the message from the queue, and there is more than one worker thread. The relationship between the three is a competitive relationship. One for you, one for me, and one for him. However, it should be noted that a message can only be processed once and cannot be processed. repeatedly.

Repetitive code can be extracted into utility classes.

Create a utils package under java — com — atguigu — rabbitmq, name the tool class RabbitMqUtils, and put the following code:

public class RabbitMqUtils {
    public static Channel getChannel() throws Exception{
        //第1步:创建一个连接工程
        ConnectionFactory factory = new ConnectionFactory();
        //第2步:输入工厂IP,用户名和密码——连接RabbitMQd队列
        factory.setHost("192.168.182.137");
        factory.setUsername("admin");
        factory.setPassword("123");
        //第3步:创建连接
        Connection connection = factory.newConnection();
        //第4步:获取信道
        Channel channel =  connection.createChannel();
        return channel;
    }
}

After the update of the worker thread, the code of worker01 is as follows:

public static final String QUEUE_NAME = "hello";
    public static void main(String [] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);//声明队列,没有会报错
        //消息接收
        DeliverCallback deliverCallback = (consumerTag,message)->{
            System.out.println("接收到的消息:" + new String(message.getBody()));
        };
        CancelCallback cancelCallback = (consumerTag)->{
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };
        System.out.println("c1等待接收消息......");
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }

Reuse the consumer class under the one package and change it to a c2 worker thread:

Task01 is used as a producer to produce data. Different from the previous one, Task01 supports inputting data from the IDEA console:

public class Task01 {
    public static final String QUEUE_NAME="hello";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //从控制台当中接收信息
        Scanner scanner = new Scanner(System.in); //扫描控制台输入内容
        while(scanner.hasNext()){
            String message = scanner.next();
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("发送消息完成..");
        }
    }
}

  

5. Message response

concept:

Automatic answer:

Manual answer:

Advantages of manual response, it is recommended not to respond in batches, select false:

 Messages are automatically requeued:

Originally transmitting normally, C1 suddenly lost connection and detected that C1 was disconnected, so the message was re-enqueued and the original message was handed over to C2 for processing.

Experimental idea: Write 1 producer and 2 consumers. When one of the working threads is closed, the message is not lost and is received by the other working thread. Consumption will not be lost during manual response and will be put back into the queue for consumption again.

Message manually answered (producer):

public class Task2 {
public static final String task_queue_name = "ack_queue";
public static void main(String[] args) throws Exception {
    Channel channel = RabbitMqUtils.getChannel();
    channel.queueDeclare(task_queue_name,false,false,false,null);
    Scanner scanner = new Scanner(System.in);
    while(scanner.hasNext()){
        String message = scanner.next();
        channel.basicPublish("",task_queue_name,null,message.getBytes("UTF-8"));
        System.out.println("生产者发出消息:"+message);
    }
}
}

Message manual response (consumer):

public class Work03 {
    public static final String task_queue_name = "ack_queue";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        System.out.println("C1等待接收消息处理时间较短");
        DeliverCallback deliverCallback = (consumerTag,message)->{
            SleepUtils.sleep(1);
            System.out.println("接收到的消息:"+new String(message.getBody(),"UTF-8"));
            //1.消息的标记tag 2.是否批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //采用手动应答
        boolean autoAck = false;
        channel.basicConsume(task_queue_name,autoAck,deliverCallback,(consumerTag->{
            System.out.println(consumerTag + "消费者取消消费接口回调逻辑");
        }));
    }
}
public class Work04 {
    public static final String task_queue_name = "ack_queue";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        System.out.println("C2等待接收消息处理时间较短");
        DeliverCallback deliverCallback = (consumerTag,message)->{
            SleepUtils.sleep(30);
            System.out.println("接收到的消息:"+new String(message.getBody(),"UTF-8"));
            //1.消息的标记tag 2.是否批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //采用手动应答
        boolean autoAck = false;
        channel.basicConsume(task_queue_name,autoAck,deliverCallback,(consumerTag->{
            System.out.println(consumerTag + "消费者取消消费接口回调逻辑");
        }));
    }
}

Implementation effect: When the producer inputs messages such as AA BB CC DD EE, Consumer 1 receives the message quickly and will print AA CC EE messages immediately. Consumer 2 receives the message slowly and will receive BB after a period of time. At this time, if When consumer 2 is closed, consumer 1 outputs DD, indicating that consumption is not lost during manual response and is put back in the queue for consumption again.

6. Persistence and Distribution

If an error is reported, it means that the original queue is not persistent. At this time, persistence cannot be set. The queue can only be deleted first and then reset.

To control queue persistence, you need to modify the second parameter of the producer declaration function:

Message persistence:

Queue persistence is different from message persistence. The queue is a component in MQ, and the message is the message sent by the producer.

If you want the message to be persistent, you must notify the queue when sending the message.

What is changed is the third parameter of basicPublish of the producer channel, adding MessageProperties.PERSISTENT_TEXT_PLAIN

Unfair distribution: 

The speed of consumers processing tasks is inconsistent. In order to prevent fast consumers from being idle for a long time, unfair distribution is adopted.

int prefetchCount = 1;
channel.basicQos(prefetchCount);

Prefetch value:

Who will handle the first N pieces of data? As shown below, among the first 7 pieces of data, 2 pieces are given to C1 and 5 pieces are given to C2.

7. Release confirmation

principle:

1. The setting requires that the queue must be persistent: even if the server goes down, the queue will not disappear.

2. The setting requires that messages in the queue must also be persisted.

3. After publishing the confirmation and saving the message to the disk, the queue must notify the producer.

Channel channel = connection.createChannel();
channel.confirmSelect();

public static void main(String[] args){
}

Single release confirmation:

It is a way of synchronously confirming the release. Publish the message-confirm the message-publish the message...it must be confirmed before continuing to publish. It is similar to paying one hand and delivering the goods. The disadvantage is that the publishing speed is very slow.

1. Create ConfirmMessage under the com/atguigu/rabbitmq/four folder

public static void publishMessageIndividually() throws Exception{
        Channel channel = RabbitMqUtils.getChannel(); //获取信道
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,false,false,false,null);
        channel.confirmSelect();//开启发布确认
        long begin = System.currentTimeMillis();
        for(int i=0;i<MESSAGE_COUNT;i++){
            String message = i +"";
            channel.basicPublish("",queueName,null,message.getBytes());
            boolean flag = channel.waitForConfirms();
            if(flag){
                System.out.println("消息发送成功");
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("发布"+MESSAGE_COUNT+"个单独确认消息,耗时:"+(end-begin)+"ms");
    }

Batch release confirmation:

public static void publishMessageBatch() throws Exception{
        Channel channel = RabbitMqUtils.getChannel(); //获取信道
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,false,false,false,null);
        channel.confirmSelect();//开启发布确认
        long begin = System.currentTimeMillis();
        int batchSize = 100; //批量确认消息的大小
        //批量发送消息,批量发布确认
        for(int i=0;i<MESSAGE_COUNT;i++){
            String message = i+"";
            channel.basicPublish("",queueName,null,message.getBytes());
            //判断达到100条消息的时候,批量确认一次
            if(i%batchSize==0) channel.waitForConfirms();
        }

        long end = System.currentTimeMillis();
        System.out.println("发布"+MESSAGE_COUNT+"个批量确认消息,耗时:"+(end-begin)+"ms");
}

Asynchronous release confirmation:

map sequence, key is the message sequence number (deliveryTag is the identifier of the message, multiple means whether it is in batches), value is the message content, number each message, and the broker will respond to the message. There are two types: one is the confirmation response. The other is an unacknowledged response. The message producer does not need to wait for the message from the receiver, it only needs to be responsible for sending the message. Whether the message is responded to will eventually be returned asynchronously, which means that the confirmation time can be later.

The single-parameter addConfirmListener can only listen successfully, and the multi-parameter one can listen successfully or fail. All interfaces need to be written by yourself.

public static void publishMessageAsync() throws Exception{
    Channel channel = RabbitMqUtils.getChannel(); //获取信道
    String queueName = UUID.randomUUID().toString();
    channel.queueDeclare(queueName,false,false,false,null);
    channel.confirmSelect();//开启发布确认
    long begin = System.currentTimeMillis();
    //消息确认成功,回调函数
    ConfirmCallback ackCallback = (deliveryTag, multiple)->{
        System.out.println("确认的消息:"+deliveryTag);
    };
    //消息确认失败回调函数
    ConfirmCallback nackCallback = (deliveryTag, multiple)->{
        System.out.println("未确认的消息:"+deliveryTag);
    };
    //准备消息的监听器,监听哪些消息成功了,哪些消息失败了
    channel.addConfirmListener(ackCallback,nackCallback);
    //批量发送消息
    for(int i=0;i<MESSAGE_COUNT;i++){
        String message="消息"+i;
        channel.basicPublish("",queueName,null,message.getBytes());
        //发布确认
    }
    long end = System.currentTimeMillis();
    System.out.println("发布"+MESSAGE_COUNT+"个异步确认消息,耗时:"+(end-begin)+"ms");
}

 Handling asynchronous unconfirmed messages:

The best solution is to put unconfirmed messages in a memory-based queue that can be accessed by the publishing thread. For example, use the ConcurrentLinkedQueue queue to transfer messages between confirm callbacks and the publishing thread.

public static void publishMessageAsync() throws Exception{
    Channel channel = RabbitMqUtils.getChannel(); //获取信道
    String queueName = UUID.randomUUID().toString();
    channel.queueDeclare(queueName,false,false,false,null);
    channel.confirmSelect();//开启发布确认
    /*线程安全有序的一个哈希表,适用于高并发的情况下
    1.轻松地将序号与消息进行关联
    2.轻松地批量删除条目只要给到序号
    3.支持高并发(多线程)*/
    ConcurrentSkipListMap<Long,String> outstandingConfirms = new ConcurrentSkipListMap<>();
    //消息确认成功,回调函数
    ConfirmCallback ackCallback = (deliveryTag, multiple)->{
        if(multiple){
            //2.删除掉已经确认的消息,剩下的就是未确认的消息
            ConcurrentNavigableMap<Long, String> confirmd =
                    outstandingConfirms.headMap(deliveryTag);
        }else{
            outstandingConfirms.remove(deliveryTag);
        }

        System.out.println("确认的消息:"+deliveryTag);
    };
    //消息确认失败回调函数
    ConfirmCallback nackCallback = (deliveryTag, multiple)->{
        //3.打印一下未确认的消息都有哪些
        String message = outstandingConfirms.get(deliveryTag);
        System.out.println("未确认的消息是:"+message+"未确认的消息:"+deliveryTag);
    };
    //准备消息的监听器,监听哪些消息成功了,哪些消息失败了
    channel.addConfirmListener(ackCallback,nackCallback);
    long begin = System.currentTimeMillis();
    //批量发送消息
    for(int i=0;i<MESSAGE_COUNT;i++){
        String message="消息"+i;
        channel.basicPublish("",queueName,null,message.getBytes());
        //1.此处记录下所有发送的消息,消息的总和
        outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
    }
    long end = System.currentTimeMillis();
    System.out.println("发布"+MESSAGE_COUNT+"个异步确认消息,耗时:"+(end-begin)+"ms");
    }
}

Comparison of three methods:

8. Switch

A message can be consumed multiple times and needs to pass through the switch. It still follows that the message in the queue can only be consumed once.

 Messages produced by producers are never sent directly to the queue. Producers send messages to the exchange. The exchange is responsible for receiving messages from producers and pushing messages into queues.

Types of Exchanges: direct, topic, headers, fanout

The routing of messages to the queue is actually specified by the routingKey (bindingkey) binding key.

Create a temporary queue:

String queueName = channel.queueDedare().getQueue();

Binding:

Determine which queue the message is to be sent to based on the Routing key. If the Routing Key is the same, the message can be sent to multiple queues.

First add a queue queue1, then add a switch exchange1, and finally click the exchange1 switch to enter the binding menu, then enter the bound queue as queue1, and then set the Routing key to 123.

Fanout switch (broadcast)

Fanout broadcasts all messages received to all queues it knows about. If the Routing Key is the same, the same message is sent to the queue.

Producer:

public class EmitLog {
    public static final String EXCHANGE_NAME="logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String message = scanner.next();
            channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息"+message);
        }
    }
}

consumer:

public class ReceiveLogs01 {
    public static final String EXCHANGE_NAME="logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");//声明一个交换机
        //声明一个队列临时队列,队列的名称是随机的,当消费者断开与队列的连接时候,队列就删除了
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机与队列
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        System.out.println("等待接收消息,把接收到消息打印在屏幕上......");
        DeliverCallback deliverCallback = (consumerTag,message)->{
            System.out.println("ReceiveLogs01控制台打印接收到的消息:"+new String(message.getBody(),"UTF-8"));
        };
        channel.basicConsume(queueName,true,deliverCallback,consumerTag->{});
    }
}

Effect: Realize the function of broadcasting

Direct routing switch

Consumer 1:

public class ReceiveLogsDirect01 {
    public static final String EXCHANGE_NAME="direct_logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.queueDeclare("console",false,false,false,null);
        channel.queueBind("console",EXCHANGE_NAME,"info"); //队列名称,交换机名称,Routingkey
        DeliverCallback deliverCallback =(consumerTag,message)->{
            System.out.println("ReceiveLogsDirect01控制台打印接收到的消息:"+new String(message.getBody(),"UTF-8"));
        };
        channel.basicConsume("console",true,deliverCallback,consumerTag->{});
    }
}

Consumer 2:

public class ReceiveLogsDirect02 {
    public static final String EXCHANGE_NAME="direct_logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.queueDeclare("disk",false,false,false,null);
        channel.queueBind("disk",EXCHANGE_NAME,"error"); //队列名称,交换机名称,Routingkey
        DeliverCallback deliverCallback =(consumerTag,message)->{
            System.out.println("ReceiveLogsDirect02控制台打印接收到的消息:"+new String(message.getBody(),"UTF-8"));
        };
        channel.basicConsume("disk",true,deliverCallback,consumerTag->{});
    }
}

Producer: 

public class DirectLogs {
    public static final String EXCHANGE_NAME="direct_logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String message = scanner.next();
            channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息"+message);
        }
    }
}

Effect:

If the second parameter of [channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes("UTF-8"));] is filled in with info, it will only send the message to consumer 1, and if filled with error, it will only A message will be sent to consumer 2.

Topics theme switch

Publish (producer) subscribe (consumer) pattern:

Consumer 1:

public class ReceiveLogsTopic01 {
    public static final String EXCHANGE_NAME="topic_logs";//交换机名称
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"topic");
        String queueName="Q1";
        channel.queueDeclare(queueName,false,false,false,null);
        channel.queueBind(queueName,EXCHANGE_NAME,"*.orange.*");
        System.out.println("等待接收消息.....");
        DeliverCallback deliverCallback = (consumerTag,message)->{
            System.out.println(new String(message.getBody(),"UTF-8"));
            System.out.println("接收队列:"+queueName+" 绑定键:"+message.getEnvelope().getRoutingKey());
        };
        channel.basicConsume(queueName,true,deliverCallback,consumerTag->{});
    }
}

Consumer 2:

public class ReceiveLogsTopic02 {
    public static final String EXCHANGE_NAME="topic_logs";//交换机名称
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"topic");
        String queueName="Q2";
        channel.queueDeclare(queueName,false,false,false,null);
        channel.queueBind(queueName,EXCHANGE_NAME,"*.*.rabbit");
        channel.queueBind(queueName,EXCHANGE_NAME,"lazy.#");
        System.out.println("等待接收消息.....");
        DeliverCallback deliverCallback = (consumerTag,message)->{
            System.out.println(new String(message.getBody(),"UTF-8"));
            System.out.println("接收队列:"+queueName+" 绑定键:"+message.getEnvelope().getRoutingKey());
        };
        channel.basicConsume(queueName,true,deliverCallback,consumerTag->{});
    }
}

 Producer 1:

public class EmitLogTopic {
    public static final String EXCHANGE_NAME="topic_logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        Map<String,String> bindingKeyMap = new HashMap<>();
        bindingKeyMap.put("quick.orange.rabbit","被队列Q1Q2接收到");
        bindingKeyMap.put("lazy.orange.elephant","被队列Q1Q2接收到");
        bindingKeyMap.put("quick.orange.fox","被队列Q1接收到");
        bindingKeyMap.put("lazy.brown.fox","被队列Q2接收到");
        bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列Q2接收一次");
        bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
        bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
        bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配Q2");
        for (Map.Entry<String, String> bindingKeyEntry : bindingKeyMap.entrySet()) {
            String routingKey = bindingKeyEntry.getKey();
            String message =  bindingKeyEntry.getValue();
            channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息:"+message);
        }
    }
}

result:

9. Dead letters

Messages that cannot be consumed are called dead letters

Source of dead letter:

Dead letter actual combat architecture diagram:

1 producer and 2 consumers. The producer originally went through the normal switch, and the message went through the normal queue and was consumed by C1. When the satisfied message is rejected, the message TTL expires, or the queue reaches the maximum length, the message becomes a dead letter and will enter the dead_exchange switch and the dead_queue dead letter queue. The information in the dead letter queue is consumed by C2.

Consumer 1:

public class Consumer01 {
    //普通交换机的名称
    public static final String NORMAL_EXCHANGE ="normal_exchange";
    //死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";
    //普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";
    public static void main(String[] args) throws Exception {
        Channel channel1 = RabbitMqUtils.getChannel();
        //声明死信和普通交换机,类型为direct
        channel1.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel1.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        Map<String,Object> arguments = new HashMap<>(); //设置参数
        //正常队列设置死信交换机
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE); //****相当于正常的C1不能消费掉就通过这个交换机进行转发
        //设置死信RoutingKey
        arguments.put("x-dead-letter-routing-key", "lisi");
        //声明普通队列
        channel1.queueDeclare(NORMAL_QUEUE,false,false,false,arguments); //正常交换机不正常,需要将死信转发给死信队列
        //声明死信队列
        channel1.queueDeclare(DEAD_QUEUE,false,false,false,null);
        //绑定普通的交换机与队列
        channel1.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
        //绑定死信的交换机与死信的队列
        channel1.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
        System.out.println("等待接收消息.....");
        DeliverCallback deliverCallback = (consumerTag,message)->{
            System.out.println("Consumer01接收的消息是:" + new String(message.getBody(),"UTF-8"));
        };
        channel1.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag->{});
    }
}

Consumer 2:

public class Consumer02 {
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";
    public static void main(String[] args) throws Exception {
        Channel channel1 = RabbitMqUtils.getChannel();
        System.out.println("等待接收消息.....");
        DeliverCallback deliverCallback = (consumerTag,message)->{
            System.out.println("Consumer02接收的消息是:" + new String(message.getBody(),"UTF-8"));
        };
        channel1.basicConsume(DEAD_QUEUE,true,deliverCallback,consumerTag->{});
    }
}

Producer:

public class Producer {
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        //死信消息,设置TTL时间,time to live
        AMQP.BasicProperties properties = new AMQP.BasicProperties()
                .builder().expiration("10000").build();
        for (int i = 1; i < 11; i++) {
            String message = "info"+i;
            channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
        }
    }
}

Experimental steps: First ensure that the normal_queue and dead_queue queues in the rabbitMq web page are deleted, then start Consumer01 to declare the switch and queue, then close Consumer01 to simulate the scenario of being unable to receive, then start the Producer and 10 messages will be sent. At this time, start Consumer02. The final message sent will become a dead letter after 10 seconds, and Consumer02 will receive the information.

**Error-prone point: If the queue already exists, an error will be reported. We can click into the queue.

Then click Delete Queue: 

Queue reaches maximum length:

Change Producer code:

Change Consumer01 code:

Experimental steps: First ensure that the normal_queue and dead_queue queues in the rabbitMq web page are deleted, then start Consumer01 to declare the switch and queue, then close Consumer01 to simulate suspended animation, and then start Consumer02 and Producer. Because the maximum capacity of the queue is 6, so there are 4 messages will be sent to the dead letter queue.

Refuse to answer:

In the Producer, make sure to set the TTL time annotation, and then change basicProperties to null.

Comment out the length limit in Consumer01 and change the code after DeliverCallback as follows:

 DeliverCallback deliverCallback = (consumerTag,message)->{ //接收消息
     String msg = new String(message.getBody(), "UTF-8");
     if(msg.equals("info5")){
         System.out.println("此消息是被Consumer01拒绝的消息是:" +msg);
         channel1.basicReject(message.getEnvelope().getDeliveryTag(),false);
      } else {
         System.out.println("Consumer01接收的消息是:" +msg);
         channel1.basicAck(message.getEnvelope().getDeliveryTag(),false);
      }
};
//开启手动应答,如果自动应答了根本不存在拒绝的问题
channel1.basicConsume(NORMAL_QUEUE,false,deliverCallback,consumerTag->{});

Note that manual response must be enabled and message info5 must be manually rejected. The effect is as follows:

Guess you like

Origin blog.csdn.net/RuanFun/article/details/133493479
Recommended