According to personal analysis and understanding, does the project need MQ technology

Posted by Sgarissta on Sat, 15 Jan 2022 17:09:12 +0100

According to personal analysis and understanding, does the project need MQ technology

Order item - > order specification - > fulfillment set - > fulfillment process - > OPU

The above is the order framework process structure abstracted by the company according to the order business.

The OPU here is the smallest execution unit, which can be synchronous or asynchronous.

Since the project does not require second kill, MQ is used for cross system asynchronous communication.

Two technologies have been used for asynchrony here. At first, it is multithreading (thread pool) + scheduled task (Quartz), and later it is changed to RabbitMQ.

Due to the confidentiality regulations, pseudo code is used here to show how to realize asynchronous communication with peripheral systems without MQ:

There are several points to pay attention to first.

  1. When using a thread pool, pay attention to the number of thread pools. Apply the formula U * U * (1 + w/c): number of CPU cores * expected CPU utilization * (1 + waiting time of a single thread / calculation time of a single thread)
  2. The cycle period of scheduled tasks needs to be determined. Once it was set to 1 minute in the project, then it was changed to 5 minutes according to the daily order quantity and the asynchronous communication quantity in the order
  3. The retry mechanism for message sending failure should be well thought out. At the beginning of the project, in order to support this business, two important tables are mainly prepared: (1) quartz for the fields such as order number, stored message, interface information and retry times_ info_ Request table (2) records the log information of all internal and external calls or external calls_ info_ Log, which contains fields such as order number and interface information
/**
 * @Description:Thread pool configuration
 * @author: Old street layman
 */
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor{
    //Number of core threads
    int corePoolSize = 50;
    //Maximum number of threads
    int maximumPoolSize = 100;
    //The maximum idle time of threads exceeds the number of corePoolSize threads
    long keepAliveTime =2;
    //In seconds
    TimeUnit unit = TimeUnit.SECONDS;
    //Used to store submitted pending tasks
    BlokckingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(40);
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,
        //The default policy is to throw RejectedExecutionException when rejecting a task
        new ThreadPoolExecutor.AbortPolicy());
    );
    return threadPoolExecutor;
}
}
/**
 * @Description:Asynchronous request
 * @author: Old street layman
 */
public class SendService{
    @Autowired
    ThreadPoolExecutor threadPoolExecutor;
    
    public void asyncSend(){
        threadPoolExecutor.submit(()->{
            //1. Call the corresponding interface and send the request
            //2. Record the corresponding information in quartz_ info_ In the request table, the number of retries is set to 0 and the success is set to false
        })
    }
}
/**
 * @Description:Responsible for scanning quartz in the scheduled task_ info_ Request and quartz_info_log table and compare which interfaces need to be retried
 * @author: Old street layman
 */
public class Sendjob{
    public void retry(){
        //Scan quartz_info_request and quartz_info_log table
        //Match according to order number
        //quartz_ info_ If the log does not match the information returned from the third-party response interface and has timed out, retry the interface;
        //If the response interface has reported an error, the quartz_ info_ Set the retry times of the data corresponding to the request as the maximum times, and enter the current order information into the exception table for reconciliation and manual document revision
        //......
        //In fact, there are still many steps to consider. Here are several main steps for simplicity and clarity
    }
    
}

In this way, thread pool + scheduled tasks can also achieve the results of asynchronous communication. As for the reconciliation and repair of orders, it is all based on the consideration of designing the message sending failure mechanism.

Obviously not so easy to control

Next, let's look at the impact of using RabbitMQ.

The first is the code:

/**
 * @Description:MQ Configuration, direct connection switch is used here
 * @author: Old street layman
 */
@Configuration
public class RabbitConfig {
//The name of the queue is DirectQueue
@Bean
public Queue DirectQueue(){
    //durable sets whether to persist. The default value is false. true will be stored on the hard disk
    return new Queue("DirectQueue",true);
}
//The name of the switch is MyDirectExchange
@Bean
public DirectExchange MyDirectExchange(){
    //durable sets whether to persist. The default value is false. true will be stored on the hard disk
    return new DirectExchange("MyDirectExchange",true,false);
}
//Bind, bind queue to switch
@Bean
public Binding bindingDirect(){
    //durable sets whether to persist. The default value is false. true will be stored on the hard disk
    return BindingBuilder.bind(DirectQueue()).to(MyDirectExchange()).with("MyDirectRouting");
}
/**
 * @Description:Configuration of message confirmation mechanism
 * @author: Old street layman
 */    
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
    RabbitTemplate rabbitTemplate = new RabbitTemplate();
    rabbitTemplate.setConnectionFactory(connectionFactory);
    //Open the Mandatory and trigger the callback function
    rabbitTemplate.setMandatory(true);
    //This is a measure for the message delivery failure of producer - > broker (host)
    rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        	System.out.println("ConfirmCallback:     "+"correlationData: "+correlationData);
            System.out.println("ConfirmCallback:     "+"Confirmation:"+correlationData);
            System.out.println("ConfirmCallback:     "+"cause: "+correlationData);
            //Record the correlationData in the exception information table to facilitate the repair of orders (the callback function in the confirmation mode may generally be a network problem or the disk is full. These situations rarely occur. With the log monitoring system, the operation and maintenance personnel can repair them immediately)
        }
    });
    //This is for the measures of switch - > queue message delivery failure
    rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){
        @Override
        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey){
            	System.out.println("ReturnCallback:     "+"Message:"+message);
				System.out.println("ReturnCallback:     "+"Response code:"+replyCode);
                System.out.println("ReturnCallback:     "+"Response information:"+replyText);
                System.out.println("ReturnCallback:     "+"Switch:"+exchange);
                System.out.println("ReturnCallback:     "+"Routing key:"+routingKey);
			//Record the message in the exception information table to facilitate order repair (in this case, the callback function may be routingkey error or the queue cannot be found. These situations rarely occur, and the production environment is basically impossible)
        }
    });
    return rabbitTemplate;
}    
    
    
}

Next is the message confirmation mechanism code manually confirmed by the consumer

@Configuration
public class RabbitConfig2 {
@Autowired
private CachingConnectionFactory connectionFactory;
@Autowired
private AckReceiver ackReceiver;//Message receiving and processing class
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(){
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    container.setConcurrentConsumers(1);
    container.setMaxConcurrentConsumers(1);
    container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ defaults to automatic confirmation, which is changed to manual confirmation
    //Set up a queue
    container.setQueueNames("DirectQueue");
    container.setMessageListener(ackReceiver);
    return container;
} 
}

Consumer code

/**
 * @Description:The consumer sends the request according to the Message. If there is an abnormal failure, they can choose to resend or give up according to the specific situation
 * @author: Old street layman
 */
@Component
public class AckReceiver implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            //msg is the message delivered
            String msg = message.toString();
            //Get the corresponding information from msg, call the corresponding interface and send the request
            //Multiple messages can be sent with the for loop
            channel.basicAck(deliveryTag, true); //When the parameter is true, the delivery can be confirmed at one time_ All messages with tag less than or equal to the incoming value
        } catch (Exception e) {
            //Exception s can be divided in more detail according to different exceptions
            //You can choose to put it back in the queue, such as timeout or network problems
            //If it fails and does not resend, it shall be recorded in the abnormal order table to facilitate reconciliation and document revision
			//channel.basicReject(deliveryTag, true);// When the parameter is true, the message will be put back to the queue, so you need to judge when to use rejection according to the business logic
            channel.basicReject(deliveryTag, false);
            e.printStackTrace();
        }
    }
}

Producer code

/**
 * @Description:Asynchronous request
 * @author: Old street layman
 */
public class SendService{
    @Autowired
    RabbitTemplate rabbitTemplate;
    
    public String asyncSend(){
    	Map<String,Object> map=new HashMap<>();
    	//Add multiple key value pairs, store relevant information and send it to the broker
        //For example, order number, request message, interface name, etc
        rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);
        return "ok";
    }
}

To sum up:

After using MQ, it is obvious that the message confirmation mechanism is more perfect, which is equivalent to using MQ to replace the Database-based scheduled task, which is more efficient. Needless to say, it is easier for developers to troubleshoot in case of message exception or loss.

Looking only at the asynchronous communication function in the distributed environment, the introduction of MQ is still necessary.

Based on personal understanding of the company's project, if you have a better view, please communicate~

Topics: MQ