Scenario analysis of SpringBoot integrating Redisson to implement delay queue

Usage scenario

1. The order was placed successfully and was not paid for 30 minutes. Payment timeout, automatic cancellation of order

2. The order was signed, and no evaluation was conducted 7 days after signing. The order timeout is not evaluated, and the system defaults to high praise

3. The order was placed successfully, the merchant did not receive the order for 5 minutes, and the order was cancelled

4. Delivery timeout, push SMS reminder

...

For the scenes with long delay and low real-time performance, we can use the way of task scheduling to conduct regular polling. For example: XXL job

Today, we adopt a relatively simple and lightweight method, using Redis's delay queue for processing. Of course, there are better solutions. The best solution can be selected according to the company's technical selection and business system. For example, the delay queue of message middleware Kafka and RabbitMQ is used

Without discussing its implementation principle, the code will first implement the Redis based delay queue in practice

1. Introduce Redisson dependency

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.10.5</version>
</dependency>

2. redis configuration in yml

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    database: 12
    timeout: 3000

3. Create redissoconfig configuration

/**
* Redisson to configure
*/
@Configuration
public class RedissonConfig {
 
	@Value("${spring.redis.host}")
	private String host;
	@Value("${spring.redis.port}")
	private int port;
	@Value("${spring.redis.database}")
	private int database;
	@Value("${spring.redis.password}")
	private String password;
 
	@Bean
	public RedissonClient redissonClient() {
		Config config = new Config();
		config.useSingleServer()
			.setAddress("redis://" + host + ":" + port)
			.setDatabase(database)
			.setPassword(password);
		return Redisson.create(config);
	}
 
}

4. Encapsulate Redis delay queue tool class

/**
 * redis Delay queue tool
 */
@Slf4j
@Component
public class RedisDelayQueueUtil {
 
    @Autowired
	private RedissonClient redissonClient;
 
    /**
     * Add delay queue
     * @param value Queue value
     * @param delay delay time 
     * @param timeUnit Time unit
     * @param queueCode Queue key
     * @param <T>
     */
    public <T> void addDelayQueue(T value, long delay, TimeUnit timeUnit, String queueCode){
        try {
            RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueCode);
            RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
            delayedQueue.offer(value, delay, timeUnit);
			log.info("(Add delay queue succeeded) Queue key:{},Queue value:{},Delay time:{}", queueCode, value, timeUnit.toSeconds(delay) + "second");
        } catch (Exception e) {
            log.error("(Failed to add delay queue) {}", e.getMessage());
            throw new RuntimeException("(Failed to add delay queue)");
        }
    }
 
	/**
	 * Get delay queue
	 * @param queueCode
	 * @param <T>
	 * @return
	 * @throws InterruptedException
	 */
    public <T> T getDelayQueue(String queueCode) throws InterruptedException {
        RBlockingDeque<Map> blockingDeque = redissonClient.getBlockingDeque(queueCode);
        T value  = (T) blockingDeque.take();
        return value;
	}
}

5. Create delay queue service

/**
 * Delay queue service enumeration
 */
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum RedisDelayQueueEnum {
 
	ORDER_PAYMENT_TIMEOUT("ORDER_PAYMENT_TIMEOUT","Order payment timeout, automatic cancellation of order", "orderPaymentTimeout"),
	ORDER_TIMEOUT_NOT_EVALUATED("ORDER_TIMEOUT_NOT_EVALUATED", "The order timeout is not evaluated, and the system defaults to high praise", "orderTimeoutNotEvaluated");
 
	/**
	 * Delay queue Redis Key
	 */
	private String code;
 
	/**
	 * Chinese description
	 */
	private String name;
 
	/**
	 * Bean for specific service implementation of delay queue
	 * It can be obtained through the context of Spring
	 */
	private String beanId;
 
}

6. Define delay queue executor

/**
 * Delay queue actuator
 */
public interface RedisDelayQueueHandle<T> {
 
	void execute(T t);
 
}

7. Create the Bean defined in the enumeration and implement the delay queue executor OrderPaymentTimeout: order payment timeout delay queue processing class

/**
 * Order payment timeout processing class
 */
@Component
@Slf4j
public class OrderPaymentTimeout implements RedisDelayQueueHandle<Map> {
	@Override
	public void execute(Map map) {
		log.info("(Received order payment timeout delay message) {}", map);
		// TODO order payment timeout, automatic cancellation of order processing business
 
	}
}

OrderTimeoutNotEvaluated: the order timeout did not evaluate the delay queue processing class

/**
 * Order timeout not evaluated processing class
 */
@Component
@Slf4j
public class OrderTimeoutNotEvaluated implements RedisDelayQueueHandle<Map> {
	@Override
	public void execute(Map map) {
		log.info("(Received order timeout no evaluation delay message) {}", map);
		// TODO order timeout is not evaluated. The system defaults to processing business
 
	}
}

8. Create a delay queue consumption thread and start it after the project is started

/**
 * Start delay queue
 */
@Slf4j
@Component
public class RedisDelayQueueRunner implements CommandLineRunner {
 
	@Autowired
	private RedisDelayQueueUtil redisDelayQueueUtil;
 
	@Override
	public void run(String... args) {
		new Thread(() -> {
			while (true){
				try {
					RedisDelayQueueEnum[] queueEnums = RedisDelayQueueEnum.values();
					for (RedisDelayQueueEnum queueEnum : queueEnums) {
						Object value = redisDelayQueueUtil.getDelayQueue(queueEnum.getCode());
						if (value != null) {
							RedisDelayQueueHandle redisDelayQueueHandle = SpringUtil.getBean(queueEnum.getBeanId());
							redisDelayQueueHandle.execute(value);
						}
					}
				} catch (InterruptedException e) {
					log.error("(Redis Abnormal interruption of delay queue) {}", e.getMessage());
				}
			}
		}).start();
		log.info("(Redis Delay queue started successfully)");
	}
}
In the above steps, the core code of Redis delay queue has been completed. Let's write a test interface and simulate it with PostMan

9. Create a test interface to simulate adding a delay queue

/**
 * Delay queue test
 * Created by LPB on 2020/04/20.
 */
@RestController
public class RedisDelayQueueController {
 
	@Autowired
	private RedisDelayQueueUtil redisDelayQueueUtil;
 
	@PostMapping("/addQueue")
	public void addQueue() {
		Map<String, String> map1 = new HashMap<>();
		map1.put("orderId", "100");
		map1.put("remark", "Order payment timeout, automatic cancellation of order");
 
		Map<String, String> map2 = new HashMap<>();
		map2.put("orderId", "200");
		map2.put("remark", "The order timeout is not evaluated, and the system defaults to high praise");
 
		// Add order payment timeout and automatically cancel the order delay queue. To test the effect, delay 10 seconds
		redisDelayQueueUtil.addDelayQueue(map1, 10, TimeUnit.SECONDS, RedisDelayQueueEnum.ORDER_PAYMENT_TIMEOUT.getCode());
 
		// If the order timeout is not evaluated, the system defaults to high praise. To test the effect, delay 20 seconds
		redisDelayQueueUtil.addDelayQueue(map2, 20, TimeUnit.SECONDS, RedisDelayQueueEnum.ORDER_TIMEOUT_NOT_EVALUATED.getCode());
	}
 
}

10. Start the SpringBoot project and add the delay queue with the PostMan calling interface. You can see that the two delay queues have been added successfully through the Redis client

Check the IDEA console log to see that the delay queue has been consumed successfully

The above contents are reproduced from Script house In this record, this blog is for reference and learning. Thank the script home. For more redis related content, please go to Script house

Keywords: Java Redis Spring Boot

Added by shadow1200 on Thu, 03 Feb 2022 04:34:54 +0200