redis 超時失效key 的監聽觸發使用

redis自2.8.0以後版本提供Keyspace Notifications功能,容許客戶訂閱Pub / Sub頻道,以便以某種方式接收影響Redis數據集的事件。html

可能收到的事件的例子以下: 
全部影響給定鍵的命令。 
全部接收LPUSH操做的密鑰。 
全部密鑰在數據庫中過時0。java

由於 Redis 目前的訂閱與發佈功能採起的是發送即忘(fire and forget)策略, 因此若是你的程序須要可靠事件通知(reliable notification of events), 那麼目前的鍵空間通知可能並不適合你:當訂閱事件的客戶端斷線時, 它會丟失全部在斷線期間分發給它的事件。並不能確保消息送達。將來有計劃容許更可靠的事件傳遞,但可能這將在更通常的層面上解決,或者爲Pub / Sub自己帶來可靠性,或者容許Lua腳本攔截Pub / Sub消息來執行諸如推送將事件列入清單。redis

事件類型

對於每一個修改數據庫的操做,鍵空間通知都會發送兩種不一樣類型的事件消息:keyspace 和 keyevent。以 keyspace 爲前綴的頻道被稱爲鍵空間通知(key-space notification), 而以 keyevent 爲前綴的頻道則被稱爲鍵事件通知(key-event notification)。spring

事件是用 __keyspace@DB__:KeyPattern 或者 __keyevent@DB__:OpsType 的格式來發布消息的。 DB表示在第幾個庫;KeyPattern則是表示須要監控的鍵模式(能夠用通配符,如:__key*__:*);OpsType則表示操做類型。所以,若是想要訂閱特殊的Key上的事件,應該是訂閱keyspace。 好比說,對 0 號數據庫的鍵 mykey 執行 DEL 命令時, 系統將分發兩條消息, 至關於執行如下兩個 PUBLISH 命令: PUBLISH __keyspace@0__:sampleKey del PUBLISH __keyevent@0__:del sampleKey 訂閱第一個頻道 __keyspace@0__:mykey 能夠接收 0 號數據庫中全部修改鍵 mykey 的事件, 而訂閱第二個頻道 __keyevent@0__:del 
則能夠接收 0 號數據庫中全部執行 del 命令的鍵。

開啓配置

開啓配置

鍵空間通知一般是不啓用的,由於這個過程會產生額外消耗。因此在使用該特性以前,請確認必定是要用這個特性的,而後修改配置文件,或使用config配置。相關配置項以下:數據庫

字符 發送通知
K 鍵空間通知,全部通知以 keyspace@ 爲前綴,針對Key
E 鍵事件通知,全部通知以 keyevent@ 爲前綴,針對event
g DEL 、 EXPIRE 、 RENAME 等類型無關的通用命令的通知
$ 字符串命令的通知
l 列表命令的通知
s 集合命令的通知
h 哈希命令的通知
z 有序集合命令的通知
x 過時事件:每當有過時鍵被刪除時發送
e 驅逐(evict)事件:每當有鍵由於 maxmemory 政策而被刪除時發送
A 參數 g$lshzxe 的別名,至關因而All

 

 

 

 

 

 

 

 

 

 

 

輸入的參數中至少要有一個 K 或者 E , 不然的話, 無論其他的參數是什麼, 都不會有任何通知被分發。上表中斜體的部分爲通用的操做或者事件,而黑體則表示特定數據類型的操做。配置文件中修改 notify-keyspace-events 「Kx」,注意:這個雙引號是必定要的,不然配置不成功,啓動也不報錯。例如,「Kx」表示想監控某個Key的失效事件。apache

也能夠經過config配置:CONFIG set notify-keyspace-events Ex (但非持久化)json

Redis 使用如下兩種方式刪除過時的鍵: 1.當一個鍵被訪問時,程序會對這個鍵進行檢查,若是鍵已通過期,那麼該鍵將被刪除。 2.底層系統會在後臺查找並刪除那些過時的鍵,從而處理那些已通過期、可是不會被訪問到的鍵。 當過時鍵被以上兩個程序的任意一個發現、 而且將鍵從數據庫中刪除時, Redis 會產生一個 expired 通知。 Redis 並不保證生存時間(TTL)變爲 0 的鍵會當即被刪除: 若是程序沒有訪問這個過時鍵, 或者帶有生存時間的鍵很是多的話,
那麼在鍵的生存時間變爲 0 , 直到鍵真正被刪除這中間,
可能會有一段比較顯著的時間間隔。 所以, Redis 產生 expired 通知的時間爲過時鍵被刪除的時候, 而不是鍵的生存時間變爲
0 的時候。

因爲通知收到的是redis key,value已通過期,沒法收到,因此須要在key上標記業務數據。ide

 

 

redis 超時失效key 的監聽觸發

1. 事件經過 Redis 的訂閱與發佈功能(pub/sub)來進行分發,故須要訂閱 __keyevent@0__:expired 通道post

0表示db0 根據本身的dbindex選擇合適的數字測試

2. 修改 redis.conf 文件 

修改 notify-keyspace-events Ex 

複製代碼
複製代碼
# K    鍵空間通知,以__keyspace@<db>__爲前綴
# E    鍵事件通知,以__keysevent@<db>__爲前綴
# g    del , expipre , rename 等類型無關的通用命令的通知, ...
# $    String命令
# l    List命令
# s    Set命令
# h    Hash命令
# z    有序集合命令
# x    過時事件(每次key過時時生成)
# e    驅逐事件(當key在內存滿了被清除時生成)
# A    g$lshzxe的別名,所以」AKE」意味着全部的事件
複製代碼
複製代碼

 

3. 重啓redis , 便可測試失效事件的觸發, 監聽獲取的值爲 key

<java>

4.  首先須要一個消息監聽器類  RedisSubListener

RedisSubListener.java
package cn.maitian.maimai.cache.message.sub; import cn.maitian.bss.duty.privilege.utils.SpringContextHolder; import cn.maitian.maimai.cache.client.MaimaiJedis; import cn.maitian.maimai.cache.keys.RedisKeys; import cn.maitian.maimai.cache.model.RedisKeyspaceNotifiRecord; import cn.maitian.maimai.cache.service.RedisKeyspaceNotifiRecordIService; import cn.maitian.maimai.core.exception.AppSysException; import cn.maitian.maimai.core.util.AppStringUtils; import cn.maitian.maimai.schedule.utils.JobUtils; import com.alibaba.fastjson.JSONObject; import org.apache.log4j.Logger; import redis.clients.jedis.JedisPubSub; import java.util.Date; /** * Redis 發佈訂閱模型(Pub/Sub)的訂閱服務監聽器 * * @author * @version V1.0 * @company * @date */
public class RedisSubListener extends JedisPubSub { /** * 日誌 */
    protected final Logger logger = Logger.getLogger(getClass()); private RedisKeyspaceNotifiRecordIService redisKeyspaceNotifiRecordIService; /** * * @Title: onMessage * @Description: 取得訂閱的消息後的處理 * @param channel * 頻道 * @param message * 消息內容 * * @author * @date */ @Override public void onMessage(String channel, String message) { logger.info("channel{" + channel + "}message{" + message + "}"); } /** * * @Title: onPMessage * @Description: 取得按表達式的方式訂閱的消息後的處理 * * @author * @date */
    public void onPMessage(String pattern, String channel, String message) { logger.info("Redis訂閱監聽超時通知開始pattern{" + pattern + "}channel{" + channel + "}message{" + message + "}"); long starTime = System.currentTimeMillis(); if (AppStringUtils.isEmpty(message)) { logger.info("Redis訂閱監聽超時通知,message爲空"); return; } RedisKeyspaceNotifiRecord record = null; String keyId; try { if (message.startsWith(RedisKeys.REDIS_LOCK_KEY_STRING)) { logger.info("Redis訂閱監聽超時通知,此key值不須要進行訂閱處理"); return; } keyId = message.substring(message.lastIndexOf(":") + 1); long result = MaimaiJedis.setnx(RedisKeys.REDIS_LOCK_KEY_STRING + keyId,RedisKeys.REDIS_LOCK_KEY_STRING + keyId); if (result==0) { logger.info("Redis訂閱監聽超時通知,此key已被設置,請勿重複設置"); return; } record = redisKeyspaceNotifiRecordIService.findById(keyId); if (record == null) { // throw new AppSysException("Redis訂閱監聽超時通知,找不到記錄id{" + keyId + "}");
                logger.error("Redis訂閱監聽超時通知,找不到記錄id{" + keyId + "}"); return; } record.setBeginTime(new Date()); if (AppStringUtils.isEmpty(record.getServiceClass())) { throw new AppSysException("Redis訂閱監聽超時通知,RedisKeyspaceNotifiRecord中必須包含ServiceClass"); } Object object = SpringContextHolder.getBean(Class.forName(record.getServiceClass())); String serviceMethod = record.getServiceMethod(); Object[] params; if (AppStringUtils.isEmpty(record.getServiceParams())) { params = new Object[] {}; } else { params = JSONObject.parseArray(record.getServiceParams()).toArray(); } if (object != null && !AppStringUtils.isEmpty(serviceMethod)) { JobUtils.invokeMethod(object, serviceMethod, params); record.setStatus(RedisKeyspaceNotifiRecord.STATUS_SUCCESS_EXECUTE);// 執行成功
 } } catch (AppSysException e) { if (record != null) { record.setStatus(RedisKeyspaceNotifiRecord.STATUS_FAIL_EXECUTE);// 執行失敗
 } e.printStackTrace(); logger.info("Redis訂閱監聽超時通知,出現異常{" + e.getCode() + "}", e); } catch (Exception e) { if (record != null) { record.setStatus(RedisKeyspaceNotifiRecord.STATUS_FAIL_EXECUTE);// 執行失敗
 } e.printStackTrace(); logger.info("Redis訂閱監聽超時通知,出現異常", e); } finally { if (record != null) { record.setEndTime(new Date()); redisKeyspaceNotifiRecordIService.saveOrUpdate(record); } } long endTime = new Date().getTime(); logger.info("Redis訂閱監聽超時通知完成pattern{" + pattern + "}channel{" + channel + "}message{" + message + "}共耗時{"
                + (endTime - starTime) / 1000 + "}秒"); } /** * @param redisKeyspaceNotifiRecordIService * the redisKeyspaceNotifiRecordIService to set */
    public void setRedisKeyspaceNotifiRecordIService( RedisKeyspaceNotifiRecordIService redisKeyspaceNotifiRecordIService) { this.redisKeyspaceNotifiRecordIService = redisKeyspaceNotifiRecordIService; } }

5.Redis訂閱指定的頻道(此類用於訂閱Redis的鍵值超時事件)
RedisKeySpaceNotification.java
package cn.maitian.maimai.cache.message.sub; import cn.maitian.maimai.cache.client.MaimaiJedis; import org.apache.log4j.Logger; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** * Redis訂閱指定的頻道(此類用於訂閱Redis的鍵值超時事件) * * @author * @version V1.0 * @company * @date */
public class RedisKeySpaceNotification implements InitializingBean, DisposableBean { /** * 是否開啓Redis鍵值超時通知服務(默認打開) */
    private static boolean isRedisActiveKeySpaceNotification = true; /** * 日誌 */
    protected final Logger logger = Logger.getLogger(getClass()); private RedisSubListener redisSubListener; @Override public void destroy() throws Exception { // 銷燬
 } @Override public void afterPropertiesSet() throws Exception { if (isRedisActiveKeySpaceNotification) { logger.error("Redis訂閱的鍵值超時事件已啓動-----------------------------------"); JedisPool jedisPool = MaimaiJedis.getMaiMaiJedisPool(); final Jedis jedis = jedisPool.getResource(); new Thread(new Runnable() { @Override public void run() { jedis.psubscribe(redisSubListener, "__keyevent@*__:expired"); } }).start(); } } /** * @param redisSubListener * the redisSubListener to set */
    public void setRedisSubListener(RedisSubListener redisSubListener) { this.redisSubListener = redisSubListener; } }
相關文章
相關標籤/搜索