Several communication methods of RabbitMQ and their code examples

Insert image description here

I. Introduction

The coupling between modules is too high. Once a module goes down, all functions will be unavailable. Moreover, the cost of synchronous communication is too high and the user experience is poor.

Introduction to RabbitMQ
Insert image description here

2. Introduction to RabbitMQ


Several popular MQs on the market:

ActiveMQ,RocketMQ,Kafka,RabbitMQ。

  • Language support: ActiveMQ and RocketMQ only support Java language, Kafka can support multiple languages, and RabbitMQ supports multiple languages.

  • In terms of efficiency: ActiveMQ, RocketMQ, and Kafka are all at the millisecond level, while RabbitMQ is at the microsecond level.

  • Message loss and message duplication problems: RabbitMQ has relatively mature solutions for message persistence and duplication problems.

  • Learning cost: RabbitMQ is very simple.

RabbitMQ is developed and maintained by Rabbit Company, and finally in Pivotal, developed by Erlang language (concurrent programming language)

RabbitMQ strictly follows the AMQP protocol, the Advanced Message Queuing Protocol, to help us deliver asynchronous messages between processes.

RabbitMQ is a highly concurrent and highly reliable AMQP message queue server implemented in Erlang. Supporting message persistence, transactions, congestion control, load balancing and other features makes RabbitMQ have a wider range of application scenarios. RabbitMQ is related to Erlang and AMQP. Here is a brief introduction to Erlang and AMQP.

​ Erlang is a dynamically typed functional programming language. It is also an interpreted language, interpreted and executed by the Erlang virtual machine. From a language model perspective, Erlang is based on the implementation of the Actor model. In the Actor model, everything is an Actor, and each Actor encapsulates internal state. Actors can only communicate with each other through message passing. Corresponding to Erlang, each Actor corresponds to an Erlang process, and processes communicate through message passing. Compared with shared memory, the direct benefit of communicating between processes through message passing is the elimination of direct lock overhead (regardless of the lock application in the underlying implementation of the Erlang virtual machine).

AMQP (Advanced Message Queue Protocol) defines a messaging system specification. This specification describes how subsystems in a distributed system interact through messages. RabbitMQ is an erlang-based implementation of AMQP. AMQP isolates various subsystems in a distributed system, and there is no longer any dependence between subsystems. The subsystem relies only on messages. The subsystem does not care about the sender of the message, nor the recipient of the message.

advantage

1. Decoupling: Reduce the coupling of system modules

2. Improve system response time

3. Asynchronous messages

4. Overload protection, traffic peak clipping

1. Application decoupling

Scenario: During Double 11 shopping, after the user places an order, the order system needs to notify the inventory system. The traditional approach is for the order system to call the interface of the inventory system.

This approach has a drawback:

  • When the inventory system fails, orders fail.
  • The order system and inventory system are highly coupled.

Introduce message queue

  • Order system: After the user places an order, the order system completes persistence processing, writes the message to the message queue, and returns the user's order success.

  • Inventory system: Subscribe to order messages, obtain order messages, and perform library operations. Even if the inventory system fails, the message queue can ensure reliable delivery of messages without causing message loss.

Insert image description here

2. Asynchronous processing
Scenario description: After the user registers, he needs to send a registration email and registration SMS. There are two traditional methods: 1. Serial method 2. Parallel method

Serial mode: After writing the registration information into the database, send a registration email, and then send a registration text message. Only after the above three tasks are completed, will it be returned to the client. One problem with this is that the email or SMS is not necessary, it is just a notification, and this approach allows the client to wait for something that is not necessary.

Insert image description here

Parallel method: After writing the registration information into the database, sending an email and a text message at the same time. After the above three tasks are completed, it is returned to the client. The parallel method can improve the processing time.

Insert image description here

Assume that the three business nodes use 50ms each, the serial use time is 150ms, and the parallel use time is 100ms. Although the processing time has been improved, as mentioned before, emails and text messages have no impact on my normal use of the website. There is no need for the client to wait for the sending to complete before it shows that the registration is successful. Yingai is written to the database. return.

message queue

After the introduction of the message queue, the business logic that is not necessary for sending emails and text messages is processed asynchronously. After the introduction of the message queue, the user's response time is equal to the time of writing to the database + the time of writing to the message queue (which can be ignored). The introduction of the message queue After post-processing, the response time is 3 times that of serial and 2 times that of parallel

Insert image description here

3. Traffic peak clipping

Traffic peak shaving is generally widely used in flash sales activities.

Scenario: Flash sale activities usually cause the application to hang due to excessive traffic. In order to solve this problem, a message queue is usually added to the front end of the application.

Function:
1. Can control the number of people active, and orders exceeding this certain threshold will be discarded directly.

2. Can alleviate short-term high traffic overwhelming the application (the application obtains orders according to its maximum processing capacity)

Insert image description here

In this way, we can use the queue mechanism to process, just like when we settle in a supermarket, we will not swarm into the checkout counter, but queue up for settlement, one after another, and cannot jump in line, because simultaneous settlement can only achieve this many.

3. RabbitMQ installation

docker installation


version: "3.1"
services:
  rabbitmq:
    image: daocloud.io/library/rabbitmq:management
    restart: always
    container_name: rabbitmq
    ports:
      - 5672:5672
      - 15672:15672
    volumes:
      - ./data:/var/lib/rabbitmq
[root@192 ~]# cd /opt
[root@192 opt]# mkdir docker_rabbitmq
[root@192 opt]# cd docker_rabbitmq/
[root@192 docker_rabbitmq]# vim docker-compose.yml
[root@192 docker_rabbitmq]# docker-compose up -d
Creating network "docker_rabbitmq_default" with the default driver
Pulling rabbitmq (daocloud.io/library/rabbitmq:management)...
management: Pulling from library/rabbitmq
01bf7da0a88c: Pull complete
f3b4a5f15c7a: Pull complete
57ffbe87baa1: Pull complete
5ef3ef76b1b5: Pull complete
82a3ce07c0eb: Pull complete
1da219d9bd70: Pull complete
446554ac749d: Pull complete
8e4c09e200e7: Pull complete
7a8620611ebf: Pull complete
c70a2924b273: Pull complete
3b0b9e36b4e9: Pull complete
7619a9a42512: Pull complete
965a8e1f1b1c: Pull complete
Digest: sha256:4cc2267788b21e0f34523b4f2d9b32ee1c2867bf2de75d572158d6115349658c
Status: Downloaded newer image for daocloud.io/library/rabbitmq:management
Creating rabbitmq ... done

Browser access: http://ip:15672 (Note: ip refers to the address of the current cloud server. Remember to open ports 15672 and 5672 on the cloud server)

The default username and password are: guest

4. RabbitMQ architecture


4.1 Official simple architecture diagram
  • Publisher - Producer: publishes messages to Exchange in RabbitMQ

  • Consumer - Consumer: listens to messages in the Queue in RabbitMQ

  • Exchange - switch: establishes a connection with the producer and receives messages from the producer

  • Queue - Queue: Exchange will distribute messages to the specified Queue, and the Queue and consumers will interact

  • Routes - Routing: What strategy does the switch use to publish messages to the Queue?

Simple architecture diagram
Insert image description here
4.2 Complete architecture diagram of RabbitMQ

Complete architecture diagram

Complete architecture diagram
4.3 RabbitMQ communication method

https://www.rabbitmq.com/getstarted.html

4.4 Hello-World case demonstration
  1. Import dependencies
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.15.0</version>
</dependency>
  1. Create producer Publisher
package com.guo.rabbitmq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

//生产者
public class Publisher {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("Publisher...");
        //配置连接参数
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.25.132");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        //获取连接
        Connection connection = factory.newConnection();
        //获取Channel
        Channel channel = connection.createChannel();
        //配置队列参数
        //参数1:queue - 指定队列的名称
        //参数2:durable - 当前队列是否需要持久化,值为true时表示持久化,rabbitmq宕机或重启后,队列依然在
        //参数3:exclusive - 当前队列是否为排他队列,值为true时表示与当前连接(connection)绑定,连接关闭,队列消失,排他队列会对当前队列加锁,其他通道channel是不能访问的,否则会报异常
        //参数4:autoDelete - 当前队列是否自动删除,值为true时表示队列中的消息一旦被消费,该队列会消失
        //参数5:arguments - 指定当前队列的相关参数
        channel.queueDeclare("helloworldQueue",false,false,false,null);
        //发布消息到exchange,同时指定路由的规则
        // 参数1:指定exchange,目前测试没有创建交换机,使用""
        // 参数2:指定路由的规则,或者使用具体的队列名称
        // 参数3:指定传递的消息所携带的properties,目前测试不需要,使用null
        // 参数4:指定发布的具体消息,byte[]类型,目前测试需要,传递数据进行类型转换
        channel.basicPublish("","helloworldQueue",null,"helloworld".getBytes());
        //关闭资源
        channel.close();
        connection.close();
    }
}
  1. Create consumerConsumer
package com.guo.rabbitmq;

import com.rabbitmq.client.*;
import java.io.IOException;

//消费者
public class Consumer {
    
    
    public static void main(String[] args)throws Exception {
    
    
        System.out.println("Consumer...");
        //配置连接参数
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.25.132");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        //获取连接
        Connection connection = factory.newConnection();
        //获取Channel
        Channel channel = connection.createChannel();
        //监听队列
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
    
    

            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("来自生产者的消息:"+new String(body));
            }
        };

        //消费消息
        //参数1:queue - 指定消费哪个队列
        //参数2:autoAck - 指定是否自动ACK (true表示接收到消息后,会立即告知RabbitMQ,false表示不告知)
        //参数3:consumer - 指定消费回调
        channel.basicConsume("helloworldQueue",true,defaultConsumer);
        
        //设置延迟关闭,否则可能无法显示相关信息,消费方一般不关,目的是为了可以及时处理消息
        Thread.sleep(5000);
        
        //关闭资源
        channel.close();
        connection.close();
    }
}

Start the producer and consumer separately for testing (produce once before consuming once)

4.5 Basic principles

RabbitMQ is an implementation of message queue, so what exactly is needed for a message queue? The answer is queue, that is, Queue, then all the following nouns are expanded around this Queue .

​ As far as RabbitMQ is concerned, Queue is one of the logical implementations. We need to connect to RabbitMQ to operate the queue and implement business functions, so there will be Connection . We send a message to connect once, which is obviously a waste of resources. The process of establishing a connection is also very time-consuming, so we will make something to let it manage the connection. When I use it, I can directly take out the established connection and send the message, and the ConnectionFactory comes into being .

Next, when the program is developed, more than one queue may be used. There may be order queues, message queues, task queues, etc. Then you need to send messages to different queues, so the one connected to each queue The concept is called Channel .

​ Further down, when we develop, we sometimes use such a function, that is, when I send a message, it needs to be received by several queues. So how to solve this problem? Do I have to give each queue a message? Queue sends a message once? Wouldn't that be a waste of bandwidth and resources? What can we think of? Of course, we send it to the RabbitMQ server once, and then let the RabbitMQ server analyze which Queue it needs to send to. Then Exchange does this, but we send it to Exchange
. How does he know which Queue to send the message to? RoutingKey and BindingKey are used here
. BindingKey is a rule description for the binding of Exchange and Queue. This description is used to parse when Exchange receives a message. The message received by Exchange will have the RoutingKey field. Exchange uses this RoutingKey and the current Exchange All bound BindingKeys are matched. If the requirements are met, a message is sent to the Queue bound by the BindingKey. In this way, we solve the process of sending a message to RabbitMQ once and distributing it to different Queues.

At this point, we have put all the nouns through, and then give a summary description:

  • Broker: Message queue server entity. Its role is to maintain a route from the producer to the consumer to ensure that data can be transmitted in the specified way.
  • ConnectionFactory: Manager for connections to the RabbitMQ server.
  • Connection: TCP connection to the RabbitMQ server.
  • Channel: connection to Exchange. A Connection can contain multiple Channels. The reason why Channel is needed is because the establishment and release of TCP connections are very expensive for multiplexing. RabbitMQ recommends not to share Channels between client threads, but it is recommended to share Connections as much as possible.
  • Queue: The carrier of the message. Each message will be put into one or more queues.
  • Exchange: Accepts messages from the message producer and routes the messages to the queue in the server based on the RoutingKey of the message, the BindingKey bound to Exchange, and the Binding rules. ExchangeType determines the behavior of Exchange routing messages. For example, in RabbitMQ, ExchangeType has three types: direct, Fanout and Topic. The behavior of different types of Exchange routing is different.
  • Message Queue: Message queue, used to store messages that have not been consumed by consumers.
  • Message: consists of Header and Body. Header is a collection of various attributes added by the producer, including whether the Message is persisted, which Message Queue accepts it, what the priority is, etc. The Body is the APP data that really needs to be transmitted.
  • RoutingKey: Specified when the Producer sends a Message, specifying who accepts the current message.
  • BindingKey: Specified by the Consumer when Binding Exchange and Message Queue, specifying what kind of RoutingKey will be assigned to the currently bound Queue under the current Exchange.
  • Binding: Contacted Exchange and Message Queue. Exchange will generate a routing table after Binding with multiple Message Queues. The routing table stores the constraints of the messages required by the Message Queue, that is, the Binding Key. When Exchange receives the Message, it parses its Header to obtain the Routing Key. Exchange routes the Message to the Message Queue based on the Routing Key and Exchange Type . The Binding Key is specified by the Consumer when Binding Exchange and Message Queue, and the Routing Key is specified by the Producer when sending a Message. The matching method of the two is determined by the Exchange Type.
  • Server: A process that accepts client connections and implements AMQP message queue and routing functions.
  • Virtual Host: It is actually a virtual concept, similar to a permission control group. Virtual Host permissions can be assigned to users through commands. The default guest user has administrator permissions. The initial space is /. There can be several Exchange and Queue, but the minimum granularity of permission control is Virtual Host.

5. SpringBoot integrates the use of RabbitMQ


5.1 Import dependencies
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
5.2 Add configuration in application.properties
#对于rabbitMQ的支持
spring.rabbitmq.host=192.168.153.136
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
5.3 Hello-World simple queue

A producer, a default exchange, a queue, and a consumer

Structure diagram

1) Create a configuration class for creating queue objects

package com.guo.simple;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SimpleQueueConfig {
    
    

    @Bean
    public Queue simple(){
    
    
        return new Queue("simpleQueue");
    }
}

2) Create a producer

package com.guo.simple;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SimpleQueueProducer {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(){
    
    
        System.out.println("SimpleQueueProducer");
        //发送消息,第一个参数为队列名称,第二参数为消息内容
        rabbitTemplate.convertAndSend("simpleQueue","简单模式");
    }
}

3) Create consumers

package com.guo.simple;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class SimpleQueueCustomer {
    
    
    
    @RabbitListener(queues="simpleQueue")//监听指定的消息队列
    public void receive(String content){
    
    
        System.out.println("SimpleQueueCustomer");
        System.out.println("来SimpleQueueProducer的信息:"+content);
    }
}

4) Test in src\test\java\com\guo\Rabbitmq01ApplicationTests.java

package com.guo;
import com.guo.simple.SimpleQueueProducer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Rabbitmq01ApplicationTests {
    
    
    
    @Test
    void contextLoads() {
    
    
    }
    
    @Autowired
    private SimpleQueueProducer simpleQueueProducer;

    @Test
    public void testSimpleQueueProducer(){
    
    
        simpleQueueProducer.send();
    }
}

If a JavaBean object is passed, the entity class needs to implement the serialization interface. The specific process is as follows:

  1. Import lombok dependencies and create User class
package com.guo.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    
    
    private String username;
    private String password;
}
  1. Modify code in producer
package com.guo.simple;

import com.guo.pojo.User;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

//生产者
@Component
public class SimplePublisher {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(){
    
    
        System.out.println("SimplePublisher...");
        //rabbitTemplate.convertAndSend("","simpleQueue","简单模式");
        rabbitTemplate.convertAndSend("","simpleQueue",new User("张三","123"));
    }
}

  1. Modify the code in the consumer
package com.guo.simple;

import com.guo.pojo.User;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

//消费者
@Component
public class SimpleConsumer {
    
    

//    @RabbitListener(queues = "simpleQueue")
//    public void receive(String content){
    
    
//        System.out.println("SimpleConsumer...");
//        System.out.println("来自SimplePublisher的消息:"+content);
//    }

    @RabbitListener(queues = "simpleQueue")
    public void receive(User user){
    
    
        System.out.println("SimpleConsumer...");
        System.out.println("来自SimplePublisher的消息:"+user);
    }
}
  1. Just run the test class!
5.4 Work work queue

One producer, one default exchange, one queue, two consumers

Structure diagram
Insert image description here

1) Create a configuration class for creating queue objects

package com.guo.work;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WorkQueueConfig {
    
    
    
    @Bean
    public Queue work(){
    
    
        return new Queue("workQueue");
    }
}

2) Create a producer

package com.guo.work;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class WorkQueueProducer {
    
    
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void send(){
    
    
        System.out.println("WorkQueueProducer");
        rabbitTemplate.convertAndSend("workQueue","工作队列模式");
    }
}

3) Create consumers. In this case, two consumers are created.

package com.guo.work;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class WorkQueueCustomer_01 {
    
    
    
    @RabbitListener(queues="workQueue")
    public void receive(String content){
    
    
        System.out.println("WorkQueueCustomer_01:"+content);
    }
}
package com.guo.work;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class WorkQueueCustomer_02 {
    
    

    @RabbitListener(queues="workQueue")
    public void receive(String content){
    
    
        System.out.println("WorkQueueCustomer_02:"+content);
    }
}

4) Add objects and methods to the test class for testing

@Autowired
private WorkQueueProducer workQueueProducer;

@Test
public void testWorkQueueProducer(){
    
    

    for (int i = 0; i<100; i++){
    
    
        workQueueProducer.send();
    }
}
5.5 Publish/Subscribe publish and subscribe model

One producer, one exchange, two queues, two consumers

Structure diagram
Insert image description here

Using this mode requires the use of a switch, the producer sends the message to the switch, and then reaches the queue through the switch.

There are four switches: direct/topic/headers/fanout. The default switch is direct. The implementation of publishing and subscription uses the fourth switch type fanout.

When using a switch, each consumer has its own queue, the producer sends messages to the switch (X), and each queue is bound to the switch

In this example:

Create 2 message queues

Create a fanout switch object

Bind switches and queues

1) Create configuration class

package com.guo.fanout;

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;

@Configuration
public class FanoutConfig {
    
    

    //创建两个队列
    @Bean
    public Queue fanoutQueue1(){
    
    
        return new Queue("fanoutQueue1");
    }

    @Bean
    public Queue fanoutQueue2(){
    
    
        return new Queue("fanoutQueue2");
    }

    //创建一个交换机
    @Bean
    public FanoutExchange fanoutExchange(){
    
    
        return new FanoutExchange("fanoutExchange");
    }

    //将两个队列绑定到交换机上
    @Bean
    public Binding bindingFanoutQueue1(){
    
    
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
    }

    @Bean
    public Binding bindingFanoutQueue2(){
    
    
        return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
    }
}

2) Create a producer

package com.guo.fanout;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class FanoutProducer {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(){
    
    
        System.out.println("FanoutProducer");
        //第一个参数是交换机的名称 ,第二个参数是routerKey 这里设置为空字符串即可,第三个参数是要发送的消息
        rabbitTemplate.convertAndSend("fanoutExchange","","发布/订阅");
    }
}

3) Create consumers. In this case, two consumers are created.

package com.guo.fanout;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class FanoutCustomer_01 {
    
    

    @RabbitListener(queues = "fanoutQueue1")
    public void receive(String content){
    
    
        System.out.println("FanoutCustomer_01:"+content);
    }
}
package com.guo.fanout;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class FanoutCustomer_02 {
    
    

    @RabbitListener(queues = "fanoutQueue2")
    public void receive(String content){
    
    
        System.out.println("FanoutCustomer_02:"+content);
    }
}

4) Add objects and methods to the test class for testing

@Autowired
private FanoutProducer fanoutProducer;

@Test
public void testFanoutProducer(){
    
    
    fanoutProducer.send();
}
5.6 Routing routing mode

One producer, one exchange, two queues, two consumers

Structure diagram
Insert image description here

The producer sends the message to the direct switch (the routing mode needs to be implemented with the help of the direct switch). There is a routing key when binding the queue and the switch. The message sent by the producer will specify a routing key, then the message will only be sent to the corresponding The queue with the same key then listens to the consumer of the queue to consume messages. That is to allow consumers to selectively receive messages.

In this example:

Create 2 message queues

Create a direct switch object

Bind switches and queues

1) Create configuration class

package com.guo.direct;

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;

@Configuration
public class DirectConfig {
    
    

    @Bean
    public Queue directQueue1(){
    
    
        return new Queue("directQueue1");
    }

    @Bean
    public Queue directQueue2(){
    
    
        return new Queue("directQueue2");
    }

    @Bean
    public DirectExchange directExchange(){
    
    
        return new DirectExchange("directExchange");
    }

    @Bean
    public Binding bingDirectQueue1(){
    
    
        return BindingBuilder.bind(directQueue1()).to(directExchange()).with("zhangsan");
    }

    @Bean
    public Binding bingDirectQueue2(){
    
    
        return BindingBuilder.bind(directQueue2()).to(directExchange()).with("lisi");
    }

}

2) Create a producer

package com.guo.direct;

import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class DirectProducer {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private DirectExchange directExchange;

    public void send(){
    
    
        System.out.println("DirectProducer");
        rabbitTemplate.convertAndSend(directExchange.getName(),"zhangsan","zhangsanContent");
        rabbitTemplate.convertAndSend(directExchange.getName(),"lisi","lisiContent");
    }
}

3) Create two consumers

package com.guo.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class DirectCustomer_01 {
    
    

    @RabbitListener(queues = "directQueue1")
    public void receive(String content){
    
    
        System.out.println("DirectCustomer_01:"+content);
    }
}
package com.guo.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class DirectCustomer_02 {
    
    

    @RabbitListener(queues = "directQueue2")
    public void receive(String content){
    
    
        System.out.println("DirectCustomer_02:"+content);
    }
}

4) Add objects and methods to the test class for testing

@Autowired
private DirectProducer directProducer;

@Test
public void testDirectProducer(){
    
    
    directProducer.send();
}
5.7 Topic theme mode

One producer, one exchange, two queues, two consumers

Structure diagram
Insert image description here

Also known as wildcard mode (can be understood as fuzzy matching, routing mode is equivalent to exact matching)

Using direct-attached switches can improve our system, but it still has limitations in that it cannot implement multi-condition routing.

In a messaging system, we want to subscribe not only to queues based on routing keys, but also to sources based on production messages. At this time, topic switches can be used.

When using topic switches, routing keys cannot be written in arbitrary ways. The routing key should be in the form of meaningful words separated by dots. For example "goods.stock.info" etc. The routing key can be up to 255 bytes.

* represents a word

The # sign represents 0 or more words

1) Create configuration class

package com.guo.topic;


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;

@Configuration
public class TopicConfig {
    
    

    @Bean
    public Queue topicQueue1(){
    
    
        return new Queue("topicQueue1");
    }

    @Bean
    public Queue topicQueue2(){
    
    
        return new Queue("topicQueue2");
    }

    @Bean
    public TopicExchange topicExchange(){
    
    
        return new TopicExchange("topicExchange");
    }

    @Bean
    public Binding bingTopicQueue1(){
    
    
        return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("wangwu.*");
    }

    @Bean
    public Binding bingTopicQueue2(){
    
    
        return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("zhaoliu.#");
    }

}

2) Create a producer

package com.guo.topic;

import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TopicProducer {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private TopicExchange topicExchange;

    public void send(){
    
    
        System.out.println("TopicProducer");
 rabbitTemplate.convertAndSend(topicExchange.getName(),"wangwu.abc","wangwuContent");    rabbitTemplate.convertAndSend(topicExchange.getName(),"zhaoliu.xyz.qwer","zhaoliuContent");
    }
}

3) Create two consumers

package com.guo.topic;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class TopicCustomer_01 {
    
    

    @RabbitListener(queues = "topicQueue1")
    public void receive(String content){
    
    
        System.out.println("TopicCustomer_01:"+content);
    }
}
package com.guo.topic;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class TopicCustomer_02 {
    
    

    @RabbitListener(queues = "topicQueue2")
    public void receive(String content){
    
    
        System.out.println("TopicCustomer_02:"+content);
    }
}

4) Add objects and methods to the test class for testing

@Autowired
private TopicProducer topicProducer;

@Test
public void testTopicProducer(){
    
    
    topicProducer.send();
}
5.8 Manual Ack

Ack in RabbitMQ: It mainly confirms that the message has been consumed by the consumer and notifies the server to clear the message in the queue. spring-boot-data-amqp is an automatic ACK mechanism, which means that MQ will automatically help us after the message is sent. Go to ACK and then delete the message in the queue. This will cause some problems: if the consumer takes a long time to process the message, or an exception occurs when consuming the message, problems will occur. Manual Ack can avoid repeated consumption of messages.

5.8.1 Native mode testing

1. Taking the simple mode as an example, you only need to modify the consumer and start the producer for testing.

package com.guo.helloworld;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

//消费者
public class Consumer {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        System.out.println("消费者启动...");

        //创建连接
        ConnectionFactory factory = new ConnectionFactory();
        //设置参数
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setHost("192.168.25.134");
        factory.setPort(5672);//浏览器访问的是:15672

        //获取连接
        Connection connection = factory.newConnection();

        //获取Channel
        Channel channel = connection.createChannel();

        //回调,创建Consumer
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                try {
    
    
                    //获取消息
                    System.out.println(new String(body, "UTF-8"));
                    //模拟异常
                    int i = 1/0;
                    //手动ack
                    //参数1:deliveryTag:投递过来消息的标签,由MQ打的,从1开始,1,2,3...
        			//参数2:multiple:是否批量确认之前已经消费过的消息,一般为false
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } catch (Exception e) {
    
    //捕获所有异常
                    //第三个参数:requeue -> true表示重新放入队列,false -> 放弃该消息
                    //channel.basicNack(envelope.getDeliveryTag(), false, true);
                    //抛弃此条消息
                    channel.basicNack(envelope.getDeliveryTag(), false, false);
                    e.printStackTrace();
                    //关闭,否则一直循环当前操作
                    connection.close();
                }
            }
        };

        //获取消息
        //1.队列名称(从哪个队列中获取消息)
        //2.true表示自动ack(消费完消息之后,自动告诉rabbitmq)
        //  false表示手动ack,需要自己收到调用方法
        //3.回调
        channel.basicConsume("helloworld",false,defaultConsumer);

        //由于channel会回调DefaultConsumer中的handleDelivery方法,直接关闭会报错,可以在关闭之前调用Thread.sleep();
        //或者可以不调用关闭方法(消费方一般不关闭,有消息过来可以及时处理)
        //channel.close();
        //connection.close();
    }
}
5.8.2 Testing in SpringBoot

1. Add configuration in application.properties

#配置手动Ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual

2. Add AckCustomer demo in any mode tested before

package com.guo.simple;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

//消费者:监听队列中的消息,然后获取消息
@Component
@RabbitListener(queues = "simpleQueue")
public class AckCustomer {
    
    

    //处理消息(获取消息,进一步操作)
    @RabbitHandler
    public void receive(String message,Channel channel,Message msg){
    
    
        System.out.println("AckCustomer...");
        //获取消息内容
        if(message!=null && message.length()>0){
    
    
            try {
    
    
                System.out.println("获取消息:"+message);
                int i = 1/0;
                //手动确认
                long deliveryTag = msg.getMessageProperties().getDeliveryTag();
                System.out.println("deliveryTag:"+deliveryTag);
                channel.basicAck(deliveryTag,false);
            } catch (Exception e) {
    
    //捕获所有异常
                System.out.println("消息处理...");
                try {
    
    
                    //重新放入队列中
                    //channel.basicNack(msg.getMessageProperties().getDeliveryTag(),false,true);
                    //放弃消息
                    channel.basicNack(msg.getMessageProperties().getDeliveryTag(),false,false);
                    e.printStackTrace();
                    //关闭
                    channel.getConnection().close();
                } catch (Exception ex) {
    
    
                    ex.printStackTrace();
                }
            }

        }else{
    
    
            System.out.println("没有消息");
        }
    }
}

Just comment or open the exception for testing!

6. Transaction and confirm mechanism


6.1 Reliability of messages

think?

1. If the message has reached RabbitMQ and RabbitMQ is down, will the message be lost?

You can use Queue’s persistence mechanism

2. When the consumer is consuming messages, what should I do if the program is executed halfway and the consumer crashes?

Can manually Ack

3. When the producer sends a message, what should I do if the message is not sent to RabbitMQ due to network problems?

RabbitMQ provides transaction operations and Confirm and Return mechanisms

To ensure the delivery of messages, you can use transactions in RabbitMQ. Transactions can ensure 100% delivery of messages. You can record logs through transaction rollback and resend the current message regularly later. However, the transaction operation efficiency is too low.

In addition to transactions, RabbitMQ also provides a Confirm confirmation mechanism, which is much more efficient than transactions.

Insert image description here

6.2 RabbitMQ transactions

RabbitMQ transactions are the implementation of the AMQP protocol and are Channelcompleted by setting the mode. The specific operations are as follows:

channel.txSelect();  //开启事务
// ....本地事务操作
channel.txCommit();  //提交事务
channel.txRollback(); //回滚事务

Special note: RabbitMQ's transaction mechanism is a synchronous operation, which will greatly reduce the performance of RabbitMQ.

6.3 Confirm mechanism

Due to the transaction performance issues of RabbitMQ, the sender confirmation mode was introduced.

6.3.1 Create tool class
package com.guo.utils;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class RabbitMQUtils {
    
    
    /**
     * 1. 创建连接工厂(ConnectionFactory)
     * 2. 创建连接 (Connection)
     * 3. 创建通道  (Channel)
     Connection conn = connectionFactory.newConnection();
     Channel channel = conn.createChannel();
    */
    private static ConnectionFactory connectionFactory;

    static {
    
    
        connectionFactory = new ConnectionFactory();
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setHost("192.168.25.134");
    }


    public static Connection getConnection() {
    
    
        Connection connection = null;
        try {
    
    
            connection = connectionFactory.newConnection();
        } catch (IOException ioException) {
    
    
            ioException.printStackTrace();
        } catch (TimeoutException e) {
    
    
            e.printStackTrace();
        }
        return connection;
    }

    public static void close(Channel channel, Connection connection) {
    
    
        try {
    
    
            if(null != channel) {
    
    
                channel.close();
            }
            if(null != connection){
    
    
                connection.close();
            }
        }catch (Exception ex) {
    
    
            ex.printStackTrace();
        }
    }
}
6.3.2 Single message confirmation
channel.confirmSelect(); //开启发送方确认模式

1. On the RabbitMq console page, create a direct type switch, then create a queue and bind it

channel.waitForConfirms(); //For confirmation of a single message, true indicates success

package com.guo.confirm;

import com.guo.utils.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class ConfirmTest1 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //获取Channel
        Channel channel = connection.createChannel();
        //开启confirm
        channel.confirmSelect();
        //发送消息(前提:队列已经通过routingKey绑定到该交换机上)
        channel.basicPublish("myExchange","my",null,"消息内容".getBytes());

        //判断消息到达交换机,true表示到达,若没有交换机则系统会直接报错
        if(channel.waitForConfirms()){
    
    
            System.out.println("消息已到达交换机");
        }
        
        //设置延迟关闭,否则可能无法显示相关信息
        Thread.sleep(5000);

        //关闭
        RabbitMQUtils.close(channel,connection);
    }
}
6.3.2 Batch message confirmation

channel.waitForConfirmsOrDie(); //Batch message confirmation, if one message is not sent successfully, an exception will be thrown

package com.guo.confirm;

import com.guo.utils.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class ComfirmTest2 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //获取Channel
        Channel channel = connection.createChannel();
        //开启confirm
        channel.confirmSelect();
        //发送消息(前提:队列已经通过routingKey绑定到该交换机上)
        for (int i = 0; i < 10; i++) {
    
    
            //判断
            if(i == 5){
    
    
                //前提是系统中没有名为myExchange2的交换机
                channel.basicPublish("myExchange2", "my", null, ("消息内容"+ i).getBytes());
                continue;
            }
            //发送消息
            channel.basicPublish("myExchange", "my", null, ("消息内容"+ i).getBytes());
        }

        //确定批量操作是否成功
        //当发送的全部消息,有一个失败的时候,就直接全部失败 抛出异常
        channel.waitForConfirmsOrDie();
        
        //设置延迟关闭,否则可能无法显示相关信息
        Thread.sleep(5000);

        //关闭
        RabbitMQUtils.close(channel,connection);
    }
}
6.3.3 Callback method confirmation
package com.guo.confirm;

import com.guo.utils.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;

import java.io.IOException;

public class ComfirmTest3 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //获取Channel
        Channel channel = connection.createChannel();
        //开启confirm
        channel.confirmSelect();

        //发送消息(前提:队列已经通过routingKey绑定到该交换机上)
        channel.basicPublish("myExchange","my",null,"消息内容".getBytes());

        //开启异步回调
        channel.addConfirmListener(new ConfirmListener() {
    
    
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
    
    
                System.out.println("成功达到交换机");
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
    
    
                System.out.println("没有到达交换机");
            }
        });
        
        //设置延迟关闭,否则可能无法显示相关信息
        Thread.sleep(5000);

        //关闭
        RabbitMQUtils.close(channel,connection);
    }
}
6.4 Return mechanism

Confirm can only guarantee that the message reaches the exchange, but cannot guarantee that the message can be distributed to the specified queue by the exchange.

Moreover, exchange cannot persist messages, but queue can persist messages.

Use the Return mechanism to monitor whether the message is sent from the exchange to the specified queue.

Turn on the Return mechanism. When sending a message, you need to specify mandatory as true.

package com.guo.confirm;

import com.guo.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class ReturnTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //获取Channel
        Channel channel = connection.createChannel();
        //开启confirm
        channel.confirmSelect();

        //发送消息
        //注意:指定mandatory参数为true,设置没有绑定的routingkey
        channel.basicPublish("myExchange","my2",true,null,"消息内容".getBytes());

        //开启异步回调
        channel.addConfirmListener(new ConfirmListener() {
    
    
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
    
    
                System.out.println("成功达到交换机");
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
    
    
                System.out.println("没有到达交换机");
            }
        });

        //没有到达队列的时候触发,设置错误的路由的key的名称进行测试
        channel.addReturnListener(new ReturnListener() {
    
    
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange,
                                     String routingKey, AMQP.BasicProperties properties,
                                     byte[] body) throws IOException {
    
    

                System.out.println("没有到达队列");
                //如果消息没有到达队列,我们可以获取到消息的相关信息
                System.out.println("exchange:" + exchange);
                System.out.println("routingKey:" + routingKey);
                System.out.println("body:" + new String(body));
                //然后人工干预,考虑编写一个补偿机制,把消息保存到redis或者mysql中,保证消息不丢失
                //....
            }
        });
        
        //设置延迟关闭,否则可能无法显示相关信息
        Thread.sleep(5000);

        //关闭
        RabbitMQUtils.close(channel,connection);
    }
}
6.5 SpringBoot implementation

1. Add configuration in application.properties

Description of the corresponding value of spring.rabbitmq.publisher-confirm-type

  • NONE: Disable release confirmation mode, which is the default value
  • CORRELATED: The callback method will be triggered after the message is successfully published to the exchange.
  • SIMPLE: two effects
  1. Like the CORRELATED value, the callback method will be triggered.
  2. After successfully publishing the message, use rabbitTemplate to call the waitForConfirms or waitForConfirmsOrDie method and wait for the broker node to return the sending result. Based on the return result, determine the logic of the next step. The point to note is that if the waitForConfirmsOrDie method returns false, the channel will be closed, and the message cannot be sent next. to broker
# 配置开启Confirm和Return
spring.rabbitmq.publisher-confirm-type: simple
spring.rabbitmq.publisher-returns: true

2. Create a configuration class

package com.guo.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;


@Configuration
public class PublisherConfirmAndReturnConfig implements
        RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {
    
    

    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
    
    
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }


    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
    
    
        System.out.println("已到达交换机");
    }

    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
    
    
        System.out.println("未到达队列");
        //考虑人工干预,获取消息信息进行保存
        String exchange = returnedMessage.getExchange();
        String routingKey = returnedMessage.getRoutingKey();
        Message message = returnedMessage.getMessage();

        System.out.println("exchange:" + exchange);
        System.out.println("routingKey:" + routingKey);
        System.out.println("message:" + new String(message.getBody()));
    }
}

3. Modify the routingKey in the producer's message sending method, and then start the test class test!

7. Dead letter queue


The dead letter queue is not a special queue, it is just an ordinary queue, but we call them dead letter queues.

x-dead-letter-exchangeThe design of the dead letter queue is to set (dead letter switch) and x-dead-letter-routing-key(dead letter routing key) in the header information of a certain queue . Associated with a queue bound to a dead-letter exchange. Then specify the expiration time for the queue or the expiration time of the specified message, then the message will automatically arrive in the dead letter queue after expiration.

7.1 Scenario

Scenario 1: Unpaid orders are canceled within the specified time. The implementation is to put the order message into a queue and specify its expiration time. When the expiration time is up, it enters the dead letter queue, and you can directly retrieve the corresponding message from the consumer side of the dead letter queue.

Scenario 2: A certain message has been tried to be consumed on the consumer side many times, but failed to be consumed successfully. Then it will enter the dead letter queue and allow manual intervention.

7.2 Testing

Just create the RabbitMQUtils tool class in advance and test it

package com.guo.dead;

import com.guo.utils.RabbitMQUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class DeadQueueTest {
    
    

    //死信交换机
    private static final String dead_letter_exchange = "dead_letter_exchange";
    //死信路由键
    private static final String dead_letter_routing_key = "dead_letter_routing_key";
    //死信队列
    private static final String dead_letter_queue = "dead_letter_queue";

    private static final String people_exchange = "people_exchange";
    private static final String people_routing_key = "people_routing_key";
    private static final String people_queue = "people_queue";

    public static void main(String[] args) throws Exception{
    
    
        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 创建一个死信的交换机
        channel.exchangeDeclare(dead_letter_exchange, "direct");
        // 创建死信队列
        channel.queueDeclare(dead_letter_queue, true, false, false, null);
        // 将死信队列绑定到死信交换机,路由键为 "dead_letter_routing_key"
        channel.queueBind(dead_letter_queue, dead_letter_exchange, dead_letter_routing_key);

        //设置队列参数
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", dead_letter_exchange);
        arguments.put("x-dead-letter-routing-key", dead_letter_routing_key);

        //创建当前交换机,队列以及路由键
        channel.exchangeDeclare(people_exchange, "direct");
        //最后一个参数是当前队列的属性
        channel.queueDeclare(people_queue, true, false, false, arguments);
        channel.queueBind(people_queue, people_exchange, people_routing_key);

        //设置消息的过期时间,单位:毫秒
        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .expiration("15000").build();

        //发送消息
        channel.basicPublish(people_exchange, people_routing_key, properties, "dead_message".getBytes());

        RabbitMQUtils.close(channel, connection);
    }
}

8. Avoid repeated consumption of messages


8.1 Idempotence

All message middleware will have such a problem, that is, the problem of repeated consumption of messages, so we must do idempotent design. The so-called idempotent design means that the result of a message is the same no matter how many times it is consumed.

Repeated consumption of messages causes problems for non-idempotent operations. The reason for repeated consumption of messages is because the consumer does not give RabbitMQ an Ack.

Insert image description here

In order to solve the problem of repeated consumption of messages, Redis can be used. Before the consumer consumes the message, the ID of the message is now placed in Redis.

id-0 (executing business)

id-1 (business execution successful)

If ack fails, when RabbitMQ hands the message to other consumers, setnx is executed first. If the key already exists, its value is obtained. If it is 0, it means that a consumer is executing business logic. If it is 1, it means that the consumer is executing the business logic. The consumption has been completed, and it will be acked directly.

Extreme case: A deadlock occurs when the first consumer is executing business. Based on setnx, set a survival time for the key.

8.2 Solution

Solution 1: Generate a globally unique ID for each message, put the ID and business data in the same transaction, insert the ID into the table after each message consumption, and check whether the ID exists before each consumption. If If it does not exist, the corresponding logic will be executed; if it exists, it will be confirmed directly.

Option 2 (recommended): Use the redis + database solution to achieve idempotent design. The implementation idea is similar to the redis cache breakdown solution; when inserting data, insert the unique ID into the database at the same time, and then put it into redis , set the expiration time, and judge it from redis each time.

8.3 Test in springboot
8.3.1 Import dependencies
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
8.3.2 Configure application.yml
#配置rabbitmq
spring:
  rabbitmq:
    username: guest
    password: guest
    host: 192.168.25.131
    port: 5672
    listener:
      simple:
        acknowledge-mode: manual #表示手动Ack
    #confirm和return配置
    publisher-confirm-type: simple
    publisher-returns: true

  #配置redis
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    lettuce:
      pool:
        max-active: 8
8.3.3 Demonstration here in simple mode

1.Write configuration class

package com.guo.config;

import org.ietf.jgss.MessageProp;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author yangl
 * @version 1.0
 * @date 2022/12/1 16:55
 */

@Configuration
public class SimplestConfig {
    
    

    @Bean
    public Queue simplest(){
    
    
        return new Queue("simplestQueue");
    }

    //返回MessagePostProcessor,获取消息id
    @Bean
    public MessagePostProcessor getMessagePostProcessor(){
    
    

        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
    
    
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
    
    
                return message;
            }

            @Override
            public Message postProcessMessage(Message message, Correlation correlation, String exchange, String routingKey) {
    
    
                //获取消息参数
                MessageProperties messageProperties = message.getMessageProperties();
                //获取消息id
                String correlationDataId = ((CorrelationData) correlation).getId();
                System.out.println("配置类中的消息id:" + correlationDataId);
                messageProperties.setCorrelationId(correlationDataId);

                return message;
            }
        };

        return messagePostProcessor;
    }
}

2. Write producers

package com.guo.simplest;

import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 生产者
 */
@Component
public class SimplestProducer {
    
    

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private MessagePostProcessor messagePostProcessor;

    public void send(){
    
    
        System.out.println("SimplestProducer生产者");
        //发送消息
        //参数一:队列名称(或是路由规则)
        //参数二:消息内容
        CorrelationData correlationData = new CorrelationData();
        String id = correlationData.getId();
        System.out.println("生产者中的消息id:" + id);

        rabbitTemplate.convertAndSend("","simplestQueue","简单队列",messagePostProcessor,correlationData);
    }

}

3. Write consumers

package com.guo.simplest;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 *
 * 消费者
 */

@Component
public class SimplestCustomer {
    
    

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //监听队列中的消息
    @RabbitListener(queues = "simplestQueue")
    public void recrive(String content, Channel channel, Message message) throws Exception {
    
    
        //获取消息id
        String correlationId = message.getMessageProperties().getCorrelationId();
        System.out.println("消费者中的消息id:" + correlationId);
        //存到redis中,使用setIfAbsent方法,防止死锁
        Boolean flag = stringRedisTemplate.opsForValue()
                .setIfAbsent(correlationId, "0", 600, TimeUnit.SECONDS);
        //判断
        if(flag){
    
    
            System.out.println("SimplestCustomer消费者");
            System.out.println("接收来自simplestQueue中的消息:" + content);
            //业务执行完毕了,使用set方法修改key的值
            stringRedisTemplate.opsForValue()
                    .set(correlationId, "1", 600, TimeUnit.SECONDS);
            //手动ack
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);

            return;
        }

        //如果key没有设置成功,意味着redis中已经有这个消息的记录了
        //判断,1表示已经执行完毕
        if("1".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(correlationId))){
    
    
            System.out.println("消息已被消费过,直接Ack");
            //直接手动Ack就可以
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }
    }

}

4. Just test the test type

package com.guo;

import com.guo.simplest.SimplestProducer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot17RabbitmqApplicationTests {
    
    

	@Test
	void contextLoads() {
    
    
	}

	//简单模式
	@Autowired
	private SimplestProducer simplestProducer;
	@Test
	public void testSimplestProducer(){
    
    
		simplestProducer.send();
	}
}

After the test is successful, just check it in redis. At this time, the id has been generated and the value is 1. Then comment out the first manual Ack method in the SimplestCustomer consumer class and execute it again. This means that the message has been consumed. However, there will still be messages in the rabbitmq queue, and then start the startup class of the project, which will load the consumer's method of automatically calling the listening queue, and then execute the method of judging that the value in redis is 1, and directly manually Ack.

Guess you like

Origin blog.csdn.net/qq_52183856/article/details/130755097