当前位置:网站首页>Solution to order timeout unpaid
Solution to order timeout unpaid
2022-07-05 12:17:00 【Xujunsheng】
The solution of order timeout and unpaid
In the previous article Third party payment interface design I left a question in : Order closed over time . This question is often asked in interviews , In this article, we will explain .
There are other scenarios similar to the overtime closing of orders :
- Taobao automatically confirms receipt ;
- WeChat Lucky Money 24 Hours not checked , Need to delay the return ;
- When didi made an appointment to take a taxi , There is no driver to answer the order in ten minutes , The system will automatically cancel .
In view of the above , It's the target time , The system automatically triggers tasks performed on behalf of users , There's a professional name : Delayed tasks .
For this kind of requirement, the first thing we think of is to use Timing task , Scan the qualified data in the database , And update it .
The difference between delayed tasks and scheduled tasks :
- Timed tasks have a fixed trigger time , Delayed tasks are not fixed , It depends on the trigger time of the business event .( such as , Cancel the order half an hour after the order is generated );
- Timed tasks are periodic , After the delayed task is triggered , It's over , It's usually one-off ;
- Timed tasks generally deal with multiple tasks , A delayed task is usually a task .
Let's take a look at the implementation of timed tasks .
Timed task implementation
There are several ways to implement scheduled tasks :
- JDK Bring their own Timer Realization
- Quartz Framework implementations
- Spring3.0 Later brought task
- Distributed task scheduling :XXL-Job
The general logic is as follows :
Suppose the order form :t_order(id,end_time,status)
;
Data scanning :
select id from t_order where end_time>=30 and status= The initial state ;
modify :
update t_order set status= end where id in ( Overtime orders id);
notes : If the number of overtime orders is large , You need paging query .
The advantage of this method is that it is easy to realize , Support distributed / Cluster environment .
shortcoming :
- Constantly scan the database through polling , If the amount of data is large , And the execution interval of the task is short , It will cause some pressure on the database ;
- The interval granularity is not easy to set ;
- There is a delay : If you set 5 Scan every minute , Then the worst delay time is 5 minute .
Passive cancellation
Passive cancellation is consistent with lazy loading . When the user queries the order , To judge whether the order is overtime , If it is , Take the logic of timeout .
This way, Rely on user's query operation . If the user does not query , Then the order will never be canceled .
This method is simple to implement , There is no need to add additional processing operations . The disadvantage is low timeliness , Affect the user experience .
It's also useful now Timing task + Passive cancellation The combination of .
The above is about the solution of timed tasks , Now let's talk about the common technical implementation of delayed tasks .
JDK The delay queue for
adopt JDK Provided DelayQueue
Class to achieve .DelayQueue
Is a support delay to get elements , Infinite blocking queue .
Elements in the queue must implement Delayed
Interface , And rewrite getDelay(TimeUnit)
and compareTo(Delayed)
Method .
Elements can only be removed from the queue when the delay expires . And the queue is orderly , The delay expiration time of elements placed by the queue head is the longest .
Code demonstration
Define element classes , As an element of the queue :
public class MyDelayedTask implements Delayed {
private String orderId;
private long startTime;
private long delayMillis;
public MyDelayedTask(String orderId, long delayMillis) {
this.orderId = orderId;
this.startTime = System.currentTimeMillis();
this.delayMillis = delayMillis;
}
/** * Get delay time * * @param unit * @return */
@Override
public long getDelay(TimeUnit unit) {
return unit.convert((startTime + delayMillis) - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
/** * The elements in the queue are sorted by * * @param o * @return */
@Override
public int compareTo(Delayed o) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
public void exec() {
System.out.println(orderId + " The order number is to be deleted !!!");
}
}
test :
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
list.add("00000001");
list.add("00000002");
list.add("00000003");
list.add("00000004");
list.add("00000005");
long start = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
// Delay 3s
delayQueue.put(new MyDelayedTask(list.get(i), 3000));
delayQueue.take().exec();
System.out.println("After " + (System.currentTimeMillis() - start) + " MilliSeconds");
}
}
Results the print :
00000001 The order number is to be deleted !!!
After 3004 MilliSeconds
00000002 The order number is to be deleted !!!
After 6009 MilliSeconds
00000003 The order number is to be deleted !!!
After 9012 MilliSeconds
00000004 The order number is to be deleted !!!
After 12018 MilliSeconds
00000005 The order number is to be deleted !!!
After 15020 MilliSeconds
advantage : Efficient , Low task trigger time delay .
shortcoming :
- After the server restarts , The data is all gone , Fear of downtime
- Cluster expansion is quite troublesome
- Because it's an infinite line , If there are too many tasks , Then it's easy to OOM abnormal
- High code complexity
Time wheel algorithm
Time wheel is a kind of scheduling model which can efficiently use thread resources for batch scheduling . Bind a large number of scheduling tasks to the same scheduler , Use this scheduler to manage all tasks (manager), Trigger (trigger) And run (runnable). Be able to manage all kinds of delay tasks efficiently , Cycle task , Inform the task and so on .
shortcoming , The time accuracy of the time wheel scheduler may not be very high , Scheduling tasks with high precision requirements may not be suitable for . Because the accuracy of the time wheel algorithm depends on , Period of time “ The pointer ” The minimum particle size of the unit , For example, the lattice of the time wheel jumps once a second , Then tasks with scheduling accuracy less than one second cannot be scheduled by the time round . And the time round algorithm does not do downtime backup , Therefore, it is impossible to resume the task rescheduling after downtime .
Code demonstration
rely on :
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.69.Final</version>
</dependency>
Demo:
public class HashedWheelTimerTest {
private static final long start = System.currentTimeMillis();
public static void main(String[] args) {
// initialization netty Time wheel
HashedWheelTimer timer = new HashedWheelTimer(1, // The time interval
TimeUnit.SECONDS,
10); // Number of slots in the time wheel
TimerTask task1 = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
System.out.println(" It's over " + costTime() + " second ,task1 Start execution ");
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
System.out.println(" It's over " + costTime() + " second ,task2 Start execution ");
}
};
TimerTask task3 = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
System.out.println(" It's over " + costTime() + " second ,task3 Start execution ");
}
};
// Add the task to the delay queue
timer.newTimeout(task1, 0, TimeUnit.SECONDS);
timer.newTimeout(task2, 3, TimeUnit.SECONDS);
timer.newTimeout(task3, 15, TimeUnit.SECONDS);
}
private static Long costTime() {
return (System.currentTimeMillis() - start) / 1000;
}
}
Redis zset Implement delayed tasks
zset
Is an ordered set ,ZSet In structure , Every element (member) There will be a score (score), Then all elements are arranged according to the size of the score .
We set the order timeout time stamp and order number to score
and member
. That is, the records in the set list are sorted by execution time , We just need to take less than the current time .
Code demonstration
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.UUID;
@Configuration
public class RedisDelayDemo {
@Autowired
private RedisTemplate redisTemplate;
public void setDelayTasks(long delayTime) {
String orderId = UUID.randomUUID().toString();
Boolean addResult = redisTemplate.opsForZSet().add("delayQueue", orderId, System.currentTimeMillis() + delayTime);
if (addResult) {
System.out.println(" Task added successfully !" + orderId + ", The current time is " + LocalDateTime.now());
}
}
/** * Listen for delayed messages */
public void listenDelayLoop() {
while (true) {
// Get a point-to-point message
Set<String> set = redisTemplate.opsForZSet().rangeByScore("delayQueue", 0, System.currentTimeMillis(), 0, 1);
// without , Just wait
if (set.isEmpty()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Carry on
continue;
}
// Get specific messages key
String it = set.iterator().next();
// Delete successful
if (redisTemplate.opsForZSet().remove("delayQueue", it) > 0) {
// Get the mission
System.out.println(" Message expiration " + it + ", Time is " + LocalDateTime.now());
}
}
}
}
test :
@RequestMapping("/delayTest")
public void delayTest() {
delayDemo.setDelayTasks(5000L);
delayDemo.listenDelayLoop();
}
Results the print :
Task added successfully !e99961a0-fc1d-43d4-a83e-8db5fb6b3273, The current time is 2021-10-24T12:06:59.037363700
Message expiration e99961a0-fc1d-43d4-a83e-8db5fb6b3273, Time is 2021-10-24T12:07:04.097486
advantage :
- Convenient cluster expansion
- High time accuracy
- Don't worry about downtime
shortcoming : It needs to be extra redis maintain . Under the condition of high concurrency , Multiple consumers may get the same order number . This situation can be handled by adding a distributed lock , however , Serious performance degradation .
MQ Delay message
We can go through MQ Delay message implementation , With RocketMQ give an example .
Usually, messages will be consumed by consumers immediately after delivery , The delay message is delivered , The specified delay level needs to be set ( Different delay levels correspond to different delay times ), That is, the message will not be consumed by consumers until a specific time interval , This shifts the pressure at the database level to MQ in , You don't need a handwriting timer , Reduced business complexity , meanwhile MQ With peak shaving function , Able to cope well with business peak .
Code demonstration
rely on :
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>5.0.0-PREVIEW</version>
</dependency>
producer demo:
@Component
public class ProducerSchedule {
private DefaultMQProducer producer;
@Value("${rocketmq.producer.producer-group}")
private String producerGroup;
@Value("${rocketmq.namesrv-addr}")
private String nameSrvAddr;
public ProducerSchedule() {
}
/** * Producer structure * * @PostConstruct This annotation is used to modify a non static void() Method Bean Execution order of initialization : * Constructor( Construction method ) -> @Autowired( Dependency injection ) -> @PostConstruct( Method of annotation ) */
@PostConstruct
public void defaultMQProducer() {
if (Objects.isNull(this.producer)) {
this.producer = new DefaultMQProducer(this.producerGroup);
this.producer.setNamesrvAddr(this.nameSrvAddr);
}
try {
this.producer.start();
System.out.println("Producer start");
} catch (MQClientException e) {
e.printStackTrace();
}
}
/** * News release * * @param topic * @param messageText * @return */
public String send(String topic, String messageText) {
Message message = new Message(topic, messageText.getBytes());
/** * Delay message level setting * messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h */
message.setDelayTimeLevel(4);
SendResult result = null;
try {
result = this.producer.send(message);
System.out.println(" Return information :" + JSON.toJSONString(result));
} catch (Exception e) {
e.printStackTrace();
}
return result.getMsgId();
}
}
consumer demo:
@Component
public class ConsumerSchedule implements CommandLineRunner {
@Value("${rocketmq.consumer.consumer-group}")
private String consumerGroup;
@Value("${rocketmq.namesrv-addr}")
private String nameSrvAddr;
@Value("${rocketmq.topic}")
private String rocketmqTopic;
public void messageListener() throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(this.consumerGroup);
consumer.setNamesrvAddr(this.nameSrvAddr);
/** * Subscribe to topics */
consumer.subscribe(rocketmqTopic, "*");
/** * Set the number of consumption messages */
consumer.setConsumeMessageBatchMaxSize(1);
/** * Register message monitor */
consumer.registerMessageListener((MessageListenerConcurrently) (messages, context) -> {
for (Message message : messages) {
System.out.println(" Listen to the news :" + new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
}
@Override
public void run(String... args) throws Exception {
this.messageListener();
}
}
The method to set the message delay level is setDelayTimeLevel()
, at present RocketMQ Delay messages with arbitrary time intervals are not supported , Only certain levels of delayed messages are supported .
At the end
It's today [2021-10-24], Wish you all Happy programmer's Day , Achieve your small goals as soon as possible !!!
If you want to see more quality original articles , Welcome to my official account. 「ShawnBlog」.
边栏推荐
- Seven polymorphisms
- GPS data format conversion [easy to understand]
- MySQL basic operation -dql
- Intern position selection and simplified career development planning in Internet companies
- Splunk configuration 163 mailbox alarm
- 【ijkplayer】when i compile file “compile-ffmpeg.sh“ ,it show error “No such file or directory“.
- Embedded software architecture design - message interaction
- 信息服务器怎么恢复,服务器数据恢复怎么弄[通俗易懂]
- II. Data type
- Read and understand the rendering mechanism and principle of flutter's three trees
猜你喜欢
MySQL transaction
互联网公司实习岗位选择与简易版职业发展规划
Principle of redis cluster mode
The survey shows that traditional data security tools cannot resist blackmail software attacks in 60% of cases
Simply solve the problem that the node in the redis cluster cannot read data (error) moved
Automated test lifecycle
[pytorch modifies the pre training model: there is little difference between the measured loading pre training model and the random initialization of the model]
Read and understand the rendering mechanism and principle of flutter's three trees
Linux安装部署LAMP(Apache+MySQL+PHP)
Yolov5 target detection neural network -- calculation principle of loss function
随机推荐
Thoughts and suggestions on the construction of intelligent management and control system platform for safe production in petrochemical enterprises
Uniapp + unicloud + Unipay realize wechat applet payment function
Is investment and finance suitable for girls? What financial products can girls buy?
Linux Installation and deployment lamp (apache+mysql+php)
MySQL view
Seven polymorphisms
Sentinel sentinel mechanism of master automatic election in redis master-slave
Read and understand the rendering mechanism and principle of flutter's three trees
Deep discussion on the decoding of sent protocol
强化学习-学习笔记3 | 策略学习
Matlab label2idx function (convert the label matrix into a cell array with linear index)
[untitled]
嵌入式软件架构设计-消息交互
How to clear floating?
调查显示传统数据安全工具在60%情况下无法抵御勒索软件攻击
Two minutes will take you to quickly master the project structure, resources, dependencies and localization of flutter
codeforces每日5题(均1700)-第五天
Time tools
How to recover the information server and how to recover the server data [easy to understand]
手机 CPU 架构类型了解