最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot監(jiān)聽Redis Key過期事件的幾種方式

 更新時間:2026年03月12日 10:17:44   作者:彭于晏Yan  
本文介紹了使用Redis監(jiān)聽鍵過期來實現(xiàn)電商平臺優(yōu)惠券自動失效的解決方案,包括配置Redis、實現(xiàn)監(jiān)聽器、使用線程池優(yōu)化以及進(jìn)一步優(yōu)化措施,需要的朋友可以參考下

1. 示例場景

電商平臺常會發(fā)放「限時優(yōu)惠券」(比如 “滿 100 減 20,30 分鐘內(nèi)有效”“24 小時專屬券”),核心訴求是:

  1. 優(yōu)惠券一旦超過有效期,必須自動標(biāo)記為 “已失效”,用戶無法再使用;
  2. 避免用戶在優(yōu)惠券過期后仍嘗試用券下單,導(dǎo)致訂單支付異常;
  3. 無需定時任務(wù)輪詢(比如每分鐘掃全量表),減少數(shù)據(jù)庫壓力;
  4. 精準(zhǔn)觸發(fā)失效邏輯(優(yōu)惠券到期瞬間就處理,而非定時任務(wù)的 “延遲生效”)。

如果用傳統(tǒng)定時任務(wù)方案,會面臨兩個核心問題:

  • 優(yōu)惠券發(fā)放時間分散(用戶隨時領(lǐng)券),無法精準(zhǔn)設(shè)定輪詢規(guī)則(比如 30 分鐘有效期的券,有的用戶 10:01 領(lǐng),有的 10:05 領(lǐng),定時任務(wù)間隔設(shè)短了耗性能,設(shè)長了失效不及時);
  • 全量表輪詢會頻繁查詢數(shù)據(jù)庫,高并發(fā)下(比如大促發(fā)百萬張券)數(shù)據(jù)庫扛不住。

此時,用「Redis 監(jiān)聽鍵過期」的方案就能完美解決,既精準(zhǔn)又低耗。

2. 配置 Redis 開啟key 過期通知

  • Redis 默認(rèn)關(guān)閉鍵空間通知,需要先開啟并指定監(jiān)聽的事件類型。

方式一:臨時配置(重啟 Redis 失效)

# 開啟過期事件監(jiān)聽,也可按需指定(如只監(jiān)聽過期+驅(qū)逐:KxKe)
127.0.0.1:6379> config set notify-keyspace-events Ex

方式 二:永久配置

  • 修改Redis配置文件redis.conf,查看 notify-keyspace-events 配置項,修改為notify-keyspace-events Ex,并重啟Redis生效,相關(guān)參數(shù)說明如下:
K:keyspace 事件,事件以 keyspace@ 為前綴進(jìn)行發(fā)布
E:keyevent 事件,事件以 keyevent@ 為前綴進(jìn)行發(fā)布
g:一般性的,非特定類型的命令,比如del,expire,rename等
$:字符串特定命令
l:列表特定命令
s:集合特定命令
h:哈希特定命令
z:有序集合特定命令
x:過期事件,當(dāng)某個鍵過期并刪除時會產(chǎn)生該事件
e:驅(qū)逐事件,當(dāng)某個鍵鍵因Redis內(nèi)存達(dá)到maxmemory上限,被淘汰策略刪除時觸發(fā)
A:g$lshzxe的別名,因此”AKE”意味著所有事件

3. 添加pom文件

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

4. RedisListenerConfig配置類

  • 實現(xiàn)監(jiān)聽 Redis key過期時間
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.RedisMessageListenerContainer;
@Configuration
public class RedisListenerConfig {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

5. RedisKeyExpirationListener監(jiān)聽類

  • 實現(xiàn)KeyExpirationEventMessageListener接口,查看源碼發(fā)現(xiàn),該接口監(jiān)聽所有db的過期事件 keyevent@*:expired"
  • 鍵過期事件并非“實時觸發(fā)”:Redis 采用 “惰性刪除 + 定期刪除” 策略,過期鍵可能延遲觸發(fā)事件。
/**
 * 監(jiān)聽所有db的過期事件__keyevent@*__:expired"
 */
@Component
@Slf4j
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    /**
     * 針對 redis 數(shù)據(jù)失效事件,進(jìn)行數(shù)據(jù)處理
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 獲取到失效的 key,進(jìn)行取消訂單業(yè)務(wù)處理
        String expiredKey = message.toString();
        log.info("失效的Key:{}", expiredKey);
    }
}

6. 使用線程池優(yōu)化

  • 先定義一個專門處理過期事件業(yè)務(wù)的線程池,避免使用默認(rèn)線程池導(dǎo)致資源競爭:
/**
 * 過期事件異步處理線程池配置
 */
@Configuration
@EnableAsync // 開啟異步注解支持
public class RedisEventExecutorConfig {

    /**
     * 自定義線程池,處理過期事件的業(yè)務(wù)邏輯
     */
    @Bean("redisExpireEventExecutor")
    public Executor redisExpireEventExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心線程數(shù)(根據(jù)業(yè)務(wù)壓力調(diào)整)
        executor.setCorePoolSize(5);
        // 最大線程數(shù)
        executor.setMaxPoolSize(20);
        // 隊列容量(緩沖待處理的任務(wù))
        executor.setQueueCapacity(1000);
        // 線程名前綴(便于日志排查)
        executor.setThreadNamePrefix("redis-expire-event-");
        // 拒絕策略:隊列滿+線程數(shù)到最大時,由提交任務(wù)的線程執(zhí)行(避免任務(wù)丟失)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 線程空閑超時時間
        executor.setKeepAliveSeconds(60);
        // 初始化線程池
        executor.initialize();
        return executor;
    }
}

// 在監(jiān)聽類中調(diào)用
@Component
public class RedisExpireBusinessService {
    
    @Async("redisExpireEventExecutor")
    public void handleExpiredKeyAsync(String expiredKey) {
        try {
            System.out.println("異步處理過期key:" + expiredKey + ",線程:" + Thread.currentThread().getName());
        } catch (Exception e) {
            System.err.println("處理過期key失?。? + expiredKey + ",異常:" + e.getMessage());
        }
    }
}

7. 進(jìn)一步優(yōu)化

如果過期事件量極大(比如每秒上萬),僅靠線程池可能不夠,可疊加以下方案:

7.1. 引入消息隊列(如 RocketMQ/Kafka)

  • 監(jiān)聽器只做 “生產(chǎn)消息”:將過期 key 發(fā)送到消息隊列;
  • 單獨的消費者服務(wù)處理業(yè)務(wù)邏輯:解耦Redis監(jiān)聽和業(yè)務(wù)處理,支持削峰填谷。

7.2. 優(yōu)化 Redis 監(jiān)聽容器線程池

RedisMessageListenerContainer 自身也有監(jiān)聽線程池,可調(diào)整參數(shù)避免監(jiān)聽線程不夠用:

@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    // 設(shè)置監(jiān)聽線程池的核心參數(shù)(默認(rèn)線程數(shù)可能不足)
    container.setTaskExecutor(new ThreadPoolTaskExecutor() {{
        setCorePoolSize(3); // 監(jiān)聽線程數(shù),根據(jù)事件量調(diào)整
        setMaxPoolSize(5);
        setThreadNamePrefix("redis-listener-");
        initialize();
    }});
    return container;
}

以上就是SpringBoot監(jiān)聽Redis Key過期事件的幾種方式的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot監(jiān)聽Redis Key過期事件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

视频| 宜章县| 南皮县| 阆中市| 射洪县| 麻城市| 德州市| 施甸县| 行唐县| 和田县| 剑川县| 郁南县| 富顺县| 江达县| 昭觉县| 偃师市| 浦江县| 迁安市| 华宁县| 余江县| 富蕴县| 专栏| 凌云县| 景德镇市| 万年县| 海兴县| 益阳市| 瓦房店市| 绿春县| 平舆县| 游戏| 随州市| 沭阳县| 新源县| 梧州市| 宣武区| 寻乌县| 浪卡子县| 平顺县| 泸州市| 榆林市|