商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > springboot+redis监听过期Key实践

springboot+redis监听过期Key实践

  发布于2026-07-11 阅读(0)

扫一扫,手机访问

聊到订单业务中的超时处理,很多团队都绕不开这样一个难题:用户下了单,但迟迟不付款——订单状态什么时候该自动变更?

解决思路其实不少。最简单的做法,就是弄个定时任务去扫表(Quartz),每生成一个订单就创建一个定时任务,到期触发业务逻辑。当然,大家也都知道这么做会有啥坑……另外两个比较主流的方案是:利用RabbitMQ的延迟队列,或者对Redis的Key做过期监听。

下面咱们就重点演示如何用SpringBoot + Redis的Key过期监听,来实现这种“订单超时未支付,自动失效”的需求。整体思路不算复杂,但有几个配置上的小细节需要特别注意。

1、引入依赖

首先,把Redis的starter加进来。这是最常规的操作:


    org.springframework.boot
    spring-boot-starter-data-redis

2、修改boot的redis配置

然后,按照项目需要配置好Redis连接信息,这个没什么好说的:

spring:
  redis:
    database: 0
    host: 127.0.0.1
    password: redis_123456
    port: 6379

3、在服务器中修改Redis配置

这一步是关键。默认情况下,Redis是不会主动通知Key过期事件的。要开启这个功能,需要修改redis.conf配置文件,找到notify-keyspace-events这个属性,把它从默认的空值改为"Ex"

notify-keyspace-events "Ex"

4、创建一个Redis监控类

接下来,我们写一个监听器,专门用来捕获过期的Key。这个类需要继承KeyExpirationEventMessageListener

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import ja va.nio.charset.StandardCharsets;

public class KeyExpiredListener extends KeyExpirationEventMessageListener {

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

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 当有key过期时,这里会收到通知
        System.out.println("过期key:" + message.toString());
    }
}

5、创建Redis配置类

有了监听器,还得有个配置类来注册它。这里要注入RedisConnectionFactory,并配置好RedisMessageListenerContainer

import com.zy.rabbitmq.base.Listener.KeyExpiredListener;
import org.springframework.beans.factory.annotation.Autowired;
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 RedisConfiguration {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer() {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        return container;
    }

    @Bean
    public KeyExpiredListener keyExpiredListener() {
        return new KeyExpiredListener(this.redisMessageListenerContainer());
    }
}

6、提供一个redis工具类

日常开发中,操作Redis免不了要封装一个工具类。这里给出一个基本的实现,涵盖了存值、取值、设置过期时间、获取剩余过期时间等常用操作:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import ja vax.annotation.Resource;
import ja va.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Resource
    private RedisTemplate redisTemplate;

    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

7、测试验证

到这里,核心逻辑基本就绪。我们来验证一下是否真的能监听到Key过期。这里简单提供两个接口:一个用来写入一个Key并设置10秒过期;另一个用来查询该Key的值和剩余有效时间。

@GetMapping("/put")
public String demo() {
    redisUtil.set("name", "zhangyi", 10);
    return "aaa";
}

@GetMapping("/get")
public Map get() {
    Map m = new HashMap<>();
    m.put("time", redisUtil.getExpire("name"));
    m.put("val", redisUtil.get("name"));
    return m;
}

springboot+redis监听过期Key实践

springboot+redis监听过期Key实践

从测试结果来看,Key过期事件确实被成功捕获了。不过图中可以看到显示的Key是乱码——这是因为SpringBoot的RedisTemplate在存储Key时,默认使用了JDK的序列化方式,而没有配置字符串序列化器。这块如果需要可读性,建议增加一个自定义的序列化配置。

总结

通过以上步骤,一个基于Redis Key过期监听的订单超时处理机制就搭建好了。实现起来并不复杂,核心在于开启Redis的过期通知事件,以及在SpringBoot中正确配置监听器容器。不过话说回来,这个方案也不是万能的——比如在高并发场景下,Redis的过期通知可能存在延迟,以及对丢失事件的容忍度也是个需要权衡的因素。但作为一套轻量级的实现方案,对于大多数中小型项目来说,已经足够实用了。

本文转载于:https://www.jb51.net/program/367212cdp.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注