RabbitMQ quick start seven simple modes

start

seven modes

These seven models are actually the message team operation mode given in the RabbitMQ official entry document

They are:

  1. "Hello World!" (initially getting started, sending and receiving)
  2. Work Queues (work queue, one send and more charge)
  3. Publish/Subscribe (publish/subscribe)
  4. Routing (message routing)
  5. Topics
  6. RPC (request/reply)
  7. Publisher Confirms

Official website address: https://www.rabbitmq.com/getstarted.html

insert image description here

project dependencies

It is recommended to create two SpringBoot projects, one as a producer and the other as a consumer

You can also use Maven's inheritance aggregation mode to manage two projects

The following dependencies need to be introduced into the project

		<!-- AMQP 依赖,包含了 RabbitMQ 的相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

1、“Hello World!”

The simplest mode is send and receive, and it is also the most basic mode, so the official name is "Hello World!", which means the initial entry

insert image description here

(1) Connection method

There are several concepts in RabbitMQ, namely: virtual host (virtualHost), channel (channel), queue (queue), and the concept of a switch (exchanges) will be encountered later

The service first establishes a connection with the virtual host, then creates a channel, declares or creates a queue and then sends or receives a message, and the message will eventually be transmitted in the queue

The following uses the connection method to realize sending and receiving messages, so as to understand the mode of RabbitMQ (not commonly used, just understand)

producer test class

@SpringBootTest
public class PublisherTest {
    
    
    @Test
    public void testSendMessage() throws IOException, TimeoutException {
    
    
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.0.102");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("root");
        factory.setPassword("123456");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

        // 2.创建通道 Channel
        Channel channel = connection.createChannel();

        // 3. 声明队列(不存在则创建)
        String queueName = "simple.queue";	// 队列名
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.发送消息
        String message = "hello, rabbitmq!";
        channel.basicPublish("", queueName, null, message.getBytes());
        System.out.println("发送消息成功:" + message);

        // 5.关闭通道和连接
        channel.close();
        connection.close();
    }
}

Consumer Test Class

@SpringBootTest
public class ConsumerTest {
    
    

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        // 1. 建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1 设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.0.102");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("root");
        factory.setPassword("123456");
        // 1.2 建立连接
        Connection connection = factory.newConnection();

        // 2. 创建通道 Channel
        Channel channel = connection.createChannel();

        // 3. 声明队列(不存在则创建)
        String queueName = "simple.queue";	// 队列名
        channel.queueDeclare(queueName, false, false, false, null);

        // 4. 获取消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
    
    
            @SneakyThrows
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) {
    
    
                // 5.处理消息
                String message = new String(body);
                System.out.println("接收到消息:" + message);
            }
        });
        System.out.println("等待接收消息......");
    }
}

We can view relevant information on the RabbitMQ management page:

insert image description here

Notice:

  1. Consumers receive messages asynchronously without blocking the main thread
  2. queueDeclare will create the queue if it does not exist, otherwise it will not create it
  3. The queue will not be deleted automatically, it can be deleted on the management page (click the queue name, click the Delete option)
  4. The message will only be read once, and the unread message is stored in the queue waiting to be consumed
  5. In the above example, the consumer does not close the channel and connection, and will not only read one message, but will wait and read continuously
  6. After RabbitMQ restarts, the queue is deleted because it is not persistent. Change the second parameter of queueDeclare to true to create a persistent queue (the existing queue cannot be changed)
  7. After RabbitMQ restarts, although there is a queue, the message is gone, because the message is not persisted. When sending the message, change the third parameter of the basicPublish method MessageProperties.PERSISTENT_TEXT_PLAINto to persist the message

The persistent queue will be marked with a letter D in the Features column, as shown in the figure:

insert image description here
You can see the Properties information for persistent messages, but not for unpersisted messages, as shown in the figure:

insert image description here

(2) RabbitTemplate method

As can be seen from the above example, most of the code is repeated, so SpringAMQP encapsulates the RabbitTemplate to facilitate the operation of the message queue

First, in the project yaml configuration file, if the connection-related configuration of RabbitMQ

spring:
  rabbitmq:
    host: 192.168.0.102	# RabbitMQ 服务 ip 地址
    port: 5672			# 消息服务端口
    username: root		# 用户名
    password: "123456"	# 密码
    virtual-host: /		#虚拟主机

Then you can autowire the RabbitTemplate class

producer test class

@RunWith(SpringRunner.class)
@SpringBootTest()
public class SpringAmqpTest {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void helloWorldModeTest() {
    
    
        String queueName = "hello.world.queue";
        String message   = "Hello, springAMQP!";
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

Simply call the convertAndSend method to send the message

Note: This operation does not create a queue and has no effect if the queue does not exist

To create a queue, you need to declare a bean of type Queue and be managed by Spring

Usually placed in a Configuration configuration class, examples are as follows:

@Configuration
public class RabbitMqConfig {
    
    
    @Bean
    public Queue simpleQueue() {
    
    
        return new Queue("hello.world.queue");	// 队列名与函数名无关
    }
}

When the project is started in this way, the bean is created, and a queue will be created (if it already exists, it will not be created again)

The consumer is no longer demonstrated in the test class, but uses the way of listening to the queue

Just annotate @RabbitListener on a method and specify the queue name.
At the same time, the class where the method is located should also be managed by Spring (annotation @Component)

@Component
public class SpringRabbitListener {
    
    

    @RabbitListener(queues = "hello.world.queue")
    public void listenSimpleQueue(String message) {
    
    
        System.out.printf("消费者接收到 hello.world.queue 的消息:【 %s 】\n", message);
    }

}

Start the project to listen to the queue and process the received messages

Note: If the monitored queue name does not exist, an error will be reported Failed to declare queue(s):[hello.world.queue]. The solution is the same as creating a queue in the previous configuration.

Notice:

  1. The queue created in this way is persistent by default
  2. Messages produced in this way are persisted by default

2、Work Queues

Work Queues The work queue is actually a mode of sending and receiving multiple times, of course, it can also be multiple sending and receiving

The main reason is that each message may be a task to be processed, so multiple consumers processing tasks can improve the execution efficiency of tasks

insert image description here

create queue

The configuration class for creating a queue can only be configured in one of the producer and the consumer, but the one with the configuration should be started first, otherwise the other will fail because there is no queue

Of course, there is no problem with all configurations. If the queue already exists, you can also not configure it.

@Configuration
public class RabbitMqConfig {
    
    
    @Bean
    public Queue simpleQueue() {
    
    
        return new Queue("work.queue");
    }
}

producer

Here loop 50 times to simulate the release of multiple tasks

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage2WorkQueue() {
    
    
        String queueName = "work.queue";
        String message = "work message ---- ";
        for (int i = 1; i <= 50; i ++) {
    
    
            rabbitTemplate.convertAndSend(queueName, message + i + "th");
        }
    }

}

consumer

Use two listening methods to simulate two consumers, and use the thread's sleep() method to simulate the time spent processing tasks

@Component
public class SpringRabbitListener {
    
    

    private int count1 = 0;

    private int count2 = 0;

    @RabbitListener(queues = "work.queue")
    public void listenSimpleQueue1(String msg) throws InterruptedException {
    
    
        System.out.printf("消费者 1 第 %d 次接收消息:【 %s 】 %s", ++count1, msg, LocalTime.now().toString());
        Thread.sleep(50);
    }

    @RabbitListener(queues = "work.queue")
    public void listenSimpleQueue2(String msg) throws InterruptedException {
    
    
        System.err.printf("消费者 2 第 %d 次接收消息:【 %s 】 %s", ++count2, msg, LocalTime.now().toString());
        Thread.sleep(200);
    }

}

Since the sleep time of the two consumers is different, the faster consumer should process more tasks

But in actual operation, the result is that the two processes processed the same number of tasks, and the faster consumer 1 processed half of the tasks and stopped processing them.

This is related to the prefetch mechanism of the message queue

When there are unprocessed messages on the consumer side, the queue will still send messages to the consumer, and these messages will be stored in the consumer's cache

The solution is to limit the number of prefetched information items on the consumer side, and do the following configuration in the consumer's yaml configuration file:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 预取信息条数

When the number of prefetches is limited to 1, the consumer will only receive new messages after processing the current message

3、Publish/Subscribe

In the previous two modes, the message will only be received once by a consumer, but here it is different

In the Publish/Subscribe (publish/subscribe) mode, the message published by the producer will be broadcast to all consumers, similar to the relationship between bloggers and fans

Also known as fanout mode, because it is implemented using a fanout switch

insert image description here

relationship binding

@Configuration
public class FanoutConfig {
    
    

    // 创建 Fanout (广播)交换机
    @Bean
    public FanoutExchange fanoutExchange() {
    
    
        return new FanoutExchange("my.fanout");
    }

    // 创建队列 1
    @Bean
    public Queue fanoutQueue1() {
    
    
        return new Queue("fanout.queue1");
    }

    // 创建队列 2
    @Bean
    public Queue fanoutQueue2() {
    
    
        return new Queue("fanout.queue2");
    }

    // 创建绑定关系(Fanout交换机与队列 1)
    @Bean
    public Binding fanoutBinding1(FanoutExchange fanoutExchange, Queue fanoutQueue1) {
    
    
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    // 创建绑定关系(Fanout交换机与队列 2)
    @Bean
    public Binding fanoutBinding2(FanoutExchange fanoutExchange, Queue fanoutQueue2) {
    
    
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }

}

producer

At this point, the producer no longer sends messages to the queue, but to the Fanout switch

Fanout exchange broadcasts messages to all queues bound to it

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void fanoutMode() {
    
    
        String exchangeName = "my.fanout";
        String msg = "Hello, everyone!";
        rabbitTemplate.convertAndSend(exchangeName, "", msg);
    }

}

When convertAndSend sends a message to the switch, the first parameter is the switch name, and the third parameter is the message content

The second parameter is the routingKey routing key, which will be mentioned in the routing mode later

consumer

Consumers still listen to queues, nothing special

@Component
public class SpringRabbitListener {
    
    

    @RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String msg) {
    
    
        System.out.printf("消费者接收到 fanout.queue1 的消息:【 %s 】\n", msg);
    }

    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String msg) {
    
    
        System.out.printf("消费者接收到 fanout.queue2 的消息:【 %s 】\n", msg);
    }

}

4、Routing

Routing is the routing mode, implemented according to the Direct switch, also known as the Direct mode

The switch conditionally routes to different queues according to the routing key of the received message, and there can be multiple queues
insert image description here

consumer

In the previous Fanout mode, we know how to create switches and queue bindings in the configuration class

In fact, we can complete this step in the @RabbitListener annotation

First look at the implementation of the consumer:

@Component
public class SpringRabbitListener {
    
    

    @RabbitListener(bindings = @QueueBinding(   // 监听一个绑定关系
            value = @Queue("direct.queue1")     // 队列
            , exchange = @Exchange(name = "my.direct", type = ExchangeTypes.DIRECT) // 交换机(名称与类型)
            , key = {
    
    "info", "warning", "err"}  // 监听的路由键
    ))
    public void listenDirectQueue1(String msg) {
    
    
        System.out.printf("消费者接收 direct.queue1 的消息:【 %s 】", msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("direct.queue2")
            , exchange = @Exchange(name = "my.direct")  // 交换机默认类型即为 ExchangeTypes.DIRECT
            , key = {
    
    "err"}
    ))
    public void listenDirectQueue2(String msg) {
    
    
        System.err.printf("消费者接收 direct.queue2 的消息:【 %s 】", msg);
    }

}

Note: When the switch and queue in the annotation do not exist, you must first start the consumer to create it, and then start the producer

By the way: if you declare the Binding bean in the configuration, use with() to specify the routing key

like:BindingBuilder.bind(queue).to(directExchange).with("info, err");

producer

At this time, when the producer sends a message to the specified exchange, specify the routing key to send the message to the corresponding queue

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendDirectExchange() {
    
    
        String exchangeName = "my.direct";
        String msg;

        msg = "Info message.";
        rabbitTemplate.convertAndSend(exchangeName, "info", msg);

        msg = "Warning message.";
        rabbitTemplate.convertAndSend(exchangeName, "warning", msg);

        msg = "Error message.";
        rabbitTemplate.convertAndSend(exchangeName, "err", msg);
    }

}

Output result:

消费者接收 direct.queue2 的消息:【 Error message. 】
消费者接收 direct.queue1 的消息:【 Info message. 】
消费者接收 direct.queue1 的消息:【 Warning message. 】
消费者接收 direct.queue1 的消息:【 Error message. 】

queue2 is output first due to the difference between the error stream and the output stream

5. Topics

Topics is the topic mode. It is very similar to the Routing mode. It is routed to the qualified queue according to the routing key, but the routing key of the Topic supports wildcards.

insert image description here

consumer

@Component
public class SpringRabbitListener {
    
    

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1")
            , exchange = @Exchange(name = "my.topic", type = ExchangeTypes.TOPIC)	// 交换机类型为 TOPIC
            , key = {
    
    "cn.*"}	// * 为通配符,只能匹配到下一个点的任意字符串(如 cn.news 可匹配, cn.news.today 不可匹配)
    ))
    public void listenTopicQueue1(String msg) {
    
    
        System.out.printf("消费者接收 cn.* 的消息:【 %s 】\n", msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue2")
            , exchange = @Exchange(name = "my.topic", type = ExchangeTypes.TOPIC)
            , key = {
    
    "#.news"}	// 也可用 # 做通配符,效果同 *
    ))
    public void listenTopicQueue2(String msg) {
    
    
        System.out.printf("消费者接收 *.news 的消息:【 %s 】\n", msg);
    }

}

producer

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendTopicExchange() {
    
    
        String exchangeName = "my.topic";
        String message = "富强民主文明和谐自由平等公正法制爱国敬业诚信友善";
        rabbitTemplate.convertAndSend(exchangeName, "cn.news", message);
    }

}

6、RPC

RPC is the Request/reply request reply mode, similar to network request and response.
insert image description here
In this mode, the producer and consumer are no longer used, but the client and server

Here is a simple little example

create queue

It is better to configure and create on the server side

@Configuration
public class RpcConfig {
    
    

	// 创建用于接收请求的队列
    @Bean
    public Queue requestQueue() {
    
    
        return new Queue("request.queue");
    }
    
}

Server

The RabbitListener annotation is on the class, and the @RabbitHandler annotation is on the method to indicate the method of processing the message

Similar to the Controller controller in the web project

@Component
@RabbitListener(queues = "request.queue")
public class RpcServer {
    
    

    @RabbitHandler
    public String process(String request) {
    
    
        System.out.println("收到请求内容:" + request);
        return "好的已收到!";
    }

}

client

Just use the convertSendAndReceive() method to send a message to the request queue, and an Object object will be returned, which is the return value of the method annotated by @RabbitHandler on the server side

The test class is as follows:

@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitRpcTest {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void rpcTest() {
    
    
        String request = "收到请回复";
        Object reply =  rabbitTemplate.convertSendAndReceive("request.queue", request);
        System.out.println("响应:" + reply);
    }

7、Publisher Confirms

Publisher Confirms is a release confirmation to ensure that the message is successfully pushed to the queue, or to take corresponding measures when the message fails to be sent

configuration

Configure publisher-confirm-type on the producer side, as follows

spring:
  rabbitmq:
    publisher-confirm-type: correlated

consumer

Add a message confirmation callback function in rabbitTemplate, when the message is sent, the function will be called according to the sending result for processing

In order to mark the status of the message, a CorrelationData object is generally set to be sent according to the message, which can contain the content of the message or some attributes

See the following example:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void confirmTest() throws ExecutionException, InterruptedException {
    
    
		// 设定消息确认的回调函数
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    
    
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    
    
                System.out.println("id:    " + correlationData.getId());
                System.out.println("ack:   " + ack);    // 是否成功
                System.out.println("cause: " + cause);  // 原因
                Message returnedMessage = correlationData.getReturnedMessage(); // 获取返回信息
                System.out.println("returnedMessage: " + new String(returnedMessage.getBody()));
            }
        });
        // 消息本体
        String message = "Hello!";
        // 创建 CorrelationData 并设置返回信息
        CorrelationData correlationData = new CorrelationData("id-1");
        correlationData.setReturnedMessage(new Message(message.getBytes(), new MessageProperties()));
        // 发送消息到 confirm.queue 队列并携带 correlationData
        rabbitTemplate.convertAndSend("confirm.queue", (Object) message, correlationData);
        // 调用 correlationData.getFuture().get() 阻塞主线程等待消息确认
        CorrelationData.Confirm confirm = correlationData.getFuture().get();
        System.out.println("confirm: " + confirm);
    }
}

Note: As long as the message is sent to the queue, the normal result (ack is true) will be returned, not after the consumer receives it

Guess you like

Origin blog.csdn.net/Cey_Tao/article/details/128098509