发布于2026-07-11 阅读(0)
扫一扫,手机访问
聊到订单业务中的超时处理,很多团队都绕不开这样一个难题:用户下了单,但迟迟不付款——订单状态什么时候该自动变更?
解决思路其实不少。最简单的做法,就是弄个定时任务去扫表(Quartz),每生成一个订单就创建一个定时任务,到期触发业务逻辑。当然,大家也都知道这么做会有啥坑……另外两个比较主流的方案是:利用RabbitMQ的延迟队列,或者对Redis的Key做过期监听。
下面咱们就重点演示如何用SpringBoot + Redis的Key过期监听,来实现这种“订单超时未支付,自动失效”的需求。整体思路不算复杂,但有几个配置上的小细节需要特别注意。
首先,把Redis的starter加进来。这是最常规的操作:
org.springframework.boot spring-boot-starter-data-redis
然后,按照项目需要配置好Redis连接信息,这个没什么好说的:
spring:
redis:
database: 0
host: 127.0.0.1
password: redis_123456
port: 6379
这一步是关键。默认情况下,Redis是不会主动通知Key过期事件的。要开启这个功能,需要修改redis.conf配置文件,找到notify-keyspace-events这个属性,把它从默认的空值改为"Ex"。
notify-keyspace-events "Ex"
接下来,我们写一个监听器,专门用来捕获过期的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());
}
}
有了监听器,还得有个配置类来注册它。这里要注入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());
}
}
日常开发中,操作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;
}
}
}
到这里,核心逻辑基本就绪。我们来验证一下是否真的能监听到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;
}


从测试结果来看,Key过期事件确实被成功捕获了。不过图中可以看到显示的Key是乱码——这是因为SpringBoot的RedisTemplate在存储Key时,默认使用了JDK的序列化方式,而没有配置字符串序列化器。这块如果需要可读性,建议增加一个自定义的序列化配置。
通过以上步骤,一个基于Redis Key过期监听的订单超时处理机制就搭建好了。实现起来并不复杂,核心在于开启Redis的过期通知事件,以及在SpringBoot中正确配置监听器容器。不过话说回来,这个方案也不是万能的——比如在高并发场景下,Redis的过期通知可能存在延迟,以及对丢失事件的容忍度也是个需要权衡的因素。但作为一套轻量级的实现方案,对于大多数中小型项目来说,已经足够实用了。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8