Franz`s blog

订单超时取消的几种方案

RabbitMQ+死信队列

基本原理是通过RabbitMQ消息过期+死信队列实现。

image-20231002081100787

将消息投递到延迟交换机,延迟交换机根据routingKey将信息投递到,因为delayQueue不存在消费者,delayQueue在消息超过有效时间后,信息将会被投递到死信交换机,死信交换机根据routingKey将消息投递到死信队列,最后被取消订单消费者消费。

需要的依赖

1
2
3
4
5
6
7
8
9
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-amqp'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
testCompileOnly 'org.projectlombok:lombok:1.18.30'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'
}

交换机以及队列配置

1.DelayQueueRabbitConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 订单延迟队列配置
* @author Franz Li
*/
@Configuration
public class DelayQueueRabbitConfig {

/**
* 订单延迟队列
*/
public static final String MALL_ORDER_DELAY_QUEUE="mall.order.delay.queue";

/**
* 订单延迟交换机
*/
public static final String MALL_ORDER_DELAY_EXCHANGE="mall.order.delay.exchange";

/**
* 订单延迟routing key
*/
public static final String MALL_ORDER_DELAY_ROUTING_KEY="mall.order.delay.routing.key";


@Bean
public Queue orderQueue(){
// 死信配置
Map<String,Object> params = new HashMap<>();
params.put("x-dead-letter-exchange", DelayQueueDlxRabbitConfig.MALL_ORDER_DLX_EXCHANGE);
params.put("x-dead-letter-routing-key", DelayQueueDlxRabbitConfig.MALL_ORDER_DLX_ROUTING_KEY);
return new Queue(MALL_ORDER_DELAY_QUEUE, true, false, false, params);
}

@Bean
public DirectExchange orderExchange(){
return new DirectExchange(MALL_ORDER_DELAY_EXCHANGE,true,false);
}

@Bean
public Binding orderBinding(){
return BindingBuilder
.bind(orderQueue())
.to(orderExchange())
.with(MALL_ORDER_DELAY_ROUTING_KEY);
}

}

2. DelayQueueDlxRabbitConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 订单延迟队列死信队列 & 交换机配置
* @author Franz Li
*/
@Configuration
public class DelayQueueDlxRabbitConfig {

/**
* 死信队列
*/
public static final String MALL_ORDER_DLX_QUEUE="mall.order.dlx.queue";

/**
* 死信交换机
*/
public static final String MALL_ORDER_DLX_EXCHANGE="mall.order.dlx.exchange";

/**
* 死信routing key
*/
public static final String MALL_ORDER_DLX_ROUTING_KEY="mall.order.dlx.routing.key";

@Bean
public Queue dlxQueue(){
return new Queue(MALL_ORDER_DLX_QUEUE);
}

@Bean
public DirectExchange dlxExchange(){
return new DirectExchange(MALL_ORDER_DLX_EXCHANGE);
}

@Bean
public Binding bindingDlx(){
return BindingBuilder
.bind(dlxQueue())
.to(dlxExchange())
.with(MALL_ORDER_DLX_ROUTING_KEY);
}

}

application.yml配置

1
2
3
4
5
6
7
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin
virtual-host: my_vhost

测试

1.Producer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
@AllArgsConstructor
@Slf4j
public class ProducerDemo {

RabbitTemplate rabbitTemplate;


public void send(String orderNo) {
rabbitTemplate.convertAndSend(
DelayQueueRabbitConfig.MALL_ORDER_DELAY_EXCHANGE,
DelayQueueRabbitConfig.MALL_ORDER_DELAY_ROUTING_KEY,
orderNo,
message -> {
message.getMessageProperties().setExpiration("2000");
return message;
}
);
log.info("send orderNo:{}", orderNo);
}

}

2.Consumer

1
2
3
4
5
6
7
8
9
10
@Component
@Slf4j
public class ConsumerDemo {

@RabbitListener(queues = DelayQueueDlxRabbitConfig.MALL_ORDER_DLX_QUEUE)
public void receive(String orderNo) {
log.info("order expire! orderNo:{}", orderNo);
}

}

image-20231002084046170

定时轮询

使用xxl-job,Quartz等中间件或框架定时轮询数据库订单,过期则取消,对于数据库压力较大。

JDK延迟队列DelayQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class DelayOrder implements Delayed {

private String orderNo;

private long timeout;

public DelayOrder(String orderNo, long timeout) {
this.orderNo = orderNo;
this.timeout = timeout + System.nanoTime();
}

@Override
public int compareTo(Delayed other) {
if (other == this) {
return 0;
}
DelayOrder t = (DelayOrder) other;
long d = (getDelay(TimeUnit.NANOSECONDS) - t.getDelay(TimeUnit.NANOSECONDS));
return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
}

@Override
public long getDelay(TimeUnit unit) {
return unit.convert(timeout - System.nanoTime(), TimeUnit.NANOSECONDS);
}

@Override
public String toString() {
return "DelayOrder{" +
"orderNo='" + orderNo + '\'' +
", timeout=" + timeout +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DelayQueueTest {
public static void main(String[] args) throws Exception {
DelayQueue<DelayOrder> queue = new DelayQueue<>();
for (int i = 0; i < 5; i++) {
DelayOrder cancelOrder = new DelayOrder("orderNo" + i, TimeUnit.NANOSECONDS.convert(10, TimeUnit.SECONDS));
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(time + ":生成了订单,10秒有效期,order:" + cancelOrder);
queue.put(cancelOrder);
Thread.sleep(1000);
}
try {
while (!queue.isEmpty()) {
DelayOrder order = queue.take();
String timeout = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(timeout + ":订单超时,order:" + order);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

image-20231002084835235