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()); } }