Implementation of retry mechanism (4m,10m,10m,1h,2h,6h,15h)

Posted by mordeith on Wed, 26 Jan 2022 10:14:43 +0100

Project scenario:

As I know, the project we are working on now has many external interfaces for the agent to call, but the response of some interfaces is not returned in real time. At this time, we need to respond to the agent by calling back the interface. During this period, other situations such as network instability may occur, resulting in the failure of callback interface call. Therefore, a specific callback retry mechanism is required. This mechanism refers to the notification mode of Alipay.

Thinking process:

The callback itself is very simple. It only needs the agent to implement our interface as required, but the retry mechanism is troublesome. The retry interval is not fixed, and the interval to the later time is too long. The effect achieved by using scheduled tasks is not ideal. Finally, by using rabbitmq, the characteristics of dead letter queue, The retry mechanism is perfectly implemented (I don't know if there is a better way), and the solution is shown below.

Solution:

First of all, if the message of rabbitmq is set with ttl (ttl corresponds to our retry interval), it will automatically enter the dead letter queue if it is not consumed within the ttl time. At this time, we will consume the messages in the dead letter queue, which just achieves the effect of message delay consumption. Then, how to realize different retry intervals: we set the interval of seven retries, Save it to the array, record the number of retries of the message with redis, and take the corresponding array subscript. If we fail to retry, we will get the number through redis, take the time from the array, set it to the message, and send it to the queue again. At this point, the retry mechanism is completed. The following is the code implementation.

Code implementation:

Here is the queue configuration

@Configuration
public class RabbitGatewayCallbackConfig {


    /**
     * Buffer switch
     */
    @Bean
    public DirectExchange gatewayCallbackDelayExchange() {
        return new DirectExchange(ExchangeConst.GATEWAY_CALLBACK_DELAY_EXCHANGE);
    }

    /**
     * Actual consumption queue
     * We will listen to the queue and consume the messages in the queue
     */
    @Bean
    public Queue gatewayCallbackQueue() {
        return new Queue(QueueConst.GATEWAY_CALLBACK_QUEUE,true,false,false);
    }

    /**
     * Bind the switch and specify the routing key
     */
    @Bean
    public Binding gatewayCallbackBinding() {
        return BindingBuilder.bind(gatewayCallbackQueue()).to(gatewayCallbackDelayExchange()).with(RoutingConstant.GATEWAY_CALLBACK_ROUTING);
    }

    /**
     * Call back the buffer queue, and all the messages we use will be put into this queue. In fact, we will not consume this
     * Messages in the queue are directly sent to the information queue for consumption after the message expires
     */
    @Bean
    public Queue gatewayCallBufferQueue() {
        Map<String,Object> args = new HashMap<>();
        //args.put("x-message-ttl", "10000"); This configuration is disabled because the delayed callback time is not fixed
        args.put("x-dead-letter-exchange", ExchangeConst.GATEWAY_CALLBACK_DELAY_EXCHANGE);
        args.put("x-dead-letter-routing-key", RoutingConstant.GATEWAY_CALLBACK_ROUTING);
        return new Queue(QueueConst.GATEWAY_CALLBACK_BUFFER_QUEUE, true, false, false, args);
    }

}

How to set ttl for messages

public static Message getMessage(String JsonStringData, int index) {
    if (index > CALLBACK_INTERVAL.length - 1) {
        throw new RuntimeException("index Cannot be greater than the length of the array");
    }
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setExpiration(CALLBACK_INTERVAL[index]);//Set the expiration time and take the corresponding array subscript
    return new Message(JsonStringData.getBytes(StandardCharsets.UTF_8), messageProperties);
}

The specific business code will not be posted because it involves the company's business. The basic idea is that when our callback fails, we will encapsulate the callback content and save it in the message, set a member array, save the interval between retries, and then set the key value recording the number of retries of the message on redis. Each retry will, Take the value from the array and set it in the message, and then add it until the seventh time. Do not continue to retry (remember to set ttl for redis to prevent business exceptions, but the key always exists and has not been deleted).

Topics: Java RabbitMQ Redis message queue