先來看一個問題:
如何處理訂單過期自動取消,例如下單30分鐘未支付自動更改訂單狀態?
解決方案:
可以利用redis天然的key自動過期機制,下單時將訂單id寫入redis,過期時間30分鐘,30分鐘後檢查訂單狀態,如果未支付,則進行處理但是key過期了redis有通知嗎?答案是肯定的。
開啟redis key過期提醒
修改redis相關事件設定。找到redis設定檔redis.conf,查看“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過期事件
127.0.0.1:6379> PSUBSCRIBE __keyevent@0__:expired Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "__keyevent@0__:expired" 3) (integer) 1
開啟另一個redis-cli ,傳送定時過期key
127.0.0.1:6379> setex test_key 3 test_value
觀察上一個redis-cli ,會發現收到了過期的keytest_key,但是無法收到過期的value test_value
127.0.0.1:6379> PSUBSCRIBE __keyevent@0__:expired Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "__keyevent@0__:expired" 3) (integer) 1 1) "pmessage" 2) "__keyevent@0__:expired" 3) "__keyevent@0__:expired" 4) "test_key"
在springboot中使用
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>在springboot中使用########### pom 中新增依賴###
import edu.zut.ding.listener.RedisExpiredListener;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.PatternTopic;import org.springframework.data.redis.listener.RedisMessageListenerContainer;/** * @Author lsm * @Date 2018/10/27 20:56 */@Configurationpublic 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; } }###定義配置RedisListenerConfig###
import edu.zut.ding.constants.SystemConstant;import edu.zut.ding.enums.OrderState;import edu.zut.ding.service.OrderService;import org.springframework.beans.factory.annotation.Autowired;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" * @author lsm */@Componentpublic class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } /** * 针对redis数据失效事件,进行数据处理 * @param message * @param pattern */ @Override public void onMessage(Message message, byte[] pattern) { // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key String expiredKey = message.toString(); if(expiredKey.startsWith("Order:")){ //如果是Order:开头的key,进行处理 } } }###定義監聽器,實作KeyExpirationEventMessageListener接口,查看原始碼發現,該介面監聽所有db的過期事件keyevent@*:expired"###
import org.springframework.data.redis.connection.Message;import org.springframework.data.redis.connection.MessageListener;/** * @author lsm */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))); } }###或開啟RedisListenerConfig中container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired")); 註釋,再定義監聽器,監控__keyevent@0__:expired事件,即db0過期事件。這個地方定義的比較靈活,可以自己定義監控什麼事件。###rrreee
以上是redis是怎麼監控失效的key的詳細內容。更多資訊請關注PHP中文網其他相關文章!