Overview
When I went to Netease for an interview, the interviewer asked me a question, saying
After placing an order, if the user has not paid and needs to cancel the order, what can I do?
My answer at the time was to scan the DB table with a scheduled task. The interviewer was not very satisfied and suggested:
It is impossible to achieve quasi-real-time notification with timed tasks. Is there any other way?
My answer at the time was:
You can use the queue. After the order is placed, send a message to the queue and specify the expiration time. When the time is up, the callback interface is executed.
After the interviewer listened, they stopped asking. In fact, my thinking at the time was correct, but I was not very professional. The professional word is to use 延迟消息
.
In fact, there are some problems with the use of timed tasks. The original business system hoped that after 10 minutes, if the order is not paid, the order will be cancelled immediately and the product inventory will be released. However, once the amount of data is large, the time for obtaining the data of unpaid orders will be prolonged, and some orders will not be cancelled after 10 minutes, maybe 15 minutes, 20 minutes, etc. In this case, the inventory will not be released in time, and it will affect the odd number. With the delayed message, it is theoretically possible to cancel the order according to the set time.
At present, most of the articles on the Internet about using RabbitMQ to implement delayed messages are about how to use RabbitMQ's dead letter queue to implement it. The implementation scheme seems to be cumbersome and complicated, and it is still implemented using the original RabbitMQ Client API, which is even more verbose.
Spring Boot has packaged the RabbitMQ Client API, which is much simpler to use. The following details how to use rabbitmq_delayed_message_exchange
plugins and Spring Boot to implement delayed messages.
software preparation
erlang
Please refer to installing erlang under Win10
The version used in this article is:
Erlang 20.3
RabbitMQ
Please refer to installing rabbitmq under win10
This article uses the window
version of RabbitMQ, the version number is:
3.7.4
rabbitmq_delayed_message_exchange插件
Plugin download address:
After opening the URL, ctrl + f, search rabbitmq_delayed_message_exchange
.
Remember, you must choose the version number. Since I am using RabbitMQ 3.7.4, the corresponding rabbitmq_delayed_message_exchange
plugin must also choose 3.7.x.
If you don't choose the right version, you will encounter all kinds of weird problems when using delayed messages, and you can't find a solution online. I struggled all night with this problem. Remember to choose the correct plugin version.
After downloading the plugin, place it in a directory under the RabbitMQ installation directory plugins
and start the plugin with the following command:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
If the startup is successful, the following message will appear:
The following plugins have been enabled:
rabbitmq_delayed_message_exchange
After starting the plugin successfully, remember to restart RabbitMQ for it to take effect.
Integrate RabbitMQ
This is very simple, add it directly to the pom.xml file of the maven project
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
The version of Spring Boot I am using is 2.0.1.RELEASE
.
Next application.properties
, add the redis configuration to the file:
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
Define ConnectionFactory and RabbitTemplate
It is also very simple, the code is as follows:
package com.mq.rabbitmq;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitMqConfig {
private String host;
private int port;
private String userName;
private String password;
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(host,port);
cachingConnectionFactory.setUsername(userName);
cachingConnectionFactory.setPassword(password);
cachingConnectionFactory.setVirtualHost("/");
cachingConnectionFactory.setPublisherConfirms(true);
return cachingConnectionFactory;
}
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
return rabbitTemplate;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Exchange and Queue configuration
package com.mq.rabbitmq;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class QueueConfig {
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange("test_exchange", "x-delayed-message",true, false,args);
}
@Bean
public Queue queue() {
Queue queue = new Queue("test_queue_1", true);
return queue;
}
@Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(delayExchange()).with("test_queue_1").noargs();
}
}
It is important to note here that using yes CustomExchange
, no DirectExchange
, another CustomExchange
type must be x-delayed-message
.
implement message sending
package com.mq.rabbitmq;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
public class MessageServiceImpl {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMsg(String queueName,String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("消息发送时间:"+sdf.format(new Date()));
rabbitTemplate.convertAndSend("test_exchange", queueName, msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setHeader("x-delay",3000);
return message;
}
});
}
}
Note that when sending, you must add a header
x-delay
The delay time I set here is 3 seconds.
message consumer
package com.mq.rabbitmq;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
public class MessageReceiver {
@RabbitListener(queues = "test_queue_1")
public void receive(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("消息接收时间:"+sdf.format(new Date()));
System.out.println("接收到的消息:"+msg);
}
}
Running Spring Boot programs and sending messages
Run the Spring Boot program directly in the main method, and Spring Boot will automatically resolve MessageReceiver
the class.
Next, you only need to use Junit to run the interface for sending messages.
package com.mq.rabbitmq;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqApplicationTests {
@Autowired
private MessageServiceImpl messageService;
@Test
public void send() {
messageService.sendMsg("test_queue_1","hello i am delay msg");
}
}
After running, you can see the following information:
Message sending time: 2018-05-03 12:44:53
After 3 seconds, the Spring Boot console outputs:
Message received time: 2018-05-03 12:44:56 Message received
: hello i am delay msg