Build the springboot project by hand 06-springboot integrates RabbitMQ and its principles and application scenarios


foreword

RabbitMQ is a message service middleware developed by erlang language that implements AMQP (Advanved Message Queue Protocol) advanced message queue protocol.

Workflow - Soul Painter

work process
1. Both the producer (Producer) and the consumer (Consumer) need to establish a long connection (Connection) with RabbitMQ before they can send and receive messages. 2. The client
(producer, consumer) and server (RabbitMQ) can only Establish a long connection, open up a channel in the long connection to send and receive messages
3. The producer sends a message, and the message arrives at the designated switch (the sending message will be specified) of the broker's designated virtual host (the service will be configured), according to the routing key and the switch The binding relationship with the queue, send the message to the corresponding queue
3, the consumer listens to the queue through the channel, and the message can be obtained by the consumer in real time after entering the queue

Glossary

1. Broker (message broker) message broker: the message queue server entity, which is simply understood as a post office, through which mail is sent and received.
2. JMS (Java Message Service) JAVA message service. It is a specification based on JVM message broker. ActiveMQ and HornetMQ are JMS implementations
3. AMQP (Advanced Message Queuing Protocol)
advanced message queuing protocol is also a message broker specification, compatible with JMS
RabbitMQ is the implementation of AMQP
insert image description here
4. Message message consists of a message header and a message body. The message body is opaque, while the message header consists of a series of optional attributes, including routing-key (routing key), priority (priority relative to other messages), delivery-mode (indicating that the message may require persistent storage), etc.
5. Producer The producer of the message is also a client application that publishes the message to the exchange.
6. The Exchange switch is used to receive the messages sent by the producer and route these messages to the queue in the server.
There are three types of Exchange commonly used: direct, fanout, topic, and different types of Exchange have different strategies for forwarding messages
7.Queue message queue, used to save messages until sent to consumers. It is the container of the message and the destination of the message. A message can be put on one or more queues. The message is always in the queue, waiting for the consumer to connect to the queue to take it away.
8. Binding binding, used for the association between the message queue and the switch. A binding is a routing rule that connects an exchange and a message queue based on a routing key, so an exchange can be understood as a routing table composed of bindings.
The binding of Exchange and Queue can be a many-to-many relationship.
9. Connection network connection, such as a TCP connection.
10. Channel channel, an independent bidirectional data flow channel in a multiplexed connection. A channel is a virtual connection established in a real TCP connection. AMQP commands are sent through the channel. Whether it is publishing a message, subscribing to a queue or receiving a message, these actions are all completed through the channel. Because it is very expensive for the operating system to establish and destroy TCP, the concept of channel is introduced to reuse a TCP connection.
11. Consumer means a client application that obtains messages from the message queue.
12. Virtual Host , which represents a batch of switches, message queues and related objects. Virtual hosts are separate domains of servers that share the same authentication and encryption environment. Each vhost is essentially a mini version of RabbitMQ server, with its own queue, switch, binding and permission mechanism. The vhost is the basis of the AMQP concept and must be specified when connecting. The default vhost of RabbitMQ is /.

switch type

insert image description here


1. Installation

1.1 RabbitMQ official website installation

1.2 Docker installation and start

docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.11-management

# 开机自启
docker update rabbitmq --restart=always

● 5672 (AMQP port)
● 15672 (web management background port)

Local installation can be accessed through: http://127.0.0.1:15672/ , the default username and password are guest

2. Food tutorial

2.1. Import dependencies

<!--RabbitMQ-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2.2 Add configuration

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: root #用户名 默认guest
    password: root #密码 默认guest
    virtual-host: springboot-test #虚拟主机 默认/

2.3 Code implementation

2.3.1 Direct type

The direct-connected switch delivers the message to the corresponding queue according to the routing key carried in the message.

Direct connection type initialization configuration

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;

/**
 * 1、直连交换机配置
 */
@Configuration
public class DirectRabbitConfig {
    
    

    public static final String DIRECT_QUEUE = "===DirectQueue===";
    public static final String DIRECT_EXCHANGE = "===DirectExchange===";
    public static final String DIRECT_ROUTING = "===DirectRouting===";

    /**
     *      durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
     *      exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
     *      autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
     *      return new Queue("TestDirectQueue",true,true,false);
     */
    @Bean
    public Queue directQueue() {
    
    
        return new Queue(DIRECT_QUEUE,false);
    }

    @Bean
    DirectExchange directExchange() {
    
    
        return new DirectExchange(DIRECT_EXCHANGE,false,false);
    }

    @Bean
    Binding binding() {
    
    
        return BindingBuilder.bind(directQueue())
                .to(directExchange()).with(DIRECT_ROUTING);
    }
}

This configuration mainly manages the queue, switch, and binding by spring. Remember to declare the queue, switch, and establish the binding relationship. After the message is sent by the specified switch, the switch can send the message to the matching queue according to the routing key.

consumer

import com.chendi.springboot_rabbitmq.config.DirectRabbitConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;

@Slf4j
@Component
public class DirectReceiver {
    
    

    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE)
    public void receiver(String dataMsg) {
    
    
        log.info("接收者A dataMsg:{} ",dataMsg);
    }

    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE)
    public void receiver(String dataMsg) {
    
    
        log.info("接收者B dataMsg:{} ",dataMsg);
    }
}

producer

@RestController
@RequiredArgsConstructor
public class RabbitMQTestController {
    
    

    final RabbitTemplate rabbitTemplate;

    @GetMapping("/sendDirectMessage")
    public String sendDirectMessage() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            String messageData = "Hello World!" + i;
            //可自定义消息体类型
            rabbitTemplate.convertAndSend(DirectRabbitConfig.DIRECT_EXCHANGE, DirectRabbitConfig.DIRECT_ROUTING, messageData);
        }
        return "发送完成";
    }
}

Run Discovery: By default, RabbitMQ round-robin distribution will send each message to the next consumer in sequence. There are the following disadvantages:
1. There is no guarantee that the message has been consumed
. 2. The service that processes messages quickly gets as many messages as the service that processes messages slowly (fair distribution, more work for those who can).

2.3.2 Introducing the message manual confirmation mechanism

configuration file

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual # 设置消费端手动 ack
        #表示消费者端每次从队列拉取多少个消息进行消费,直到手动确认消费完毕后,才会继续拉取下一条
        prefetch: 1 # 预加载消息数量--QOS

consumer response

@Slf4j
@Component
public class DirectReceiver {
    
    

    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE)
    public void receiver(String dataMsg, Channel channel, Message message) throws IOException, InterruptedException {
    
    
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        Thread.sleep(1000);
        log.info("接收者A deliveryTag:{} dataMsg:{} ",deliveryTag ,dataMsg);
        channel.basicAck(deliveryTag,true);
    }

    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE)
    public void receiver2(String dataMsg, Channel channel, Message message) throws IOException {
    
    
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        log.info("接收者B deliveryTag:{} dataMsg:{} ",deliveryTag ,dataMsg);
        channel.basicAck(deliveryTag,true);
    }
}

Receipt method (
1. channel.basicAck indicates successful confirmation. After using this receipt method, the message will be deleted by rabbitmq broker.
2. channel.basicNack indicates failure confirmation. Generally, this method is used when the consumption message business is abnormal, and it can determine whether the message Re-enqueue
3. channel.basicReject rejects messages, the difference from basicNack is that batch operations cannot be performed, and other usages are similar.

2.3.2 Broadcast (Fanout) Type

Fan-type switch, this switch does not have the concept of routing keys, this switch will directly forward to all the queues bound to it after receiving the message.

Broadcast type configuration class

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;
/**
 * 2、广播、扇出交换机
 */
@Configuration
public class FanoutRabbitConfig {
    
    
    public final static String FANOUT_EXCHANGE = "fanoutExchange";
    public static final String FANOUT_QUEUE_A = "fanoutQueueA";
    public static final String FANOUT_QUEUE_B = "fanoutQueueB";
    public static final String FANOUT_QUEUE_C = "fanoutQueueC";
    /**
     *  创建三个队列
     *  将三个队列都绑定在交换机 fanoutExchange 上
     *  因为是扇型交换机, 路由键无需配置,配置也不起作用
     */
    @Bean
    public Queue queueA() {
    
    
        return new Queue(FANOUT_QUEUE_A);
    }
    @Bean
    public Queue queueB() {
    
    
        return new Queue(FANOUT_QUEUE_B);
    }
    @Bean
    public Queue queueC() {
    
    
        return new Queue(FANOUT_QUEUE_C);
    }
    @Bean
    FanoutExchange fanoutExchange() {
    
    
        return new FanoutExchange(FANOUT_EXCHANGE);
    }
    @Bean
    Binding bindingExchangeA() {
    
    
        return BindingBuilder.bind(queueA()).to(fanoutExchange());
    }
    @Bean
    Binding bindingExchangeB() {
    
    
        return BindingBuilder.bind(queueB()).to(fanoutExchange());
    }
    @Bean
    Binding bindingExchangeC() {
    
    
        return BindingBuilder.bind(queueC()).to(fanoutExchange());
    }
}

consumer

import com.chendi.springboot_rabbitmq.config.FanoutRabbitConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

//如果开启了消息手动确认机制,一定要记得应答消息噢
//不然消息会一直堆积在mq里
@Slf4j
@Component
public class FanoutReceiver {
    
    
    @RabbitListener(queues = FanoutRabbitConfig.FANOUT_QUEUE_A)
    public void fanout_A(String message) {
    
    
        log.info("fanout_A  {}" , message);
    }

    @RabbitListener(queues = FanoutRabbitConfig.FANOUT_QUEUE_B)
    public void fanout_B(String message) {
    
    
        log.info("fanout_B  {}" , message);
    }

    @RabbitListener(queues = FanoutRabbitConfig.FANOUT_QUEUE_C)
    public void fanout_C(String message) {
    
    
        log.info("fanout_C  {}" , message);
    }
}

Test Producer Controller plus

@GetMapping("/sendFanoutMessage")
public String sendFanoutMessage() {
    
    
    String messageData = "这是一条广播消息";
    rabbitTemplate.convertAndSend(FanoutRabbitConfig.FANOUT_EXCHANGE, "", messageData);
    return "发送完成";
}

2.3.3 Topic type

The characteristic of topic exchange is that there are rules between its routing key and binding key.

"*" (asterisk) is used to indicate a word (must appear)
"#" (pound sign) is used to indicate any number (zero or more) of words. When
the topic switch is not bound to the routing key, it is a direct switch. It is a fan-shaped switch when it is bound with the "#" sign .

Theme mode configuration class

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;
/**
 * 主题交换机
 * 转发规则:
 * #:匹配一个或者多个词
 * *:匹配一个或者0个词
 * 比如 有msg.# 、msg.* 匹配规则
 * msg.# 会匹配 msg.email、msg.email.b、msg.email.a
 * msg.* 只会匹配 msg.email 和 msg ,
 */
@Configuration
public class TopicRabbitConfig {
    
    
    //绑定键
    public final static String MSG_EMAIL = "msg.email";
    public final static String MSG_EMAIL_A = "msg.email.a";
    public final static String MSG_SMS = "msg.sms";
    public final static String TOPIC_EXCHANGE = "topicExchange";
    @Bean
    public Queue firstQueue() {
    
    
        return new Queue(TopicRabbitConfig.MSG_EMAIL);
    }
    @Bean
    public Queue secondQueue() {
    
    
        return new Queue(TopicRabbitConfig.MSG_EMAIL_A);
    }
    @Bean
    public Queue thirdQueue() {
    
    
        return new Queue(TopicRabbitConfig.MSG_SMS);
    }
    @Bean
    TopicExchange exchange() {
    
    
        return new TopicExchange(TOPIC_EXCHANGE);
    }
    @Bean
    Binding bindingExchangeMessage() {
    
    
        return BindingBuilder.bind(firstQueue()).to(exchange()).with(MSG_EMAIL);
    }
    @Bean
    Binding bindingExchangeMessage2() {
    
    
        return BindingBuilder.bind(secondQueue()).to(exchange()).with("msg.#");
    }
    @Bean
    Binding bindingExchangeMessage3() {
    
    
        return BindingBuilder.bind(thirdQueue()).to(exchange()).with("msg.*");
    }
}

consumer

import com.chendi.springboot_rabbitmq.config.TopicRabbitConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class TopicReceiver {
    
    
    @RabbitListener(queues = TopicRabbitConfig.MSG_EMAIL)
    public void topic_man(String message) {
    
    
        log.info("队列{} 收到消息:{}" ,TopicRabbitConfig.MSG_EMAIL, message);
    }
    @RabbitListener(queues = TopicRabbitConfig.MSG_SMS)
    public void topic_woman(String message) {
    
    
        log.info("队列{} 收到消息:{}" ,TopicRabbitConfig.MSG_SMS, message);
    }
    @RabbitListener(queues = TopicRabbitConfig.MSG_EMAIL_A)
    public void xxx(String message) {
    
    
        log.info("队列{} 收到消息:{}" ,TopicRabbitConfig.MSG_EMAIL_A, message);
    }
}

Test Producer Controller plus

@GetMapping("/sendTopicMessage")
public String sendTopicMessage() {
    
    
    rabbitTemplate.convertAndSend(TopicRabbitConfig.TOPIC_EXCHANGE, TopicRabbitConfig.MSG_EMAIL, "Hello Topic!所有队列都可以收到这条信息");
    rabbitTemplate.convertAndSend(TopicRabbitConfig.TOPIC_EXCHANGE, TopicRabbitConfig.MSG_EMAIL_A, "只有 msg.email.a可以收到这条信息");
    rabbitTemplate.convertAndSend(TopicRabbitConfig.TOPIC_EXCHANGE, TopicRabbitConfig.MSG_SMS, "msg.email.a 和 msg.sms可以收到这条信息");
    return "发送完成";
}

If you enable the message manual confirmation mechanism, you must remember to answer the message! ! !


The above integration is complete.

3. Practical application scenarios

3.1 How to control the order of messages

1. When only one consumer can guarantee the order of messages, but the efficiency is low.
2. The producer sends messages to the queue sequentially, but when multiple consumers listen to a queue, they will poll for distribution, resulting in disorder. It is modified so that a consumer only listens to one queue, and the producer customizes the delivery strategy. 1, 2, and 3 are put into the A queue, and 4, 5, and 6 are put into the B queue (the sequential messages are a whole) and put into a queue.

3.2 Ensure that messages are not consumed repeatedly (idempotency)

After the consumer consumes, under normal circumstances, a receipt will be sent to the message queue to prove that the message has been consumed. But at this time, the consumer network transmission failure or downtime, the message queue does not receive the message consumption receipt and will redistribute the message to other consumers, resulting in the message being consumed multiple times.
········
Solutions: (Specific analysis of specific problems)
1. Maintain a set in redis. Before the producer sends the message, add a globally unique id. Before the consumer consumes, check it in redis. Check to see if it has been consumed, and continue to execute if it has not been consumed.

//生产者
public void sendMessageIde() {
    
    
    MessageProperties properties = new MessageProperties();
    properties.setMessageId(UUID.randomUUID().toString());
    Message message = new Message("消息".getBytes(), properties);
    rabbitTemplate.convertAndSend("exchange", "", message);
}

//消费者
@RabbitListener(queues = "queue")
@RabbitHandler
public void processIde(Message message, Channel channel) throws IOException {
    
    
    if (stringRedisTemplate.opsForValue().setIfAbsent(message.getMessageProperties().getMessageId(),"1")){
    
    
        // 业务操作...
        System.out.println("消费消息:"+ new String(message.getBody(), "UTF-8"));
        // 手动确认   
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

3.3 Guarantee the reliability of the message

Message sending process
message sending process
It can be seen that the message sent by the producer is divided into two parts when it reaches the consumer accurately.
1. The sender: when the message is delivered to the broker successfully, it calls back the confirmCallback , and when the switch fails to deliver the message to the queue , it calls back the returnCallback .

configuration file

spring:
  rabbitmq:
    publisher-returns: true # 开启消息抵达队列的确认  
    # 低版本 publisher-confirms: true
    publisher-confirm-type: correlated # 开启发送端确认

configuration class

/**
 * 常用的三个配置如下
 * 1---设置手动应答(acknowledge-mode: manual)
 * 2---设置生产者消息发送的确认回调机制 (  #这个配置是保证提供者确保消息推送到交换机中,不管成不成功,都会回调
 *     publisher-confirm-type: correlated
 *     #保证交换机能把消息推送到队列中
 *     publisher-returns: true
 *      template:
 *       #以下是rabbitmqTemplate配置
 *       mandatory: true)
 *  3---设置重试
 */
@Slf4j
@Configuration
public class RabbitConfig {
    
    
    @Autowired
    private ConnectionFactory rabbitConnectionFactory;

    // 存在此名字的bean 自带的容器工厂会不加载(yml下rabbitmq下的template的配置),如果想自定义来区分开 需要改变bean 的名称
    @Bean
    public RabbitTemplate rabbitTemplate(){
    
    
        RabbitTemplate rabbitTemplate=new RabbitTemplate(rabbitConnectionFactory);
        //默认是用jdk序列化
        //数据转换为json存入消息队列,方便可视化界面查看消息数据
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);
        //此处设置重试template后,会再生产者发送消息的时候,调用该template中的调用链
        rabbitTemplate.setRetryTemplate(rabbitRetryTemplate());
        rabbitTemplate.setConfirmCallback(
                (correlationData, ack, cause) -> {
    
    
                    if(!ack){
    
    
                        System.out.println("ConfirmCallback     "+"相关数据:"+  correlationData);
                        System.out.println("ConfirmCallback     "+"确认情况:"+ ack);
                        System.out.println("ConfirmCallback     "+"原因:"+ cause);
                    }
                });
        rabbitTemplate.setReturnsCallback((ReturnedMessage returned) -> {
    
    
            System.out.println("ReturnsCallback:     "+"消息:"+ returned.getMessage());
            System.out.println("ReturnsCallback:     "+"回应码:"+ returned.getReplyCode());
            System.out.println("ReturnsCallback:     "+"回应消息:"+ returned.getReplyText());
            System.out.println("ReturnsCallback:     "+"交换机:"+ returned.getExchange());
            System.out.println("ReturnsCallback:     "+"路由键:"+ returned.getRoutingKey());
        });
        return rabbitTemplate;
    }

    //重试的Template
    @Bean
    public RetryTemplate rabbitRetryTemplate() {
    
    
        RetryTemplate retryTemplate = new RetryTemplate();
        // 设置监听  调用重试处理过程
        retryTemplate.registerListener(new RetryListener() {
    
    
            @Override
            public <T, E extends Throwable> boolean open(RetryContext retryContext, RetryCallback<T, E> retryCallback) {
    
    
                // 执行之前调用 (返回false时会终止执行)
                //log.info("执行之前调用 (返回false时会终止执行)");
                return true;
            }
            @Override
            public <T, E extends Throwable> void close(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
    
    
                // 方法结束的时候调用
                if(retryContext.getRetryCount() > 0){
    
    
                    log.info("最后一次调用");
                }
            }
            @Override
            public <T, E extends Throwable> void onError(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
    
    
                // 方法异常时会调用
                log.info("第{}次调用", retryContext.getRetryCount());
            }
        });
        return retryTemplate;
    }
}

sender test

import com.chendi.springboot_rabbitmq.config.DirectRabbitConfig;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;

@RestController
public class SendCallbackMessageController {
    
    

    @Autowired
    RabbitTemplate rabbitTemplate;  //使用RabbitTemplate,这提供了接收/发送等等方法

    @ResponseBody
    @GetMapping("/sendMessageToExchangeFail")
    public Object sendMessageToExchangeFail() {
    
    
        String messageData = "这条消息不会到达交换机";
        rabbitTemplate.convertAndSend("不存在的交换机", "", messageData, new CorrelationData(UUID.randomUUID().toString()));
        return messageData;
    }
    @ResponseBody
    @GetMapping("/sendMessageToQueueFail")
    public Object sendMessageToQueueFail() {
    
    
        String messageData = "这条消息不会到达队列";
        rabbitTemplate.convertAndSend(DirectRabbitConfig.DIRECT_EXCHANGE, "不存在的路由键", messageData, new CorrelationData(UUID.randomUUID().toString()));
        return messageData;
    }
}

Request result:
insert image description here

3.4 The dead letter queue solves the order timeout and fails to pay

Scenario: When a customer purchases a product, there is an operation
to generate an order = "deduct inventory =" to complete the payment.
When there is only 1 item left in the inventory, user A places an order but has not paid for it, which will cause user B to judge the inventory when placing an order Insufficient lead to failure to generate order.
At this point, it is necessary to solve the problem of unpaid orders overtime.

Process:
Initialize two groups of normal queues and switches A and B. The initialization parameters x-dead-letter-exchange and x-dead-letter-routing-key of group A point to the switch and routing key of group B. It means that the deleted or expired data in A can be put into the queue of the specified routing key of the specified switch.
-In this way, if the order is set to be unpaid for more than 5 minutes
, the sender specifies the expiration time as 5 * 60 * 1000 when sending the message.
After the time expires, the message will be delivered to queue B (dead letter queue), and queue B will judge according to the order id Whether to pay, do corresponding operations such as adding inventory.

Dead letter queue configuration class

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;

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

/**
 * 解决订单超时未支付的问题
 *
 * 创建两个队列
 * 1、队列A(正常的队列只是设置了某些参数):设置队列中的超时未消费信息指定丢到对应的队列B
 * 2、队列B(也是一个正常的队列),只是把超时的信息丢给它所以称呼为死信队列
 */

@Configuration
public class DeadLetterExchangeConfig {
    
    

    /**
     * x-message-tti(Time-To-Live)发送到队列的消息在丟弃之前可以存活多长时间(毫秒)
     * x-max-length限制队列最大长度(新增后挤出最早的),单位个数
     * x-expires队列没有访问超时时,自动删除(包含没有消费的消息),单位毫秒
     * x-max-length-bytes限制队列最大容量
     * x-dead-letter-exchange死信交换机,将删除/过期的数据,放入指定交换机
     * x-dead-letter-routing-key死信路由,将删除/过期的数据,放入指定routingKey
     * x-max-priority队列优先级
     * x-queue-mode对列模式,默认lazy(将数据放入磁盘,消费时放入内存)
     * x-queue-master-locator镜像队列
     */
    @Bean
    public Queue orderQueue(){
    
    
        Map<String, Object> args = new HashMap<>(2);
        // 绑定我们的死信交换机
        args.put("x-dead-letter-exchange", "orderDeadExChange");
        // 绑定我们的路由key
        args.put("x-dead-letter-routing-key", "orderDeadRoutingKey");
        return new Queue("orderQueue", true, false, false, args);
    }
    @Bean
    public Queue orderDeadQueue(){
    
    
        return new Queue("orderDeadQueue");
    }
    @Bean
    public DirectExchange orderExchange(){
    
    
        return new DirectExchange("orderExchange");
    }
    @Bean
    public DirectExchange orderDeadExchange(){
    
    
        return new DirectExchange("orderDeadExChange");
    }
    //绑定正常队列到交换机
    @Bean
    public Binding orderBindingExchange(Queue orderQueue, DirectExchange orderExchange) {
    
    
        return BindingBuilder.bind(orderQueue).to(orderExchange).with("orderRoutingKey");
    }
    //绑定死信队列到死信交换机
    @Bean
    public Binding deadBindingExchange(Queue orderDeadQueue,  DirectExchange orderDeadExchange) {
    
    
        return BindingBuilder.bind(orderDeadQueue).to(orderDeadExchange).with("orderDeadRoutingKey");
    }
}

consumer

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
 * 死信队列的消费者
 */
@Slf4j
@Component
public class DeadLetterReceiver {
    
    

    @RabbitListener(queues = "orderDeadQueue")
    public void orderDeadQueueReceiver(String dataMsg, Channel channel, Message message) {
    
    
        try{
    
    
            long deliveryTag = message.getMessageProperties().getDeliveryTag();
            log.info("死信队列接收者A收到消息,根据订单id查询订单是否支付,未支付解锁库存 deliveryTag:{} dataMsg:{} ",deliveryTag ,dataMsg);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e){
    
    
            log.info("如果报错了,执行补偿机制");
        }
    }
}

producer

@GetMapping("/createOrder")
public String createOrder() {
    
    
    rabbitTemplate.convertAndSend("orderExchange", "orderRoutingKey", "我是订单json", message -> {
    
    
        //设置过期时间10s
        message.getMessageProperties().setExpiration("10000");
        return message;
    });
    return "发送完成";
}

Summarize

Application scenarios of MQ:

  1. Asynchronous processing (registration, sending emails and sending short messages)
  2. Application decoupling (after the user places an order, the order system needs to notify the inventory system to deduct the inventory. Even if the inventory system fails, the message queue can ensure reliable delivery of messages without causing message loss.)
  3. Traffic peak shaving (spike activity, generally due to excessive traffic, the application will hang up, set the message queue parameters, if the length exceeds the maximum value, the user request will be discarded directly or jump to the error page)

Guess you like

Origin blog.csdn.net/weixin_45549188/article/details/129175289
Recommended