1、業務場景
- 在開發中我們可能會遇到下面這樣的場景,比如:
- 當一個用戶下單之後後一段時間不支付訂單會自動關閉,但每個訂單的創建時間又不一樣怎麼保證30分鐘不支付就取消訂單呢?
- 當用戶的優惠券或者積分等等快要過期的時候需要提前一天或者幾個小時或者30分鐘的時候發消息提醒用戶,或者去做一些其他的業務處理
- 那麼針對上述場景我們怎麼去實現呢?估計很多人首先會想到用定時任務每隔一段時間去執行任務判斷吧,這種方式完全可行,但是如果是需要比較精確的提醒呢?就比如我就需要過期前一個小時或者30分鐘的時候去提醒,那怎麼解決?
- 如果還用定時任務那麼這個跑批的時間間隔我們如何設置,太長或者太短都不太合適,所以針對這種問題,我們強大的redis就可以實現了。
2、Redis key 過期提醒
2.1、在這裏介紹一種監聽 Redis 鍵值對過期時間來實現定時任務的功能觸發事件機制。
2.2、實現思路
-
- 在生成訂單時,向 Redis 中增加一個 KV 鍵值對,K 爲訂單號,保證通過 K 能定位到數據庫中的某個訂單即可,V 可爲任意值(因爲過期時只能獲取到k的值,不能獲取到V的值)。
- 假設:生成訂單時向 Redis 中存放 K 爲訂單號,V 也爲訂單號的鍵值對,並設置過期時間爲 30 分鐘,如果該鍵值對在 30 分鐘過期後能夠發送給程序一個通知,或者執行一個方法,那麼即可解決訂單關閉問題。
- 實現:通過監聽 Redis 提供的過期隊列來實現,監聽過期隊列後,如果 Redis 中某一個 KV 鍵值對過期了,那麼將向監聽者發送消息,監聽者可以獲取到該鍵值對的 K,注意,是獲取不到 V 的,因爲已經過期了,這就是上面所提到的,爲什麼要保證能通過 K 來定位到訂單,而 V 爲任意值即可。拿到 K 後,通過 K 定位訂單,並判斷其狀態,如果是未支付,更新爲關閉,或者取消狀態即可。
3、實現步驟
3.1、修改 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”意味着所有事件
-
3.2、引入依賴
-
- 在 pom.xml 中添加 org.springframework.boot:spring-boot-starter-data-redis 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.3、代碼編寫
-
- 定義配置 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"
- 定義監聽器 RedisKeyExpirationListener,實現
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);
}
}