redis实现30分钟未支付取消订单

文章介绍了如何运用Redis的setex命令设置订单、求租求购和拼团等业务的超时机制,当key过期后,通过psubscribe监听事件进行回调处理,如取消订单或退回押金。此外,还提到了在Linux环境下配置守护进程的方法以及代码示例的GitHub链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

应用背景

  1. 订单下单30分钟内未支付,取消订单

  2. 发布的求租 求购 拼团超时需求无人响应,退回押金

  3. 各种可以使用超时来实现的业务

应用原理

  • Redis setex 设置一个指定时间的key,setex order_no 1800 1

  • 开启psubscribe监听key过期事件。注意setex psubscribe需要在同一个Redis db里面

业务实现

  • 根据callback中的key,去处理业务~这里随意

Linux配置

  • 设置守护进程 nohup php psubscribe.php &

  • 关闭进程 ps aux | grep "nohup php psubscribe.php &" 找到pid 然后kill

  • 设置自动启动

代码地址:https://github.com/SmallForest/redisKeyCancel

<think>我们被要求使用Spring框架实现订单在创建三十分钟后若支付则自动取消的功能。根据提供的引用,有几种常见的实现方案: 1. **定时任务(Spring Scheduled)**:通过定时任务周期性扫描数据库,查找超时支付订单并进行取消操作。 2. **Redis过期监听**:利用Redis的key过期特性,在订单创建时存入Redis并设置30分钟过期,当订单过期时触发事件处理取消逻辑。 3. **消息队列延迟消息**:如RabbitMQ的延迟队列或RocketMQ的延迟消息,在订单创建时发送一个延迟30分钟的消息,消费者收到消息后检查订单状态并执行取消。 由于用户要求使用Spring框架,我们选择两种常见方式详细说明:定时任务和Redis过期监听。 ### 方案一:使用Spring Scheduled定时任务 #### 实现步骤: 1. 在订单创建时,记录订单的创建时间(或支付截止时间)和状态(如“待支付”)。 2. 编写一个定时任务,每隔一段时间(如1分钟)执行一次,查询创建时间早于当前时间30分钟且状态为“待支付”的订单。 3. 遍历这些订单,将其状态更新为“已取消”,并执行相关后续操作(如释放库存、发送通知等)。 #### 代码示例: 1. **订单实体类(Order)**: ```java import java.time.LocalDateTime; public class Order { private Long id; private String status; // 状态:如"待支付"、"已支付"、"已取消" private LocalDateTime createTime; // 订单创建时间 // 其他字段... // getter和setter } ``` 2. **订单数据访问层(OrderMapper)**: ```java @Mapper public interface OrderMapper { // 查询超时支付订单 List<Order> selectTimeoutOrders(@Param("timeoutTime") LocalDateTime timeoutTime, @Param("status") String status); // 更新订单状态 void update(Order order); } ``` 3. **定时任务类(OrderTimeoutTask)**: ```java import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.List; @Component public class OrderTimeoutTask { @Autowired private OrderMapper orderMapper; // 每分钟执行一次(cron表达式:每分钟的0秒触发) @Scheduled(cron = "0 * * * * ?") public void checkTimeoutOrders() { // 计算30分钟前的时间点 LocalDateTime timeoutTime = LocalDateTime.now().minusMinutes(30); // 查询状态为"待支付"且创建时间早于timeoutTime的订单 List<Order> orders = orderMapper.selectTimeoutOrders(timeoutTime, "待支付"); // 遍历订单取消 for (Order order : orders) { order.setStatus("已取消"); orderMapper.update(order); // 其他操作:如释放库存、发送通知等 } } } ``` #### 注意事项: - 定时任务的执行周期需要根据业务需求调整,如果对时效性要求高,可以缩短周期(如每10秒),但会增加数据库压力。 - 在分布式环境下,定时任务会在每个节点都执行,可能导致重复操作。可以通过分布式锁或者使用只在一个节点执行定时任务的方式解决(如使用ShedLock或Quartz集群)。 ### 方案二:使用Redis过期监听 #### 实现步骤: 1. 订单创建时,将订单ID存入Redis,并设置过期时间为30分钟。 2. 配置Redis监听器,监听key过期事件。 3. 当key过期时,根据订单ID查询订单状态,如果还是支付,则执行取消操作。 #### 代码示例: 1. **配置Redis监听**: 在Spring Boot配置文件中启用Redis键空间通知: ```properties # application.properties spring.redis.database=0 spring.redis.host=localhost spring.redis.port=6379 # 启用键空间通知 spring.redis.listeners.enabled=true ``` 2. **订单创建时存储Redis**: ```java @Service public class OrderService { @Autowired private RedisTemplate<String, String> redisTemplate; public void createOrder(Order order) { // 保存订单到数据库... // 将订单ID存入Redis,设置过期时间30分钟 String orderKey = "order:timeout:" + order.getId(); redisTemplate.opsForValue().set(orderKey, order.getId().toString(), 30, TimeUnit.MINUTES); } } ``` 3. **配置Redis过期事件监听器**: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; @Configuration public class RedisConfig { @Bean public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); // 订阅所有key过期事件 container.addMessageListener(new MessageListener() { @Override public void onMessage(Message message, byte[] pattern) { // 当key过期时,会触发这里 String expiredKey = new String(message.getBody()); // 我们只处理以"order:timeout:"开头的key if (expiredKey.startsWith("order:timeout:")) { String orderId = expiredKey.split(":")[2]; // 处理订单超时 handleOrderTimeout(Long.parseLong(orderId)); } } }, new PatternTopic("__keyevent@0__:expired")); // 0号数据库的过期事件 return container; } // 处理订单超时 private void handleOrderTimeout(Long orderId) { // 根据orderId查询订单,如果状态还是待支付,则取消 // 注意:这里需要处理并发情况,因为可能用户刚好在过期时支付 // 使用数据库更新条件(状态为待支付)来避免重复取消 // 示例省略,需注入OrderService或OrderMapper } } ``` #### 注意事项: - Redis的键空间通知需要服务器配置(默认关闭),在redis.conf中配置`notify-keyspace-events Ex`。 - 过期事件可能因为Redis重启等原因丢失,所以这种方案不是绝对可靠,通常需要结合定时任务做兜底。 - 在高并发场景下,大量订单同时过期可能导致事件处理堆积,需要考虑异步处理或消息队列缓冲。 ### 方案对比 | 方案 | 优点 | 缺点 | |------|------|------| | 定时任务 | 实现简单,依赖少 | 时效性不够精确(取决于扫描周期),数据库压力大 | | Redis过期监听 | 时效精确,资源消耗低 | 配置复杂,存在事件丢失风险,需额外处理兜底 | ### 推荐方案 对于一般应用,推荐使用**定时任务**,因为实现简单且稳定。如果对时效性要求高,可以使用**Redis过期监听+定时任务兜底**。 ### 相关问题 1. 如何防止定时任务在分布式环境下重复执行? 2. Redis过期事件丢失如何处理? 3. 除了定时任务和Redis,还有哪些延迟消息方案可以实现订单超时取消?</think>### Spring框架实现订单30分钟支付自动取消方案 在Spring框架中实现订单超时自动取消功能,主要有三种主流方案。下面详细介绍每种方案的实现步骤和代码示例: --- ### 方案一:Spring Scheduled定时任务(推荐) **实现原理**:通过Spring的定时任务定期扫描数据库,检查超时订单。 #### 实现步骤: 1. **订单创建时记录时间戳** ```java @Entity public class Order { @Id private Long id; private String status; // "待支付"/"已支付"/"已取消" private LocalDateTime createTime; // 订单创建时间 // 其他字段... } ``` 2. **定时任务扫描超时订单** ```java @Component @Slf4j public class OrderTimeoutTask { @Autowired private OrderRepository orderRepository; // 每分钟执行一次(cron表达式可调) @Scheduled(cron = "0 */1 * * * ?") public void checkTimeoutOrders() { LocalDateTime timeoutThreshold = LocalDateTime.now().minusMinutes(30); // 查询超时支付订单 List<Order> orders = orderRepository.findByStatusAndCreateTimeBefore( "待支付", timeoutThreshold); // 批量取消订单 orders.forEach(order -> { order.setStatus("已取消"); order.setCancelReason("支付超时"); orderRepository.save(order); // 释放库存/发送通知等后续操作 }); } } ``` 3. **启用定时任务配置** ```java @SpringBootApplication @EnableScheduling // 关键注解 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` **优点**:实现简单,依赖少 **缺点**:扫描间隔影响时效性(可通过缩短间隔优化)[^2][^5] --- ### 方案二:Redis过期监听 **实现原理**:利用Redis的Key过期通知机制触发取消操作。 #### 实现步骤: 1. **订单创建时设置Redis键** ```java @Service public class OrderService { @Autowired private RedisTemplate<String, String> redisTemplate; public void createOrder(Order order) { // 保存订单到DB... // 设置Redis键,30分钟后过期 String key = "order:timeout:" + order.getId(); redisTemplate.opsForValue().set(key, order.getId().toString(), 30, TimeUnit.MINUTES); } } ``` 2. **配置Redis监听器** ```java @Component public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { @Autowired private OrderService orderService; public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } @Override public void onMessage(Message message, byte[] pattern) { String expiredKey = message.toString(); if (expiredKey.startsWith("order:timeout:")) { String orderId = expiredKey.split(":")[2]; orderService.cancelOrder(orderId, "支付超时"); } } } ``` 3. **Redis服务器配置** ```conf # redis.conf 启用键空间通知 notify-keyspace-events Ex ``` **优点**:时效精确到秒级 **缺点**:需要Redis集群支持,配置较复杂[^3] --- ### 方案三:消息队列延迟消息(RabbitMQ) **实现原理**:利用消息队列的延迟队列特性 #### 实现步骤: 1. **配置延迟队列** ```java @Configuration public class RabbitMQConfig { // 定义延迟交换机 @Bean public CustomExchange orderDelayExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); return new CustomExchange("order-delay-exchange", "x-delayed-message", true, false, args); } // 定义延迟队列 @Bean public Queue orderDelayQueue() { return new Queue("order.delay.queue", true); } // 绑定队列 @Bean public Binding binding() { return BindingBuilder.bind(orderDelayQueue()) .to(orderDelayExchange()) .with("order.delay.routingKey") .noargs(); } } ``` 2. **发送延迟消息** ```java public void createOrder(Order order) { // 保存订单... // 发送延迟消息 rabbitTemplate.convertAndSend( "order-delay-exchange", "order.delay.routingKey", order.getId(), message -> { message.getMessageProperties() .setDelay(30 * 60 * 1000); // 30分钟延迟 return message; } ); } ``` 3. **消费消息处理超时** ```java @RabbitListener(queues = "order.delay.queue") public void handleTimeoutOrder(String orderId) { Order order = orderRepository.findById(orderId); if ("待支付".equals(order.getStatus())) { orderService.cancelOrder(orderId, "支付超时"); } } ``` **优点**:解耦性好,支持分布式 **缺点**:依赖消息队列中间件[^4] --- ### 方案对比与选型建议 | 方案 | 时效性 | 复杂度 | 依赖 | 适用场景 | |--------------------|----------|--------|--------------|----------------------| | Spring Scheduled | 分钟级 | ★☆☆ | 无 | 中小型系统 | | Redis过期监听 | 秒级 | ★★☆ | Redis | 高时效性要求系统 | | 消息队列延迟消息 | 秒级 | ★★★ | RabbitMQ等 | 分布式大型系统 | **推荐选择**: - 普通应用:方案一(Spring Scheduled),实现简单成本低 - 高并发系统:方案三(消息队列),通过削峰提高系统稳定性 - 实时性要求高:方案二(Redis),但需注意事件丢失风险 --- ### 关键注意事项 1. **并发处理**:使用数据库乐观锁(如JPA的@Version)防止重复取消 ```java @Version private Long version; // 乐观锁字段 ``` 2. **幂等性设计**:在取消操作前二次校验订单状态 3. **事务管理**:订单取消与库存释放需在同一事务中 4. **兜底方案**:建议组合使用定时任务(方案一)+ 其他方案,确保可靠性 --- ### 相关问题 1. 如何防止定时任务在分布式环境下重复执行? 2. Redis过期事件丢失该如何处理? 3. 消息队列方案中如何保证消息不丢失? 4. 订单取消后需要执行哪些关联操作? 5. 如何优化数据库查询避免全表扫描? [^1]: 在电子商务和其他采用在线支付的应用场景中,为了优化订单处理流程,通常会引入订单自动取消功能 [^2]: 通过Spring Boot的@Scheduled定时任务实现订单超时检查 [^3]: 利用Redis的key过期特性实现订单超时自动取消 [^4]: 使用消息队列延迟消息实现分布式环境下的订单超时处理 [^5]: 定时任务方案中需要合理设置执行频率以平衡时效性和系统压力
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值