自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

訂單 30 分鐘未支付就自動取消?這五個狠招幫你搞定!

開發(fā) 前端
訂單 30 分鐘未支付自動取消這個功能,看似簡單,背后卻有很多技術細節(jié)需要考慮。從簡單的數(shù)據(jù)庫輪詢到復雜的分布式定時器,每種方案都有自己的適用場景和優(yōu)缺點。大家在實際開發(fā)中,要根據(jù)自己的系統(tǒng)情況選擇合適的方案,同時注意冪等性、事務處理、性能優(yōu)化和監(jiān)控報警等問題。
兄弟們,不知道大家有沒有這樣的經(jīng)歷:點外賣時選了半天,最后糾結癥發(fā)作沒付款,過一會兒再看訂單居然自己消失了;網(wǎng)上購物時加入購物車一頓操作,結果有事耽擱沒付錢,回來發(fā)現(xiàn)訂單 “不翼而飛”。其實這背后藏著一個重要的技術需求 —— 訂單 30 分鐘未支付自動取消。今天咱就來聊聊怎么用技術手段搞定這個需求,讓你的系統(tǒng)也能像這些大廠一樣智能。

一、先搞明白需求本質(zhì)

咱先不著急上代碼,先把需求掰扯清楚。訂單自動取消功能,核心就是在訂單創(chuàng)建后的 30 分鐘內(nèi),如果用戶沒完成支付,系統(tǒng)就自動把這個訂單關掉。這里面有幾個關鍵點得注意:

  1. 時間準確性:必須嚴格在 30 分鐘后執(zhí)行取消操作,不能早也不能晚,不然用戶體驗可就不好了。比如用戶剛付完錢,訂單就被取消了,那不得罵娘。
  2. 可靠性:不管系統(tǒng)是高峰期還是低谷期,都得保證該取消的訂單一定能取消,不能漏掉任何一個。要是有訂單沒取消,可能會導致庫存錯誤、資金結算異常等問題。
  3. 性能影響:不能因為這個功能把系統(tǒng)搞得卡頓,尤其是在訂單量大的時候,得考慮如何高效地處理這些定時任務。

二、五大狠招逐個解析

狠招一:數(shù)據(jù)庫輪詢 —— 簡單直接但有點笨的辦法

這是最容易想到的辦法,就像班主任盯著全班學生抄作業(yè)一樣,定時去數(shù)據(jù)庫里查一遍所有未支付的訂單,看看有沒有超過 30 分鐘的,有的話就取消。

實現(xiàn)步驟

  1. 建一張訂單表,里面得有訂單狀態(tài)(比如未支付、已支付、已取消)、創(chuàng)建時間等字段。
  2. 寫一個定時任務,比如用 Spring 的 @Scheduled 注解,每隔一段時間(比如 1 分鐘)就去數(shù)據(jù)庫查詢一次狀態(tài)為未支付且創(chuàng)建時間超過 30 分鐘的訂單。
  3. 對查詢出來的訂單執(zhí)行取消操作,更新訂單狀態(tài),可能還需要釋放庫存、發(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);
            // 釋放庫存等操作
            releaseStock(order);
            // 發(fā)送通知
            sendCancelNotification(order);
            orderRepository.save(order);
        }
    }
    private void releaseStock(Order order) {
        // 具體庫存釋放邏輯
    }
    private void sendCancelNotification(Order order) {
        // 發(fā)送短信、APP通知等邏輯
    }
}

優(yōu)缺點分析

  • 優(yōu)點:簡單易懂,不需要引入額外的中間件,對于小型系統(tǒng)來說,快速就能實現(xiàn)。
  • 缺點:太笨了!定時任務的間隔不好把握,間隔短了會頻繁查詢數(shù)據(jù)庫,影響性能;間隔長了又可能導致訂單取消不及時。而且如果訂單量很大,每次查詢都可能是全表掃描,數(shù)據(jù)庫壓力山大,就像讓一個小學生去搬一堆磚,累得氣喘吁吁。

狠招二:JDK 自帶定時器 —— 稍微聰明一點的本地方案

Java 自帶了一個 Timer 類,可以實現(xiàn)定時任務,相當于在本地搞了個小鬧鐘,到時間就提醒系統(tǒng)去取消訂單。

實現(xiàn)原理

Timer 類可以安排 TimerTask 任務在指定的時間執(zhí)行,或者周期性地執(zhí)行。我們可以在訂單創(chuàng)建的時候,啟動一個 Timer,讓它在 30 分鐘后執(zhí)行訂單取消任務。

實現(xiàn)步驟

  1. 訂單創(chuàng)建時,獲取訂單的創(chuàng)建時間,計算出 30 分鐘后的執(zhí)行時間。
  2. 創(chuàng)建一個 TimerTask 任務,在 run 方法里實現(xiàn)訂單取消邏輯。
  3. 通過 Timer 的 schedule 方法,把任務安排在計算好的時間執(zhí)行。

代碼示例

public class OrderService {
    private Timer timer = new Timer("OrderCancelTimer");
    public void createOrder(Order order) {
        // 保存訂單到數(shù)據(jù)庫
        saveOrder(order);
        // 安排取消任務
        scheduleCancelTask(order);
    }
    private void scheduleCancelTask(Order order) {
        long delay = 30 * 60 * 1000; // 30分鐘
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                // 檢查訂單狀態(tài),防止重復取消或已支付的情況
                Order existingOrder = getOrderById(order.getId());
                if (existingOrder.getStatus() == OrderStatus.UNPAID) {
                    cancelOrder(existingOrder);
                }
            }
        };
        timer.schedule(task, delay);
    }
    private void cancelOrder(Order order) {
        // 執(zhí)行取消邏輯,更新數(shù)據(jù)庫等
    }
}

優(yōu)缺點分析

  • 優(yōu)點:比數(shù)據(jù)庫輪詢更精準,每個訂單都有自己的 “鬧鐘”,到時間就執(zhí)行,不會有延遲。而且是 JDK 自帶的,不需要額外依賴。
  • 缺點:局限性很大,只適用于單節(jié)點部署的系統(tǒng)。如果是分布式系統(tǒng),每個節(jié)點都得自己管理定時器,任務無法共享,容易出現(xiàn)重復執(zhí)行或者漏執(zhí)行的情況。而且 Timer 線程是非守護線程,如果程序不關閉,它會一直運行,可能會有資源泄漏的問題,就像你養(yǎng)了一堆小寵物,卻不管它們,最后家里亂成一團。

狠招三:消息隊列延遲隊列 —— 分布式場景的好幫手

現(xiàn)在很多系統(tǒng)都是分布式部署的,這時候就需要一個分布式的解決方案,延遲隊列就派上用場了。比如 RabbitMQ 的死信隊列、RocketMQ 的延遲消息,都可以實現(xiàn)這個功能。

以 RabbitMQ 死信隊列為例

  1. 什么是死信隊列:死信隊列就是當消息成為死信后,會被發(fā)送到的那個隊列。而消息成為死信的原因有很多,比如消息被拒絕、超時未消費等。我們可以利用消息超時未消費這一點來實現(xiàn)延遲隊列。
  2. 實現(xiàn)步驟:

創(chuàng)建一個普通隊列(死信源隊列),設置隊列的過期時間(TTL)為 30 分鐘。

創(chuàng)建一個死信隊列,用于接收過期的消息。

將死信源隊列和死信隊列綁定,當死信源隊列中的消息過期后,會自動轉(zhuǎn)發(fā)到死信隊列。

消費者監(jiān)聽死信隊列,當收到消息時,執(zhí)行訂單取消邏輯。

代碼示例(RabbitMQ)

// 配置類
@Configuration
public class RabbitMQConfig {
    // 死信源隊列
    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";
    // 真正的死信隊列
    public static final String REAL_DEAD_LETTER_QUEUE = "real_dead_letter_queue";
    @Bean
    public Queue deadLetterQueue() {
        Map<String, Object> arguments = new HashMap<>();
        // 設置隊列過期時間30分鐘
        arguments.put("x-message-ttl", 30 * 60 * 1000);
        // 設置死信交換器
        arguments.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // 設置死信路由鍵
        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)建時發(fā)送消息到死信源隊列
@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);
    }
}
// 消費者,監(jiān)聽真正的死信隊列
@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)缺點分析

  • 優(yōu)點:適合分布式系統(tǒng),解耦了訂單創(chuàng)建和訂單取消邏輯,消息隊列可以承載大量的延遲任務,性能較好。而且通過設置 TTL,能比較準確地控制延遲時間。
  • 缺點:不同的消息隊列實現(xiàn)方式略有不同,比如 RabbitMQ 的 TTL 是隊列級別的,一旦設置,隊列里所有消息的過期時間都一樣,不夠靈活;RocketMQ 雖然支持不同的延遲級別,但需要提前配置好,不能動態(tài)設置延遲時間。而且引入消息隊列會增加系統(tǒng)的復雜度,需要處理消息的可靠性、重復消費等問題,就像找了個幫手,但這個幫手有時候也會鬧點小脾氣,得花時間磨合。

狠招四:分布式定時器 —— 專業(yè)的定時任務框架

如果系統(tǒng)是分布式的,而且定時任務很多,要求也比較高,那就可以用專業(yè)的分布式定時器框架,比如 Quartz、Elastic-Job、XXL-JOB 等。這里以 Quartz 為例來看看。

Quartz 實現(xiàn)原理

Quartz 是一個功能強大的開源作業(yè)調(diào)度框架,支持分布式部署。它有一個調(diào)度器(Scheduler),可以管理多個作業(yè)(Job)和觸發(fā)器(Trigger)。觸發(fā)器可以設置觸發(fā)時間,比如在指定時間執(zhí)行一次,或者周期性執(zhí)行。作業(yè)就是具體要執(zhí)行的任務。

實現(xiàn)步驟

  1. 引入 Quartz 依賴。
  2. 創(chuàng)建 Job 類,實現(xiàn)具體的訂單取消邏輯。
  3. 在訂單創(chuàng)建時,創(chuàng)建 Trigger 和 JobDetail,設置觸發(fā)時間為訂單創(chuàng)建時間 + 30 分鐘,然后將它們注冊到 Scheduler 中。
  4. 配置 Quartz 的集群,確保在分布式環(huán)境下任務不會重復執(zhí)行。

代碼示例

// Job類
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)建時調(diào)度任務
public class OrderService {
    @Autowired
    private Scheduler scheduler;
    public void createOrder(Order order) {
        // 保存訂單
        saveOrder(order);
        // 調(diào)度取消任務
        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注冊到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)缺點分析

  • 優(yōu)點:功能強大,支持分布式集群,任務調(diào)度精準,可配置性高,能處理大量的定時任務。而且有豐富的監(jiān)聽器和管理接口,方便監(jiān)控和管理。
  • 缺點:引入 Quartz 框架需要學習成本,配置相對復雜,尤其是集群環(huán)境下的配置。而且如果任務量非常大,數(shù)據(jù)庫中存儲的 Trigger 和 Job 數(shù)據(jù)會越來越多,需要定期清理,否則會影響性能,就像家里東西太多不收拾,最后找東西都難。

狠招五:Redis 過期監(jiān)聽 —— 利用緩存特性實現(xiàn)

Redis 是一個高性能的鍵值對數(shù)據(jù)庫,它支持為鍵設置過期時間,當鍵過期時,可以通過發(fā)布訂閱模式通知客戶端。我們可以利用這個特性來實現(xiàn)訂單的自動取消。

實現(xiàn)原理

  1. 在訂單創(chuàng)建時,將訂單信息存儲到 Redis 中,并設置 30 分鐘的過期時間。
  2. 開啟 Redis 的過期鍵通知功能,當訂單鍵過期時,Redis 會發(fā)布一個事件。
  3. 客戶端監(jiān)聽這個事件,收到事件后,執(zhí)行訂單取消邏輯。

實現(xiàn)步驟

  • 配置 Redis,開啟過期鍵通知。在 redis.conf 中設置:
notify-keyspace-events Ex

然后重啟 Redis 服務。

  • 訂單創(chuàng)建時,將訂單 ID 作為鍵,存儲到 Redis 中,設置過期時間 30 分鐘??梢赃x擇是否存儲訂單的其他信息,也可以只存儲訂單 ID,取消時再從數(shù)據(jù)庫查詢詳細信息。
  • 使用 Redis 的發(fā)布訂閱功能,創(chuàng)建一個監(jiān)聽器,監(jiān)聽鍵過期事件。
  • 監(jiān)聽器收到事件后,獲取訂單 ID,執(zhí)行取消邏輯。

代碼示例(Spring Boot 集成 Redis)

// 訂單創(chuàng)建時存儲到Redis
@Service
public class OrderService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    public void createOrder(Order order) {
        // 保存訂單到數(shù)據(jù)庫
        saveOrderToDatabase(order);
        // 將訂單ID存儲到Redis,設置30分鐘過期
        stringRedisTemplate.opsForValue().set("order:unpaid:" + order.getId(), "1", 30, TimeUnit.MINUTES);
    }
}
// Redis監(jiān)聽器
@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) {
        // 獲取過期的鍵
        String expiredKey = message.toString();
        if (expiredKey.startsWith("order:unpaid:")) {
            String orderId = expiredKey.split(":")[2];
            // 發(fā)布訂單過期事件
            applicationEventPublisher.publishEvent(new OrderExpiredEvent(this, orderId));
        }
    }
}
// 訂單過期事件處理
@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)缺點分析

  • 優(yōu)點:利用 Redis 的高性能和過期通知特性,能快速處理大量的訂單過期事件,適用于高并發(fā)場景。而且實現(xiàn)相對簡單,不需要引入復雜的框架,只需要依賴 Redis 即可。
  • 缺點:Redis 的過期通知不是實時的,可能會有一定的延遲,因為 Redis 是單線程處理,只有在處理到過期鍵時才會發(fā)布事件。另外,如果系統(tǒng)對 Redis 的依賴很強,一旦 Redis 出現(xiàn)故障,可能會影響訂單取消功能,需要做好容災處理,就像你太依賴一個朋友,他要是生病了,你可能就麻煩了。

三、五大方案對比與選擇建議

方案

優(yōu)點

缺點

適用場景

數(shù)據(jù)庫輪詢

簡單易實現(xiàn),無需額外依賴

性能差,實時性低,訂單量大時壓力大

小型系統(tǒng),訂單量少

JDK 自帶定時器

精準,單節(jié)點適用

分布式支持差,資源管理麻煩

單節(jié)點應用,定時任務少

消息隊列延遲隊列

分布式支持好,解耦度高

不同 MQ 實現(xiàn)有局限,復雜度增加

分布式系統(tǒng),對解耦有要求

分布式定時器

功能強大,支持集群,可配置性高

學習成本高,配置復雜

大型分布式系統(tǒng),定時任務多

Redis 過期監(jiān)聽

高性能,適合高并發(fā)

通知有延遲,依賴 Redis

高并發(fā)場景,對實時性要求不是極高

選擇的時候可以根據(jù)自己的系統(tǒng)規(guī)模、并發(fā)量、架構復雜度來決定。如果是小型系統(tǒng),訂單量不大,用數(shù)據(jù)庫輪詢或者 JDK 定時器就能搞定;要是分布式系統(tǒng),訂單量中等,消息隊列延遲隊列是個不錯的選擇;如果是大型分布式系統(tǒng),定時任務很多,對可靠性和功能要求高,那就選分布式定時器;要是高并發(fā)場景,Redis 過期監(jiān)聽則更合適。

四、踩坑指南

  1. 冪等性問題:不管用哪種方案,都要保證訂單取消操作是冪等的,也就是多次執(zhí)行和執(zhí)行一次的結果是一樣的。比如在取消訂單前,先檢查訂單狀態(tài),只有未支付的訂單才取消,防止重復取消已支付或已取消的訂單。
  2. 事務處理:在執(zhí)行訂單取消時,涉及到更新訂單狀態(tài)、釋放庫存、通知用戶等操作,要保證這些操作要么全部成功,要么全部失敗,避免出現(xiàn)部分成功的情況。
  3. 性能優(yōu)化:對于數(shù)據(jù)庫輪詢和分布式定時器等方案,要做好數(shù)據(jù)庫索引優(yōu)化,避免全表掃描;對于消息隊列和 Redis 方案,要合理設置過期時間和隊列參數(shù),提高系統(tǒng)吞吐量。
  4. 監(jiān)控與報警:一定要對訂單取消功能進行監(jiān)控,比如統(tǒng)計每分鐘取消的訂單量、失敗的訂單量等,一旦出現(xiàn)異常,及時報警處理,避免大量訂單未取消的情況發(fā)生。

五、總結

訂單 30 分鐘未支付自動取消這個功能,看似簡單,背后卻有很多技術細節(jié)需要考慮。從簡單的數(shù)據(jù)庫輪詢到復雜的分布式定時器,每種方案都有自己的適用場景和優(yōu)缺點。大家在實際開發(fā)中,要根據(jù)自己的系統(tǒng)情況選擇合適的方案,同時注意冪等性、事務處理、性能優(yōu)化和監(jiān)控報警等問題。

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2024-08-27 13:43:38

Spring系統(tǒng)業(yè)務

2023-10-09 16:35:19

方案Spring支付

2021-09-07 08:14:26

訂單超時未支付

2020-10-21 09:25:01

互聯(lián)網(wǎng)訂單自動關閉

2023-11-20 08:39:24

Spring定時任務

2023-11-27 08:15:26

Spring訂單取消

2021-01-18 11:41:22

SQL數(shù)據(jù)庫編程語言

2024-02-26 08:50:37

訂單自動取消消息

2017-10-12 13:50:49

大數(shù)據(jù)企業(yè)分析專家

2025-03-18 10:25:59

2016-08-24 11:46:28

移動應用DevOps私有云

2017-10-20 23:50:36

大數(shù)據(jù)數(shù)據(jù)分析企業(yè)

2022-12-01 08:25:03

訂單超時定時任務

2023-01-30 08:12:53

訂單超時自動取消延長訂單

2017-09-26 10:51:55

提高數(shù)據(jù)庫性能

2021-12-01 06:50:50

Docker底層原理

2024-03-28 08:32:10

美團關閉訂單輪訓

2016-04-06 11:14:48

iOS相機自定義

2016-09-21 22:31:47

Python作用域
點贊
收藏

51CTO技術棧公眾號