首頁 > 軟體

利用Redis實現訂單30分鐘自動取消

2022-06-27 14:03:14

業務場景

我們以訂單功能為例說明下:

生成訂單後一段時間不支付訂單會自動關閉。最簡單的想法是設定定時任務輪詢,但是每個訂單的建立時間不一樣,定時任務的規則無法設定,如果將定時任務執行的間隔設定的過短,太影響效率。

還有一種想法,在使用者進入訂單介面的時候,判斷時間執行相關操作。方式可能有很多,在這裡介紹一種監聽 Redis 鍵值對過期時間來實現訂單自動關閉。

實現思路

在生成訂單時,向 Redis 中增加一個 KV 鍵值對,K 為訂單號,保證通過 K 能定位到資料庫中的某個訂單即可,V 可為任意值。

假設,生成訂單時向 Redis 中存放 K 為訂單號,V 也為訂單號的鍵值對,並設定過期時間為 30 分鐘,如果該鍵值對在 30 分鐘過期後能夠傳送給程式一個通知,或者執行一個方法,那麼即可解決訂單關閉問題。

實現:通過監聽 Redis 提供的過期佇列來實現,監聽過期佇列後,如果 Redis 中某一個 KV 鍵值對過期了,那麼將向監聽者傳送訊息,監聽者可以獲取到該鍵值對的 K,注意,是獲取不到 V 的,因為已經過期了,這就是上面所提到的,為什麼要保證能通過 K 來定位到訂單,而 V 為任意值即可。拿到 K 後,通過 K 定位訂單,並判斷其狀態,如果是未支付,更新為關閉,或者取消狀態即可。

開啟 Redis key 過期提醒

修改 redis 相關事件設定。找到 redis 組態檔 redis.conf,檢視 notify-keyspace-events 設定項,如果沒有,新增 notify-keyspace-events Ex,如果有值,則追加 Ex,相關引數說明如下:

  • K:keyspace 事件,事件以 keyspace@ 為字首進行釋出
  • E:keyevent 事件,事件以 keyevent@ 為字首進行釋出
  • g:一般性的,非特定型別的命令,比如del,expire,rename等
  • $:字串特定命令
  • l:列表特定命令
  • s:集合特定命令
  • h:雜湊特定命令
  • z:有序集合特定命令
  • x:過期事件,當某個鍵過期並刪除時會產生該事件
  • e:驅逐事件,當某個鍵因 maxmemore 策略而被刪除時,產生該事件
  • A:g$lshzxe的別名,因此”AKE”意味著所有事件

引入依賴

在 pom.xml 中新增 org.springframework.boot:spring-boot-starter-data-redis 依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

相關設定

定義設定 RedisListenerConfig 實現監聽 Redis key 過期時間

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

@Configuration
public class RedisListenerConfig {

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

定義監聽器 RedisKeyExpirationListener,實現KeyExpirationEventMessageListener 介面,檢視原始碼發現,該介面監聽所有 db 的過期事件 keyevent@*:expired"

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

/**
 * 監聽所有db的過期事件__keyevent@*__:expired"
 */
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    /**
     * 針對 redis 資料失效事件,進行資料處理
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {

        // 獲取到失效的 key,進行取消訂單業務處理
        String expiredKey = message.toString();
        System.out.println(expiredKey);
    }
}

redis 過期監聽真的好麼?

在 Redis 官方手冊的keyspace-notifications: timing-of-expired-events中明確指出:

Basically expired events are generated when the Redis server deletes the key and not when the time to live theoretically reaches the value of zero

redis 自動過期的實現方式是:定時任務離線掃描並刪除部分過期鍵;在存取鍵時惰性檢查是否過期並刪除過期鍵。redis 從未保證會在設定的過期時間立即刪除並行送過期通知。實際上,過期通知晚於設定的過期時間數分鐘的情況也比較常見。

此外來鍵空間通知採用的是傳送即忘(fire and forget)策略,並不像訊息佇列一樣保證送達。當訂閱事件的使用者端會丟失所有在斷線期間所有分發給它的事件。

這是一種比定時掃描資料庫更 “LOW” 的解決方案,不建議使用。

實現關閉訂單的方法

一般實現的方法有幾種:

  • 使用 rocketmq、rabbitmq、pulsar 等訊息佇列的延時投遞功能
  • 使用 redisson 提供的 DelayedQueue

有一些方案雖然廣為流傳但存在著致命缺陷,不要用來實現延時任務

  • 使用 redis 的過期監聽
  • 使用 rabbitmq 的死信佇列
  • 使用非持久化的時間輪

到此這篇關於利用Redis實現訂單30分鐘自動取消的文章就介紹到這了,更多相關Redis訂單30分鐘自動取消內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com