Redis 版本:5.0 :?
一:過期監聽:
Spring Data Redis 封裝了 Redis 的 Pub/Sub 功能,提供了對 key 過期事件的監聽支持。
1. 核心類:KeyExpirationEventMessageListener
這個抽象類是 Spring 提供的,專門用于監聽 Redis 的 key 過期事件。
代碼中的 RedisKeyExpiredListener 繼承自它
Redis 的 key 過期事件是一種發布/訂閱(Pub/Sub)機制,當某個設置了過期時間的 key 被 Redis 刪除時,Redis 會發布一個事件通知。Spring 通過監聽這些事件,并將其封裝為 Spring 事件模型中的對象,從而實現對 key 過期事件的捕獲和處理
Redis 的定期刪除策略是 默認每秒執行 10 次(即每 100 毫秒運行一次),這是由 Redis 配置中的 hz 參數控制的。
實際上:默認配置中,hz=10,表示每秒運行 10 次過期鍵清理任務。
每次運行時,Redis 會從數據庫中隨機選取一批設置了過期時間的鍵進行檢查,并刪除其中已過期的鍵。
這個過程不會掃描所有鍵,因此可能會有部分過期鍵在過期后不會立刻被刪除,導致“過期鍵延遲刪除”
可以在 redis.conf 文件中調整 hz 參數來改變 Redis 清理過期鍵的頻率:
增大 hz 值(例如設為 20 或 30)可以提高清理頻率,從而提升過期鍵的刪除實時性,但也會增加 CPU 消耗。
減小 hz 值則節省 CPU 資源,但可能導致更多的過期鍵滯留內存。?
實際操作也會遇到,手動更改了TTL方便更快過期,然后不刷新Redis Manager的情況下,盡管有定期刪除策略,也沒有收到那幾個key的過期監聽?
首先來認知原理:
Redis 使用兩種策略來處理設置了 TTL 的鍵(即有過期時間的鍵):
刪除策略 | 觸發方式 | 是否會觸發 expired 事件 |
惰性刪除(Lazy Expiration) | key 被訪問時發現已過期但未被刪除(惰性刪除) | ? 會觸發 expired 事件 |
定期刪除(Active Expiration) | Redis 每隔一段時間掃描并隨機刪除部分過期鍵 | ? 不會觸發 expired 事件 |
public class RedisKeyExpiredListener extends KeyExpirationEventMessageListener
這個類繼承自 Spring Data Redis 提供的 KeyExpirationEventMessageListener。
它監聽的是 Redis 的 __keyevent@*:expired 通道。
只有當 Redis 通過惰性刪除觸發過期事件時,才會向這個通道發布消息,從而觸發你的 onMessage() 方法。?
如果一個 key 設置了過期時間(TTL),不主動訪問它,也沒有在過期后被 Redis 的惰性刪除機制觸發,那么你確實 可能永遠收不到 expired 過期通知。
疑問?
如果 Redis 通過定期刪除策略已經刪除了一個設置了 TTL 的 key,此時再去手動 GET 這個 key,還會觸發 Redis 的 expired 過期事件嗎?
答:不會觸發過期事件監聽器(如 RedisKeyExpiredListener)
只有在訪問一個 key 時,發現它已經過期但尚未被刪除的情況下,才會真正執行刪除操作,并觸發 expired 事件。
但如果這個 key 已經被 Redis 的定期刪除機制提前刪掉了:
它已經不存在于 Redis 中;此時你再訪問它(如 GET my_key),會直接返回 nil;
不會再次觸發 Redis 的過期檢查和事件通知。
二: 那么問題來了:什么是“查詢”key?什么又是“訪問”key?
2.1這是關鍵區分點 👇
操作 | 是否算作“訪問” |
GET key | ? 是 |
EXISTS key | ? 是 |
TTL key | ? 是 |
DEL key | ? 是 |
Redis 內部線程檢查 key 是否存在 | ? 是 |
Redis 定期刪除時只是遍歷并比較過期時間 | ? 不是 |
所以就是代碼中有對Redis進行不定時的設置復合鍵的key時,就會訪問key,這樣不用直接去手動get 此key,一樣是訪問到了
key格式復合鍵:TEST_KEY:
redisTemplate.opsForValue().set(TEST_KEY+ relatedId , null ,60, TimeUnit.SECONDS);
使用這類代碼,就是會導致,如果有進行定期set,就會訪問key,從而觸發一些過期key的過期監聽收到消息,
如果不進行設置,則一些已經過期的就有可能被定時刪除而收不到消息,導致消息丟失,數據不準確的問題
另外: Redis 定期刪除到底做了什么?
Redis 使用了一個叫做 activeExpireCycle() 的函數來執行定期刪除。
它的大致流程如下:
void activeExpireCycle(...) {// 獲取當前數據庫for (j = 0; j < dbs_per_call; j++) {for (k = 0; k < keys_per_call; k++) {// 隨機選取一個設置了 TTL 的 keyif (currentExpiresTime < now()) {// 直接刪除,不進行任何訪問操作dbDelete(db, key);}}}
}
關鍵點:
Redis 只檢查 key 的過期時間;
沒有真正訪問這個 key 的內容(比如讀取、修改等);
所以 不會觸發惰性刪除邏輯;自然也就 不會發布 expired 事件。
2.2? ?除了上面說的情況,某些 key 即使沒手動 get,也被監聽到過期事件?
模塊 | 行為 | 是否可能訪問 key |
Lua 腳本 | EVAL 中使用 key | ? 是 |
集群遷移 | MIGRATE 命令 | ? 是 |
持久化 | RDB 快照生成時 | ? 否 |
主從同步 | 復制 key 到從節點 | ? 否 |
慢日志 | 記錄慢命令 | ? 否 |
其他業務模塊 | 如緩存統計、監控工具 | ? 是 |
所以你雖然沒有顯式調用 get(key),但這些模塊可能會在你不察覺的情況下訪問 key,從而觸發惰性刪除。
三:Redis 淘汰策略中訪問到這個 key
場景 | 是否觸發 expired 事件 |
Redis 定期刪除直接刪掉了這個 key | ? 否 |
Redis 主從同步時訪問了這個 key | ? 是 |
Redis 集群遷移該 key 到新節點 | ? 是 |
Redis 慢日志記錄了訪問該 key 的命令 | ? 是(說明它已經被訪問) |
Redis 監控工具周期性檢查 key | ? 是 |
Redis 淘汰策略中訪問到這個 key | ? 是 |
所以,你的 key 是如何被訪問到的?
機制 | 是否可能訪問 key |
Redisson 的 RLock、RLock.tryLock() | ? 是 |
Spring Cacheable 緩存組件 | ? 是 |
Redis 的 SCAN 命令 | ? 是 |
Redis 的 KEYS * 命令 | ? 是 |
Redis 的 EXPIRE 命令 | ? 是 |
Redis 的 DEL 命令 | ? 是 |
這些命令雖然你不寫,但 Redis 內部模塊或框架庫可能會使用它們。
?四:使用 Redisson 的延遲隊列(推薦)
RDelayedQueue<String> queue = redisson.getDelayedQueue(redisson.getQueue("my_queue"));
queue.offer("data", 60, TimeUnit.SECONDS);// 注冊監聽器
queue.registerListener(item -> {log.info("收到延遲消息: {}", item);
});
這種方式比依賴 Redis 的 expired 事件更加穩定可靠。
Redis 內部模塊和后臺任務 可能 會訪問你的 key,但這不是一定會發生的行為。
所以你看到的 key 過期事件,是 “可能觸發”的結果,而不是“一定會觸發”的機制。
如果你需要確保 key 過期后觸發回調,請使用 Redisson 的延遲隊列 或 Kafka + 定時輪詢 等更可靠的機制。