需求:
處理訂單過期自動取消,比如下單30分鍾未支付自動更改訂單狀態
用戶綁定隱私號碼當訂單結束取消綁定等
解決方案1:
可以利用redis自帶的key自動過期機制,下單時將訂單id寫入redis,過期時間30分鍾,30分鍾后檢查訂單狀態,如果未支付,則進行處理但是key過期了redis有通知嗎?答案是肯定的。
開啟redis key過期提醒
修改redis相關事件配置。找到redis配置文件redis.conf,只需修改配置文件redis.conf中的:notify-keyspace-events Ex,默認為notify-keyspace-events "", 查看“notify-keyspace-events”的配置項,如果沒有,添加“notify-keyspace-events Ex”,如果有值,添加Ex,相關參數說明如下:
K:keyspace事件,事件以__keyspace@<db>__為前綴進行發布; E:keyevent事件,事件以__keyevent@<db>__為前綴進行發布; g:一般性的,非特定類型的命令,比如del,expire,rename等; $:字符串特定命令; l:列表特定命令; s:集合特定命令; h:哈希特定命令; z:有序集合特定命令; x:過期事件,當某個鍵過期並刪除時會產生該事件; e:驅逐事件,當某個鍵因maxmemore策略而被刪除時,產生該事件; A:g$lshzxe的別名,因此”AKE”意味着所有事件。
Redis測試:
打開一個redis-cli ,監控db0的key過期事件

打開另一個redis-cli ,發送定時過期key

觀察上一個redis-cli ,會發現收到了過期的key hello,但是無法收到過期的value world

根據這個特性在springboot中使用
1.pom 中添加依賴
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.定義配置RedisListenerConfig
/**
* @author mazhq
* @Title: RedisListenerConfig
* @ProjectName: zeus
* @date 2019/2/20 11:25
*/
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired"));
return container;
}
}
- 3.定義監聽器,實現
KeyExpirationEventMessageListener接口,查看源碼發現,該接口監聽所有db的過期事件keyevent@*:expired"
/**
* @author mazhq
* @Title: RedisKeyExpirationListener
* @ProjectName: zeus
* @date 2019/2/20 11:26
*/
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
private final static Logger logger = LoggerFactory.getLogger(RedisKeyExpirationListener.class);
@Autowired
private RedisClient redisClient;
/**
* Creates new {@link MessageListener} for {@code __keyevent@*__:expired} messages.
*
* @param listenerContainer must not be {@literal null}.
*/
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
// 用戶做自己的業務處理即可,注意message.toString()可以獲取失效的key
String expiredKey = message.toString();
if(expiredKey.startsWith("zeus:order")){
//TODO
}
}
}
或者打開RedisListenerConfig中 container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired")); 注釋,再定義監聽器,監控__keyevent@0__:expired事件,即db0過期事件。這個地方定義的比較靈活,可以自己定義監控什么事件。
public class RedisExpiredListener implements MessageListener {
/**
* 客戶端監聽訂閱的topic,當有消息的時候,會觸發該方法;
* 並不能得到value, 只能得到key。
* 姑且理解為: redis服務在key失效時(或失效后)通知到java服務某個key失效了, 那么在java中不可能得到這個redis-key對應的redis-value。
* * 解決方案:
* 創建copy/shadow key, 例如 set vkey "vergilyn"; 對應copykey: set copykey:vkey "" ex 10;
* 真正的key是"vkey"(業務中使用), 失效觸發key是"copykey:vkey"(其value為空字符為了減少內存空間消耗)。
* 當"copykey:vkey"觸發失效時, 從"vkey"得到失效時的值, 並在邏輯處理完后"del vkey"
*
* 缺陷:
* 1: 存在多余的key; (copykey/shadowkey)
* 2: 不嚴謹, 假設copykey在 12:00:00失效, 通知在12:10:00收到, 這間隔的10min內程序修改了key, 得到的並不是 失效時的value.
* (第1點影響不大; 第2點貌似redis本身的Pub/Sub就不是嚴謹的, 失效后還存在value的修改, 應該在設計/邏輯上杜絕)
* 當"copykey:vkey"觸發失效時, 從"vkey"得到失效時的值, 並在邏輯處理完后"del vkey"
*
*/
@Override
public void onMessage(Message message, byte[] bytes) {
byte[] body = message.getBody();// 建議使用: valueSerializer
byte[] channel = message.getChannel();
System.out.print("onMessage >> " );
System.out.println(String.format("channel: %s, body: %s, bytes: %s"
,new String(channel), new String(body), new String(bytes)));
}
}
解決方案2
使用spring + quartz定時任務(支持任務信息寫入mysql,多節點分布式執行任務),下單成功后,生成一個30分鍾后運行的任務,30分鍾后檢查訂單狀態,如果未支付,則進行處理
解決方案3
將訂單過期時間信息寫入mysql,按分鍾輪詢查詢mysql,如果超時則進行處理,效率差!時間精准度底!
結論
推薦使用方案1和方案2
