Spring boot實現監聽Redis key失效事件實現和其它方式

需求:

處理訂單過時自動取消,好比下單30分鐘未支付自動更改訂單狀態java

用戶綁定隱私號碼當訂單結束取消綁定等mysql

解決方案1:

能夠利用redis自帶的key自動過時機制,下單時將訂單id寫入redis,過時時間30分鐘,30分鐘後檢查訂單狀態,若是未支付,則進行處理可是key過時了redis有通知嗎?答案是確定的。redis

開啓redis key過時提醒

修改redis相關事件配置。找到redis配置文件redis.conf,只需修改配置文件redis.conf中的:notify-keyspace-events Ex,默認爲notify-keyspace-events "", 查看「notify-keyspace-events」的配置項,若是沒有,添加「notify-keyspace-events Ex」,若是有值,添加Ex,相關參數說明以下:spring

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過時事件sql

打開另外一個redis-cli ,發送定時過時keyspringboot

觀察上一個redis-cli ,會發現收到了過時的key hello,可是沒法收到過時的value world分佈式

根據這個特性在springboot中使用

1.pom 中添加依賴ide

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

2.定義配置RedisListenerConfigspring-boot

/**
 * @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
        }
    }
}

  

或者打開RedisListenerConfigcontainer.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

相關文章
相關標籤/搜索