2026/4/18 8:25:31
网站建设
项目流程
深圳网站建设 排行榜,dw网站模板下载,有没有做旅游攻略的网站,vps 同时wordpress vpn大家好#xff0c;我是小悟。
听说你要用Redis来处理超时支付订单#xff1f;Redis就像一个住在你内存里的闪电侠#xff0c;它跑得飞快#xff0c;但记性有点差#xff08;断电就失忆#xff09;。它是个键值对存储的社交恐惧症患者#xff0c;就喜欢简单直接的交流。不…大家好我是小悟。听说你要用Redis来处理超时支付订单Redis就像一个住在你内存里的闪电侠它跑得飞快但记性有点差断电就失忆。它是个键值对存储的社交恐惧症患者就喜欢简单直接的交流。不过对付订单超时这种“限时任务”它可是专业的“时间管理大师”为什么选Redis来做这个你开了一家网红奶茶店顾客下单后30分钟不付款订单就自动取消。你总不能雇个店员盯着每个订单看30分钟吧Redis的过期键和发布订阅功能就是那个不知疲倦的“自动取消专员”详细步骤让我们开始组装这个“订单取消机器人”第1步引入Redis依赖包!-- pom.xml 里加入这个“能量饮料” -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependency第2步配置Redis连接# application.yml spring: redis: # Redis的地址默认是本地6379端口 host: localhost port: 6379 # 密码如果设置了的话 password: # 数据库索引就像给闪电侠安排的第几个房间 database: 0 lettuce: pool: # 连接池配置别让闪电侠累着了 max-active: 8 max-idle: 8 min-idle: 0 max-wait: 100ms第3步配置RedisTemplateimport com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // 键的序列化 - 字符串序列化 template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); // 值的序列化 - JSON序列化 Jackson2JsonRedisSerializerObject serializer new Jackson2JsonRedisSerializer(Object.class); ObjectMapper mapper new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.activateDefaultTyping( mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); serializer.setObjectMapper(mapper); template.setValueSerializer(serializer); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }第4步订单实体类import lombok.Data; import java.time.LocalDateTime; Data public class Order { private String orderId; // 订单ID private String userId; // 用户ID private Double amount; // 订单金额 private Integer status; // 订单状态0-待支付1-已支付2-已取消 private LocalDateTime createTime;// 创建时间 private LocalDateTime expireTime;// 过期时间 // 判断是否已过期 public boolean isExpired() { return LocalDateTime.now().isAfter(expireTime); } }第5步Redis服务类import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; Service public class RedisOrderService { Autowired private RedisTemplateString, Object redisTemplate; Autowired private StringRedisTemplate stringRedisTemplate; // 订单前缀避免键冲突 private static final String ORDER_KEY_PREFIX order:pay:; private static final String ORDER_EXPIRE_CHANNEL order.expire; /** * 创建订单并设置30分钟过期时间 * 就像给闪电侠说“盯着这个订单30分钟后提醒我” */ public void createOrderWithExpire(Order order, int expireMinutes) { String orderKey ORDER_KEY_PREFIX order.getOrderId(); // 保存订单到Redis30分钟后自动删除 redisTemplate.opsForValue().set( orderKey, order, expireMinutes, TimeUnit.MINUTES ); // 同时设置一个简单的标志用于监听过期事件 stringRedisTemplate.opsForValue().set( orderKey :flag, 1, expireMinutes, TimeUnit.MINUTES ); System.out.println(订单 order.getOrderId() 已放入Redis设置 expireMinutes 分钟后过期); } /** * 用户支付成功删除过期键 * 相当于告诉闪电侠“不用盯了顾客付钱了” */ public void handlePaymentSuccess(String orderId) { String orderKey ORDER_KEY_PREFIX orderId; // 手动删除订单和标志 redisTemplate.delete(orderKey); stringRedisTemplate.delete(orderKey :flag); System.out.println(订单 orderId 支付成功已从Redis移除); } /** * 检查订单是否还存在是否已过期 */ public boolean isOrderExist(String orderId) { String orderKey ORDER_KEY_PREFIX orderId; return Boolean.TRUE.equals(redisTemplate.hasKey(orderKey)); } /** * 获取订单信息 */ public Order getOrder(String orderId) { String orderKey ORDER_KEY_PREFIX orderId; return (Order) redisTemplate.opsForValue().get(orderKey); } }第6步Redis过期监听配置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.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; Configuration public class RedisExpireConfig { Bean public RedisMessageListenerContainer container( RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); // 监听所有key过期事件 container.addMessageListener(listenerAdapter, new PatternTopic(__keyevent0__:expired)); return container; } Bean public MessageListenerAdapter listenerAdapter(RedisKeyExpireListener receiver) { return new MessageListenerAdapter(receiver, handleMessage); } }第7步过期事件监听器import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; Component public class RedisKeyExpireListener extends MessageListenerAdapter { Autowired private OrderService orderService; /** * 当Redis键过期时这个方法会被调用 * 闪电侠会喊“嘿那个订单过期了” */ Override public void handleMessage(Message message, byte[] pattern) { String expiredKey new String(message.getBody(), StandardCharsets.UTF_8); // 只处理我们的订单过期键 if (expiredKey.startsWith(order:pay:)) { // 去掉:flag后缀获取订单ID String orderId expiredKey .replace(order:pay:, ) .replace(:flag, ); System.out.println(Redis报告订单 orderId 已超时); // 处理订单超时逻辑 orderService.cancelExpiredOrder(orderId); } } }第8步订单服务层import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; Service public class OrderService { Autowired private RedisOrderService redisOrderService; Autowired private OrderRepository orderRepository; /** * 创建订单 */ Transactional public Order createOrder(String userId, Double amount) { Order order new Order(); order.setOrderId(generateOrderId()); order.setUserId(userId); order.setAmount(amount); order.setStatus(0); // 待支付 order.setCreateTime(LocalDateTime.now()); order.setExpireTime(LocalDateTime.now().plusMinutes(30)); // 保存到数据库 orderRepository.save(order); // 保存到Redis并设置30分钟过期 redisOrderService.createOrderWithExpire(order, 30); return order; } /** * 处理支付回调 */ Transactional public void handlePaymentCallback(String orderId) { // 检查订单是否已过期 if (!redisOrderService.isOrderExist(orderId)) { throw new RuntimeException(订单已超时请重新下单); } // 更新订单状态为已支付 orderRepository.updateOrderStatus(orderId, 1); // 从Redis移除过期键 redisOrderService.handlePaymentSuccess(orderId); System.out.println(订单 orderId 支付处理完成); } /** * 取消超时订单 */ Transactional public void cancelExpiredOrder(String orderId) { // 再次检查防止重复处理 Order order orderRepository.findById(orderId); if (order ! null order.getStatus() 0) { order.setStatus(2); // 已取消 orderRepository.save(order); // 可以在这里添加其他逻辑比如释放库存、发送通知等 System.out.println(订单 orderId 因超时未支付已被自动取消); // 发送取消通知 sendCancelNotification(order); } } /** * 发送取消通知模拟 */ private void sendCancelNotification(Order order) { // 这里可以集成消息队列、邮件、短信等 System.out.println(发送通知亲爱的用户 order.getUserId() 您的订单 order.getOrderId() 因超时未支付已取消); } private String generateOrderId() { return ORD System.currentTimeMillis() (int)(Math.random() * 1000); } }第9步控制器层import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/orders) public class OrderController { Autowired private OrderService orderService; Autowired private RedisOrderService redisOrderService; /** * 创建订单 */ PostMapping(/create) public ApiResult createOrder(RequestParam String userId, RequestParam Double amount) { try { Order order orderService.createOrder(userId, amount); return ApiResult.success(订单创建成功, order); } catch (Exception e) { return ApiResult.error(订单创建失败 e.getMessage()); } } /** * 模拟支付 */ PostMapping(/pay) public ApiResult payOrder(RequestParam String orderId) { try { // 模拟支付处理时间 Thread.sleep(1000); orderService.handlePaymentCallback(orderId); return ApiResult.success(支付成功); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return ApiResult.error(支付处理中断); } catch (Exception e) { return ApiResult.error(支付失败 e.getMessage()); } } /** * 检查订单状态 */ GetMapping(/status/{orderId}) public ApiResult checkOrderStatus(PathVariable String orderId) { boolean exists redisOrderService.isOrderExist(orderId); if (exists) { return ApiResult.success(订单有效请尽快支付); } else { return ApiResult.success(订单已超时或不存在); } } } // 简单的返回结果类 class ApiResult { private boolean success; private String message; private Object data; // 构造方法和getter/setter省略... public static ApiResult success(String message) { return new ApiResult(true, message, null); } public static ApiResult success(String message, Object data) { return new ApiResult(true, message, data); } public static ApiResult error(String message) { return new ApiResult(false, message, null); } }第10步别忘了开启Redis的键空间通知重要在Redis配置文件(redis.conf)中或通过Redis命令行开启# 方式1配置文件 notify-keyspace-events Ex # 方式2命令行临时生效 redis-cli config set notify-keyspace-events Ex或者在你的Spring Boot应用启动时自动配置import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; Component public class RedisConfigRunner implements CommandLineRunner { Autowired private StringRedisTemplate stringRedisTemplate; Override public void run(String... args) { // 开启键过期事件通知 stringRedisTemplate.getConnectionFactory() .getConnection() .serverCommands() .configSet(notify-keyspace-events, Ex); System.out.println(Redis键空间通知已开启); } }完整的工作流程顾客下单POST /orders/create→ 订单存入数据库和Redis开始30分钟倒计时Redis盯梢闪电侠开始计时30分钟寸步不离顾客支付30分钟内支付POST /orders/pay→ Redis删除订单交易完成超过30分钟Redis键自动过期 → 触发过期事件 → 自动取消订单系统通知给顾客发送“订单已取消”的贴心小提示一些高级玩法方案优化使用Redisson的延迟队列更可靠import org.redisson.api.RBlockingDeque; import org.redisson.api.RDelayedQueue; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.concurrent.TimeUnit; Service public class RedissonOrderService { Autowired private RedissonClient redissonClient; private RBlockingDequeString orderQueue; private RDelayedQueueString delayedQueue; PostConstruct public void init() { orderQueue redissonClient.getBlockingDeque(orderDelayQueue); delayedQueue redissonClient.getDelayedQueue(orderQueue); // 启动消费者线程 new Thread(this::consumeExpiredOrders).start(); } /** * 添加延迟订单 */ public void addDelayOrder(String orderId, long delay, TimeUnit unit) { delayedQueue.offer(orderId, delay, unit); System.out.println(订单 orderId 已加入延迟队列 delay unit 后过期); } /** * 消费过期订单 */ private void consumeExpiredOrders() { while (true) { try { // 阻塞获取过期订单 String orderId orderQueue.take(); System.out.println(延迟队列报告订单 orderId 已过期); // 处理订单取消逻辑... } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }注意事项防重复处理幂等性// 在OrderService中添加防重复处理 Transactional public void cancelExpiredOrder(String orderId) { // 使用Redis分布式锁防止多个实例同时处理同一个订单 String lockKey order:cancel:lock: orderId; Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 30, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { try { // 再次检查订单状态双重校验 Order order orderRepository.findById(orderId); if (order ! null order.getStatus() 0) { // 更新订单状态 order.setStatus(2); orderRepository.save(order); System.out.println(订单 orderId 已取消); } } finally { // 释放锁 redisTemplate.delete(lockKey); } } }总结性能爆表Redis基于内存操作处理速度堪比闪电侠跑步精准定时Redis的过期机制精准可靠误差极小解耦神器业务逻辑和定时任务分离代码清爽不油腻扩展性强轻松应对高并发加个Redis集群就能撑起双11资源友好不需要额外的定时任务中间件省心省力但也要注意这些“坑”Redis持久化记得配置RDB/AOF不然闪电侠“失忆”就麻烦了网络波动Redis挂了怎么办要有降级方案事件丢失Redis的过期事件可能丢失重要业务要有补偿机制时钟同步多服务器时间要同步别自己人跟自己人“打架”最后想象一下没有Redis时你的数据库被定时任务扫得气喘吁吁每次都要问“哪些订单超时了”有了Redis后Redis主动报告“嘿这几个订单超时了快处理”这就好比从“挨家挨户查水表”变成了“水表自己打电话报警”效率提升不是一点点好的架构就是让合适的工具做合适的事。Redis就是这个场景下的“时间管理大师”谢谢你看我的文章既然看到这里了如果觉得不错随手点个赞、转发、在看三连吧感谢感谢。那我们下次再见。您的一键三连是我更新的最大动力谢谢山水有相逢来日皆可期谢谢阅读我们再会我手中的金箍棒上能通天下能探海