訂單 30 分鐘未支付就自動(dòng)取消?這五個(gè)狠招幫你搞定!
一、先搞明白需求本質(zhì)
咱先不著急上代碼,先把需求掰扯清楚。訂單自動(dòng)取消功能,核心就是在訂單創(chuàng)建后的 30 分鐘內(nèi),如果用戶沒(méi)完成支付,系統(tǒng)就自動(dòng)把這個(gè)訂單關(guān)掉。這里面有幾個(gè)關(guān)鍵點(diǎn)得注意:
- 時(shí)間準(zhǔn)確性:必須嚴(yán)格在 30 分鐘后執(zhí)行取消操作,不能早也不能晚,不然用戶體驗(yàn)可就不好了。比如用戶剛付完錢(qián),訂單就被取消了,那不得罵娘。
- 可靠性:不管系統(tǒng)是高峰期還是低谷期,都得保證該取消的訂單一定能取消,不能漏掉任何一個(gè)。要是有訂單沒(méi)取消,可能會(huì)導(dǎo)致庫(kù)存錯(cuò)誤、資金結(jié)算異常等問(wèn)題。
- 性能影響:不能因?yàn)檫@個(gè)功能把系統(tǒng)搞得卡頓,尤其是在訂單量大的時(shí)候,得考慮如何高效地處理這些定時(shí)任務(wù)。
二、五大狠招逐個(gè)解析
狠招一:數(shù)據(jù)庫(kù)輪詢 —— 簡(jiǎn)單直接但有點(diǎn)笨的辦法
這是最容易想到的辦法,就像班主任盯著全班學(xué)生抄作業(yè)一樣,定時(shí)去數(shù)據(jù)庫(kù)里查一遍所有未支付的訂單,看看有沒(méi)有超過(guò) 30 分鐘的,有的話就取消。
實(shí)現(xiàn)步驟
- 建一張訂單表,里面得有訂單狀態(tài)(比如未支付、已支付、已取消)、創(chuàng)建時(shí)間等字段。
- 寫(xiě)一個(gè)定時(shí)任務(wù),比如用 Spring 的 @Scheduled 注解,每隔一段時(shí)間(比如 1 分鐘)就去數(shù)據(jù)庫(kù)查詢一次狀態(tài)為未支付且創(chuàng)建時(shí)間超過(guò) 30 分鐘的訂單。
- 對(duì)查詢出來(lái)的訂單執(zhí)行取消操作,更新訂單狀態(tài),可能還需要釋放庫(kù)存、發(fā)送通知等。
代碼示例
@Service
public class OrderCancelService {
@Autowired
private OrderRepository orderRepository;
@Scheduled(fixedRate = 60 * 1000) // 每分鐘執(zhí)行一次
public void cancelUnpaidOrders() {
Date thirtyMinutesAgo = new Date(System.currentTimeMillis() - 30 * 60 * 1000);
List<Order> unpaidOrders = orderRepository.findByStatusAndCreateTimeBefore(OrderStatus.UNPAID, thirtyMinutesAgo);
for (Order order : unpaidOrders) {
// 執(zhí)行取消邏輯
order.setStatus(OrderStatus.CANCELED);
// 釋放庫(kù)存等操作
releaseStock(order);
// 發(fā)送通知
sendCancelNotification(order);
orderRepository.save(order);
}
}
private void releaseStock(Order order) {
// 具體庫(kù)存釋放邏輯
}
private void sendCancelNotification(Order order) {
// 發(fā)送短信、APP通知等邏輯
}
}優(yōu)缺點(diǎn)分析
- 優(yōu)點(diǎn):簡(jiǎn)單易懂,不需要引入額外的中間件,對(duì)于小型系統(tǒng)來(lái)說(shuō),快速就能實(shí)現(xiàn)。
- 缺點(diǎn):太笨了!定時(shí)任務(wù)的間隔不好把握,間隔短了會(huì)頻繁查詢數(shù)據(jù)庫(kù),影響性能;間隔長(zhǎng)了又可能導(dǎo)致訂單取消不及時(shí)。而且如果訂單量很大,每次查詢都可能是全表掃描,數(shù)據(jù)庫(kù)壓力山大,就像讓一個(gè)小學(xué)生去搬一堆磚,累得氣喘吁吁。
狠招二:JDK 自帶定時(shí)器 —— 稍微聰明一點(diǎn)的本地方案
Java 自帶了一個(gè) Timer 類(lèi),可以實(shí)現(xiàn)定時(shí)任務(wù),相當(dāng)于在本地搞了個(gè)小鬧鐘,到時(shí)間就提醒系統(tǒng)去取消訂單。
實(shí)現(xiàn)原理
Timer 類(lèi)可以安排 TimerTask 任務(wù)在指定的時(shí)間執(zhí)行,或者周期性地執(zhí)行。我們可以在訂單創(chuàng)建的時(shí)候,啟動(dòng)一個(gè) Timer,讓它在 30 分鐘后執(zhí)行訂單取消任務(wù)。
實(shí)現(xiàn)步驟
- 訂單創(chuàng)建時(shí),獲取訂單的創(chuàng)建時(shí)間,計(jì)算出 30 分鐘后的執(zhí)行時(shí)間。
- 創(chuàng)建一個(gè) TimerTask 任務(wù),在 run 方法里實(shí)現(xiàn)訂單取消邏輯。
- 通過(guò) Timer 的 schedule 方法,把任務(wù)安排在計(jì)算好的時(shí)間執(zhí)行。
代碼示例
public class OrderService {
private Timer timer = new Timer("OrderCancelTimer");
public void createOrder(Order order) {
// 保存訂單到數(shù)據(jù)庫(kù)
saveOrder(order);
// 安排取消任務(wù)
scheduleCancelTask(order);
}
private void scheduleCancelTask(Order order) {
long delay = 30 * 60 * 1000; // 30分鐘
TimerTask task = new TimerTask() {
@Override
public void run() {
// 檢查訂單狀態(tài),防止重復(fù)取消或已支付的情況
Order existingOrder = getOrderById(order.getId());
if (existingOrder.getStatus() == OrderStatus.UNPAID) {
cancelOrder(existingOrder);
}
}
};
timer.schedule(task, delay);
}
private void cancelOrder(Order order) {
// 執(zhí)行取消邏輯,更新數(shù)據(jù)庫(kù)等
}
}優(yōu)缺點(diǎn)分析
- 優(yōu)點(diǎn):比數(shù)據(jù)庫(kù)輪詢更精準(zhǔn),每個(gè)訂單都有自己的 “鬧鐘”,到時(shí)間就執(zhí)行,不會(huì)有延遲。而且是 JDK 自帶的,不需要額外依賴。
- 缺點(diǎn):局限性很大,只適用于單節(jié)點(diǎn)部署的系統(tǒng)。如果是分布式系統(tǒng),每個(gè)節(jié)點(diǎn)都得自己管理定時(shí)器,任務(wù)無(wú)法共享,容易出現(xiàn)重復(fù)執(zhí)行或者漏執(zhí)行的情況。而且 Timer 線程是非守護(hù)線程,如果程序不關(guān)閉,它會(huì)一直運(yùn)行,可能會(huì)有資源泄漏的問(wèn)題,就像你養(yǎng)了一堆小寵物,卻不管它們,最后家里亂成一團(tuán)。
狠招三:消息隊(duì)列延遲隊(duì)列 —— 分布式場(chǎng)景的好幫手
現(xiàn)在很多系統(tǒng)都是分布式部署的,這時(shí)候就需要一個(gè)分布式的解決方案,延遲隊(duì)列就派上用場(chǎng)了。比如 RabbitMQ 的死信隊(duì)列、RocketMQ 的延遲消息,都可以實(shí)現(xiàn)這個(gè)功能。
以 RabbitMQ 死信隊(duì)列為例
- 什么是死信隊(duì)列:死信隊(duì)列就是當(dāng)消息成為死信后,會(huì)被發(fā)送到的那個(gè)隊(duì)列。而消息成為死信的原因有很多,比如消息被拒絕、超時(shí)未消費(fèi)等。我們可以利用消息超時(shí)未消費(fèi)這一點(diǎn)來(lái)實(shí)現(xiàn)延遲隊(duì)列。
- 實(shí)現(xiàn)步驟:
創(chuàng)建一個(gè)普通隊(duì)列(死信源隊(duì)列),設(shè)置隊(duì)列的過(guò)期時(shí)間(TTL)為 30 分鐘。
創(chuàng)建一個(gè)死信隊(duì)列,用于接收過(guò)期的消息。
將死信源隊(duì)列和死信隊(duì)列綁定,當(dāng)死信源隊(duì)列中的消息過(guò)期后,會(huì)自動(dòng)轉(zhuǎn)發(fā)到死信隊(duì)列。
消費(fèi)者監(jiān)聽(tīng)死信隊(duì)列,當(dāng)收到消息時(shí),執(zhí)行訂單取消邏輯。
代碼示例(RabbitMQ)
// 配置類(lèi)
@Configuration
public class RabbitMQConfig {
// 死信源隊(duì)列
public static final String DEAD_LETTER_QUEUE = "dead_letter_queue";
// 死信交換器
public static final String DEAD_LETTER_EXCHANGE = "dead_letter_exchange";
// 死信路由鍵
public static final String DEAD_LETTER_ROUTING_KEY = "dead_letter_routing_key";
// 真正的死信隊(duì)列
public static final String REAL_DEAD_LETTER_QUEUE = "real_dead_letter_queue";
@Bean
public Queue deadLetterQueue() {
Map<String, Object> arguments = new HashMap<>();
// 設(shè)置隊(duì)列過(guò)期時(shí)間30分鐘
arguments.put("x-message-ttl", 30 * 60 * 1000);
// 設(shè)置死信交換器
arguments.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
// 設(shè)置死信路由鍵
arguments.put("x-dead-letter-routing-key", DEAD_LETTER_ROUTING_KEY);
return new Queue(DEAD_LETTER_QUEUE, true, false, false, arguments);
}
@Bean
public Exchange deadLetterExchange() {
return ExchangeBuilder.directExchange(DEAD_LETTER_EXCHANGE).durable(true).build();
}
@Bean
public Queue realDeadLetterQueue() {
return new Queue(REAL_DEAD_LETTER_QUEUE, true);
}
@Bean
public Binding binding() {
return BindingBuilder.bind(realDeadLetterQueue()).to(deadLetterExchange()).with(DEAD_LETTER_ROUTING_KEY).noargs();
}
}
// 生產(chǎn)者,訂單創(chuàng)建時(shí)發(fā)送消息到死信源隊(duì)列
@Service
public class OrderProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrderToDeadLetterQueue(Order order) {
// 將訂單信息轉(zhuǎn)換為JSON
String orderJson = JSON.toJSONString(order);
rabbitTemplate.convertAndSend(RabbitMQConfig.DEAD_LETTER_EXCHANGE, RabbitMQConfig.DEAD_LETTER_ROUTING_KEY, orderJson);
}
}
// 消費(fèi)者,監(jiān)聽(tīng)真正的死信隊(duì)列
@Service
public class OrderConsumer {
@RabbitListener(queues = RabbitMQConfig.REAL_DEAD_LETTER_QUEUE)
public void handleDeadLetterMessage(String orderJson) {
Order order = JSON.parseObject(orderJson, Order.class);
// 執(zhí)行訂單取消邏輯
cancelOrder(order);
}
}優(yōu)缺點(diǎn)分析
- 優(yōu)點(diǎn):適合分布式系統(tǒng),解耦了訂單創(chuàng)建和訂單取消邏輯,消息隊(duì)列可以承載大量的延遲任務(wù),性能較好。而且通過(guò)設(shè)置 TTL,能比較準(zhǔn)確地控制延遲時(shí)間。
- 缺點(diǎn):不同的消息隊(duì)列實(shí)現(xiàn)方式略有不同,比如 RabbitMQ 的 TTL 是隊(duì)列級(jí)別的,一旦設(shè)置,隊(duì)列里所有消息的過(guò)期時(shí)間都一樣,不夠靈活;RocketMQ 雖然支持不同的延遲級(jí)別,但需要提前配置好,不能動(dòng)態(tài)設(shè)置延遲時(shí)間。而且引入消息隊(duì)列會(huì)增加系統(tǒng)的復(fù)雜度,需要處理消息的可靠性、重復(fù)消費(fèi)等問(wèn)題,就像找了個(gè)幫手,但這個(gè)幫手有時(shí)候也會(huì)鬧點(diǎn)小脾氣,得花時(shí)間磨合。
狠招四:分布式定時(shí)器 —— 專業(yè)的定時(shí)任務(wù)框架
如果系統(tǒng)是分布式的,而且定時(shí)任務(wù)很多,要求也比較高,那就可以用專業(yè)的分布式定時(shí)器框架,比如 Quartz、Elastic-Job、XXL-JOB 等。這里以 Quartz 為例來(lái)看看。
Quartz 實(shí)現(xiàn)原理
Quartz 是一個(gè)功能強(qiáng)大的開(kāi)源作業(yè)調(diào)度框架,支持分布式部署。它有一個(gè)調(diào)度器(Scheduler),可以管理多個(gè)作業(yè)(Job)和觸發(fā)器(Trigger)。觸發(fā)器可以設(shè)置觸發(fā)時(shí)間,比如在指定時(shí)間執(zhí)行一次,或者周期性執(zhí)行。作業(yè)就是具體要執(zhí)行的任務(wù)。
實(shí)現(xiàn)步驟
- 引入 Quartz 依賴。
- 創(chuàng)建 Job 類(lèi),實(shí)現(xiàn)具體的訂單取消邏輯。
- 在訂單創(chuàng)建時(shí),創(chuàng)建 Trigger 和 JobDetail,設(shè)置觸發(fā)時(shí)間為訂單創(chuàng)建時(shí)間 + 30 分鐘,然后將它們注冊(cè)到 Scheduler 中。
- 配置 Quartz 的集群,確保在分布式環(huán)境下任務(wù)不會(huì)重復(fù)執(zhí)行。
代碼示例
// Job類(lèi)
public class OrderCancelJob implements Job {
@Autowired
private OrderService orderService;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String orderId = dataMap.getString("orderId");
orderService.cancelOrder(orderId);
}
}
// 訂單創(chuàng)建時(shí)調(diào)度任務(wù)
public class OrderService {
@Autowired
private Scheduler scheduler;
public void createOrder(Order order) {
// 保存訂單
saveOrder(order);
// 調(diào)度取消任務(wù)
scheduleCancelJob(order);
}
private void scheduleCancelJob(Order order) throws SchedulerException {
// 創(chuàng)建JobDetail
JobDetail jobDetail = JobBuilder.newJob(OrderCancelJob.class)
.withIdentity("orderCancelJob_" + order.getId(), "orderCancelGroup")
.usingJobData("orderId", order.getId().toString())
.build();
// 創(chuàng)建Trigger,30分鐘后觸發(fā)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("orderCancelTrigger_" + order.getId(), "orderCancelGroup")
.startAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.build();
// 將Job和Trigger注冊(cè)到Scheduler
scheduler.scheduleJob(jobDetail, trigger);
}
}
// Quartz集群配置(application.properties)
# Quartz配置
quartz.job-store-type=jdbc
quartz.data-source=quartzDataSource
quartz.jdbc.driver=com.mysql.cj.jdbc.Driver
quartz.jdbc.url=jdbc:mysql://localhost:3306/quartz_db?useUnicode=true&characterEncoding=utf-8
quartz.jdbc.user=root
quartz.jdbc.password=123456
# 啟用集群
quartz.scheduler.instanceName=ClusterScheduler
quartz.scheduler.instanceId=AUTO
quartz.job-store-is-clustered=true優(yōu)缺點(diǎn)分析
- 優(yōu)點(diǎn):功能強(qiáng)大,支持分布式集群,任務(wù)調(diào)度精準(zhǔn),可配置性高,能處理大量的定時(shí)任務(wù)。而且有豐富的監(jiān)聽(tīng)器和管理接口,方便監(jiān)控和管理。
- 缺點(diǎn):引入 Quartz 框架需要學(xué)習(xí)成本,配置相對(duì)復(fù)雜,尤其是集群環(huán)境下的配置。而且如果任務(wù)量非常大,數(shù)據(jù)庫(kù)中存儲(chǔ)的 Trigger 和 Job 數(shù)據(jù)會(huì)越來(lái)越多,需要定期清理,否則會(huì)影響性能,就像家里東西太多不收拾,最后找東西都難。
狠招五:Redis 過(guò)期監(jiān)聽(tīng) —— 利用緩存特性實(shí)現(xiàn)
Redis 是一個(gè)高性能的鍵值對(duì)數(shù)據(jù)庫(kù),它支持為鍵設(shè)置過(guò)期時(shí)間,當(dāng)鍵過(guò)期時(shí),可以通過(guò)發(fā)布訂閱模式通知客戶端。我們可以利用這個(gè)特性來(lái)實(shí)現(xiàn)訂單的自動(dòng)取消。
實(shí)現(xiàn)原理
- 在訂單創(chuàng)建時(shí),將訂單信息存儲(chǔ)到 Redis 中,并設(shè)置 30 分鐘的過(guò)期時(shí)間。
- 開(kāi)啟 Redis 的過(guò)期鍵通知功能,當(dāng)訂單鍵過(guò)期時(shí),Redis 會(huì)發(fā)布一個(gè)事件。
- 客戶端監(jiān)聽(tīng)這個(gè)事件,收到事件后,執(zhí)行訂單取消邏輯。
實(shí)現(xiàn)步驟
- 配置 Redis,開(kāi)啟過(guò)期鍵通知。在 redis.conf 中設(shè)置:
notify-keyspace-events Ex然后重啟 Redis 服務(wù)。
- 訂單創(chuàng)建時(shí),將訂單 ID 作為鍵,存儲(chǔ)到 Redis 中,設(shè)置過(guò)期時(shí)間 30 分鐘??梢赃x擇是否存儲(chǔ)訂單的其他信息,也可以只存儲(chǔ)訂單 ID,取消時(shí)再?gòu)臄?shù)據(jù)庫(kù)查詢?cè)敿?xì)信息。
- 使用 Redis 的發(fā)布訂閱功能,創(chuàng)建一個(gè)監(jiān)聽(tīng)器,監(jiān)聽(tīng)鍵過(guò)期事件。
- 監(jiān)聽(tīng)器收到事件后,獲取訂單 ID,執(zhí)行取消邏輯。
代碼示例(Spring Boot 集成 Redis)
// 訂單創(chuàng)建時(shí)存儲(chǔ)到Redis
@Service
public class OrderService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void createOrder(Order order) {
// 保存訂單到數(shù)據(jù)庫(kù)
saveOrderToDatabase(order);
// 將訂單ID存儲(chǔ)到Redis,設(shè)置30分鐘過(guò)期
stringRedisTemplate.opsForValue().set("order:unpaid:" + order.getId(), "1", 30, TimeUnit.MINUTES);
}
}
// Redis監(jiān)聽(tīng)器
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
private final ApplicationEventPublisher applicationEventPublisher;
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer, ApplicationEventPublisher applicationEventPublisher) {
super(listenerContainer);
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void onMessage(Message message, byte[] pattern) {
// 獲取過(guò)期的鍵
String expiredKey = message.toString();
if (expiredKey.startsWith("order:unpaid:")) {
String orderId = expiredKey.split(":")[2];
// 發(fā)布訂單過(guò)期事件
applicationEventPublisher.publishEvent(new OrderExpiredEvent(this, orderId));
}
}
}
// 訂單過(guò)期事件處理
@Service
public class OrderExpiredEventHandler {
@EventListener
public void handleOrderExpiredEvent(OrderExpiredEvent event) {
String orderId = event.getOrderId();
// 查詢訂單狀態(tài),防止已支付的情況
Order order = getOrderFromDatabase(orderId);
if (order.getStatus() == OrderStatus.UNPAID) {
cancelOrder(order);
}
}
}優(yōu)缺點(diǎn)分析
- 優(yōu)點(diǎn):利用 Redis 的高性能和過(guò)期通知特性,能快速處理大量的訂單過(guò)期事件,適用于高并發(fā)場(chǎng)景。而且實(shí)現(xiàn)相對(duì)簡(jiǎn)單,不需要引入復(fù)雜的框架,只需要依賴 Redis 即可。
- 缺點(diǎn):Redis 的過(guò)期通知不是實(shí)時(shí)的,可能會(huì)有一定的延遲,因?yàn)?Redis 是單線程處理,只有在處理到過(guò)期鍵時(shí)才會(huì)發(fā)布事件。另外,如果系統(tǒng)對(duì) Redis 的依賴很強(qiáng),一旦 Redis 出現(xiàn)故障,可能會(huì)影響訂單取消功能,需要做好容災(zāi)處理,就像你太依賴一個(gè)朋友,他要是生病了,你可能就麻煩了。
三、五大方案對(duì)比與選擇建議
方案 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場(chǎng)景 |
數(shù)據(jù)庫(kù)輪詢 | 簡(jiǎn)單易實(shí)現(xiàn),無(wú)需額外依賴 | 性能差,實(shí)時(shí)性低,訂單量大時(shí)壓力大 | 小型系統(tǒng),訂單量少 |
JDK 自帶定時(shí)器 | 精準(zhǔn),單節(jié)點(diǎn)適用 | 分布式支持差,資源管理麻煩 | 單節(jié)點(diǎn)應(yīng)用,定時(shí)任務(wù)少 |
消息隊(duì)列延遲隊(duì)列 | 分布式支持好,解耦度高 | 不同 MQ 實(shí)現(xiàn)有局限,復(fù)雜度增加 | 分布式系統(tǒng),對(duì)解耦有要求 |
分布式定時(shí)器 | 功能強(qiáng)大,支持集群,可配置性高 | 學(xué)習(xí)成本高,配置復(fù)雜 | 大型分布式系統(tǒng),定時(shí)任務(wù)多 |
Redis 過(guò)期監(jiān)聽(tīng) | 高性能,適合高并發(fā) | 通知有延遲,依賴 Redis | 高并發(fā)場(chǎng)景,對(duì)實(shí)時(shí)性要求不是極高 |
選擇的時(shí)候可以根據(jù)自己的系統(tǒng)規(guī)模、并發(fā)量、架構(gòu)復(fù)雜度來(lái)決定。如果是小型系統(tǒng),訂單量不大,用數(shù)據(jù)庫(kù)輪詢或者 JDK 定時(shí)器就能搞定;要是分布式系統(tǒng),訂單量中等,消息隊(duì)列延遲隊(duì)列是個(gè)不錯(cuò)的選擇;如果是大型分布式系統(tǒng),定時(shí)任務(wù)很多,對(duì)可靠性和功能要求高,那就選分布式定時(shí)器;要是高并發(fā)場(chǎng)景,Redis 過(guò)期監(jiān)聽(tīng)則更合適。
四、踩坑指南
- 冪等性問(wèn)題:不管用哪種方案,都要保證訂單取消操作是冪等的,也就是多次執(zhí)行和執(zhí)行一次的結(jié)果是一樣的。比如在取消訂單前,先檢查訂單狀態(tài),只有未支付的訂單才取消,防止重復(fù)取消已支付或已取消的訂單。
- 事務(wù)處理:在執(zhí)行訂單取消時(shí),涉及到更新訂單狀態(tài)、釋放庫(kù)存、通知用戶等操作,要保證這些操作要么全部成功,要么全部失敗,避免出現(xiàn)部分成功的情況。
- 性能優(yōu)化:對(duì)于數(shù)據(jù)庫(kù)輪詢和分布式定時(shí)器等方案,要做好數(shù)據(jù)庫(kù)索引優(yōu)化,避免全表掃描;對(duì)于消息隊(duì)列和 Redis 方案,要合理設(shè)置過(guò)期時(shí)間和隊(duì)列參數(shù),提高系統(tǒng)吞吐量。
- 監(jiān)控與報(bào)警:一定要對(duì)訂單取消功能進(jìn)行監(jiān)控,比如統(tǒng)計(jì)每分鐘取消的訂單量、失敗的訂單量等,一旦出現(xiàn)異常,及時(shí)報(bào)警處理,避免大量訂單未取消的情況發(fā)生。
五、總結(jié)
訂單 30 分鐘未支付自動(dòng)取消這個(gè)功能,看似簡(jiǎn)單,背后卻有很多技術(shù)細(xì)節(jié)需要考慮。從簡(jiǎn)單的數(shù)據(jù)庫(kù)輪詢到復(fù)雜的分布式定時(shí)器,每種方案都有自己的適用場(chǎng)景和優(yōu)缺點(diǎn)。大家在實(shí)際開(kāi)發(fā)中,要根據(jù)自己的系統(tǒng)情況選擇合適的方案,同時(shí)注意冪等性、事務(wù)處理、性能優(yōu)化和監(jiān)控報(bào)警等問(wèn)題。






























