pringboot integrated rabbitmq commodity spike business practice

Posted by metomeya on Mon, 22 Nov 2021 04:11:34 +0100

#springboot integrated rabbitmq commodity spike business practice (traffic peak shaving) how does the message queue realize traffic peak shaving?

To cut the peak of traffic, the easiest solution is to use the message queue to buffer the instantaneous traffic, convert the synchronous direct call into asynchronous indirect push, accept the instantaneous traffic peak at one end through a queue, and push the message smoothly at the other end.

Insert picture description here

I won't talk about how to integrate springboot and rabbitmq here. Please refer to the article https://www.cnblogs.com/fantongxue/p/12493497.html

1, Preparation:

The database has a commodity table with an inventory of 100. Now 1000 consumers are ready to rob the 100 stocks.

t_ The product table maintains the item number and the remaining quantity of the item inventory. The inventory of this commodity numbered No123321 has 100.

Insert picture description here

t_product_record maintains the user ID of the grabbed goods. Theoretically t_ The number of records in the product table should be 100 (a total of 100 people grabbed the goods).

Insert picture description here

We use the stress testing tool jweter to test its concurrency.

2, Spring boot starts integrating rabbitmq

1. Add amqp dependency

        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
        </dependency>

2. Configure the application.yml configuration file

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    username: root
    password: 1234
  rabbitmq:
    host: 101.201.101.206
    username: guest
    password: guest
    publisher-confirms: true  # Enable the Rabbitmq send message confirmation mechanism, send messages to the queue and trigger the callback method
    publisher-returns: true
    listener:
      simple:
        concurrency: 10 #Number of consumers
        max-concurrency: 10 #Maximum number of consumers
        prefetch: 1 #Current limit (number of messages consumers get from the queue each time)
        auto-startup: true  #Automatically start container on startup
        acknowledge-mode: manual #Enable ACK manual confirmation mode

3. RabbitConfig configuration class

1. Define the message conversion instance and convert it into JSON transmission

2. Configure and enable rabbitmq transaction

package com.aaa.springredis.controller;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.transaction.RabbitTransactionManager;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class RabbitConfig {
    /**
     * Define the message conversion instance and convert it into JSON transmission
     *
     * @return Jackson2JsonMessageConverter
     */
    @Bean
    public MessageConverter integrationEventMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    /**
     * Configure enable rabbitmq affair
     *
     * @param connectionFactory connectionFactory
     * @return RabbitTransactionManager
     */
    @Bean
    public RabbitTransactionManager rabbitTransactionManager(CachingConnectionFactory connectionFactory) {
        return new RabbitTransactionManager(connectionFactory);
    }
}

4. Initialize the callback function of rabbitmq

<font

Color = "red" > note: the method modified by @ PostConstruct will run when the server loads the Servlet, and will only be executed once by the server. If you want to complete some initialization operations when generating objects, but these initialization operations depend on dependency injection, you can't implement them in the constructor for a long time. For this purpose, you can use @ PostConstruct annotation to complete initialization. The @ PostConstruct annotation method will be called automatically after dependency injection is completed</ font>

The precondition for using the callback function is that the rabitmq message confirmation mechanism is enabled in the configuration file

Insert picture description here

Constructor >> @Autowired >> @PostConstruct

      @Autowired
        RabbitTemplate rabbitTemplate;
    private static final Logger LOGGER = LoggerFactory.getLogger(RabbitController.class);
    @PostConstruct
    private void init(){
        /**
         * Message sent to switch Exchange Trigger callback after.
         * To use this function, you need to turn it on for confirmation, spring-boot The configuration in is as follows:
         * spring.rabbitmq.publisher-confirms = true
         */
    rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
        @Override
        public void confirm(CorrelationData correlationData, boolean b, String s) {
            if (b) {
                System.out.println("Message confirmed cause:{"+s+"} - {"+correlationData+"}");
            } else {
                System.out.println("Message unacknowledged cause:{"+s+"} - {"+correlationData+"}");
            }
        }
    });
        /**
         * By implementing the ReturnCallback interface,
         * Triggered if the message fails to be sent from the switch to the corresponding queue
         * For example, it will be triggered when the queue cannot be found according to the routingKey specified when sending the message
         * To use this function, you need to start confirmation. The spring boot configuration is as follows:
         * spring.rabbitmq.publisher-returns = true
         */
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                LOGGER.error("Message returned:{}", message);
                LOGGER.error("Switch used by message:{}", exchange);
                LOGGER.error("The routing key used by the message:{}", routingKey);
                LOGGER.error("describe:{}", replyText);
            }
        });
    }

3, Start test

1. Write order Grab Test

To write the order grabbing test class, we use jweter stress test tool to start 1000 threads for testing (start multi-threaded concurrent testing). Therefore, in order to distinguish each simulated user, we use userId accumulation to distinguish.

        private int userId=0;
        //Start grabbing orders
        @RequestMapping("/begin")
        @ResponseBody
        public void begin(){
           userId++;
           this.send(userId);
        }

The above send method is to send the received user request to rabbitmq message middleware.

        @RequestMapping("/send")
        @ResponseBody
        public String send(Integer messge){
                //The first parameter: switch name, the second parameter: the value of Routing Key, and the third parameter: the message object to be delivered
                rabbitTemplate.convertAndSend("test.direct","test",messge);
                return "Message sent successfully";
        }

2. Configure rabbitmq listening method

rabbitmq listening, as mentioned in the previous article, is used to listen to messages received from the switch in a specified queue. One message is received until it is received!

Confirm whether the Message is received correctly through ACK. Each Message must be confirmed and can be sent manually

ACK or automatic ACK, if the information consumption fails, it will reject the current message and return the message to the original queue.

Receive the user's userId from the queue, and then simulate the purchase of goods (reduce an inventory and add a purchase record)

    @Autowired
    RabbitController controller;
    /**
     * @RabbitListener It can be marked on the class and needs to be matched @RabbitHandler Use with annotations
     * @RabbitListener The label on the class indicates that when a message is received, it will be handed over to the @RabbitHandler Which method to use,
     * according to MessageConverter Converted parameter type
     *
     * use @Payload and @Headers Annotations can be used in messages body And headers information
     *
     * adopt ACK Confirm whether it is received correctly, each Message All have to be confirmed( acknowledged),You can go there manually ACK Or automatic ACK
     */
    @RabbitListener(queues = "test") //Specifies the queue name to listen on
    public void receiver(@Payload Integer userId, @Headers Channel channel, Message message) throws IOException {
        LOGGER.info("user{}Start grabbing orders", userId);
        try {
            //Processing messages
            controller.robbingProduct(userId);
//             Confirm that the message has been consumed successfully
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            LOGGER.error("Consumption processing exception:{} - {}", userId, e);
//             Reject the current message and return the message to the original queue
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }

Method of purchasing goods

 public void robbingProduct(Integer userId){
                Product product = testDao.selectProductByNo("No123321");
                if (product != null && product.getTotal() > 0) {
                    //When updating the inventory table, the inventory quantity decreases by 1. Returning 1 indicates that the update is successful. Returning 0 indicates that the inventory has been 0
                    int i = testDao.updateProduct("No123321");
                    if(i>0){
                        //insert record
                        testDao.insertProductRecord(new ProductRecord("No123321", userId));
                        //Send SMS
                        LOGGER.info("user{}Order grabbing succeeded", userId);
                    }else {
                        LOGGER.error("user{}Order grabbing failed", userId);
                    }
                } else {
                    LOGGER.error("user{}Order grabbing failed", userId);
                }
        }

3. jweter tool test concurrency

jweter stress test tool how to use Baidu, ignore here!

Console printing

Insert picture description here

Insert picture description here

The inventory in the database becomes 0

Insert picture description here

The purchase record stores the user id (100 records) of successful order grabbing

Insert picture description here

Of course, the remaining 900 users failed to grab orders!

<font

Color = "blur" > rabbitmq queue is in the order of first in first out, first in first out. You have to queue up for 1000 requests. After the first 100 requests succeed, it is doomed that the last 900 requests fail! < / font >

<font

color="red" >The main change in using RabbitMQ is that in the past, the order grabbing request was directly executed by our order grabbing application, and now the request is transferred to the RabbitMQ server. The RabbitMQ server queues the received order grabbing request, and finally the RabbitMQ server forwards the order grabbing request to our order grabbing application. This benefit is to avoid our order grabbing application The sequence directly processes a large number of requests in a short time. The RabbitMQ server is mainly used to slow down the concurrency pressure of the order grabbing application, which is equivalent to adding a request buffer before our order grabbing application. < / font >

The actual combat is over!

seckill](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Ffantongxue666%2Frabbitmq-

seckill)