在支付系统中,订单通常具有时效性。例如下单后30分钟仍未完成支付,则必须取消订单,无法进行后续流程。说到这里,大家的第一反应可能是启动一个定时任务去轮询订单状态,看看是否支付完成了。如果超时还没有完成,则修改订单的close字段。当然,在数据量小的时候这样做没有错,但是如果订单量变大,那么读取数据就会出现瓶颈。毕竟全表扫描是相当耗时的。对于定时任务的这个缺陷,需要关闭订单的情况多半依赖于延迟任务。这里就是延迟任务和定时任务最大的区别。定时任务是有执行周期的,延时任务是在一定的事件中执行的。触发后一定时间内执行,没有执行周期。对于延迟任务,大家可能对RabbitMQ的延迟队列比较熟悉,用起来得心应手,但是你知道Redis也可以实现延迟任务的功能吗?今天我们就来看看如何实现它。Redis实现的延迟队列需要Redisson的依赖:org.redissonredisson-spring-boot-starter3.10.7>/dependency>首先实现添加任务到延迟队列的方法。为了测试方便,我们将延迟时间设置为30秒。@ComponentpublicclassUnpaidOrderQueue{@AutowiredRedissonClientredissonClient;publicvoidaddUnpaid(StringorderId){RBlockingQueueblockingFairQueue=redissonClient.getBlockingQueue("orderQueue");RDelayedQueuedelayedQueue=redissonClient.getDelayedQueue(blockingFairQueue);(System.out.Time.printl()).toString(JodaUtil.HH_MM_SS)+"添加任务到延迟队列");delayedQueue.offer(orderId,30,TimeUnit.SECONDS);}}给队列添加一个监听方法,在springboot中通过实现CommandLineRunner接口启动时就开始执行:@ComponentpublicclassQueueRunnerimplementsCommandLineRunner{@AutowiredprivateRedissonClientredissonClient;@AutowiredprivateOrderServiceorderService;@Overridevopublic(String...args)throwsException{newThread(()->{RBlockingQueueblockingFairQueue=redissonClient.getBlockingQueue("orderQueue");RDelayedQueuedelayedQueue=redissonClient.getDelayedQueue(blockingFairQueue);delayedQueue.offer(null,1,TimeUnit.SECONDS);while(true){StringorderId=null;try{orderId=blockingFairQueue.take();}catch(Exceptione){continue;}if(orderId==null){continue;}System.out.println(String.format(DateTime.now().toString(JodaUtil.HH_MM_SS)+"延迟队列收到:"+orderId));System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+"查看订单是否已支付");if(orderService.isTimeOut(orderId)){orderService.closeOrder(orderId);}}}).start();}}方法中,启动一个单独的线程监听,如果有任务进入延迟队列,则拿到订单号后,调用我们OrderService提供的服务检测订单是否过期。如果到期,则执行平仓操作创建一个简单的OrderService用于测试,提供创建订单、检测超时和关闭订单的方法:@ServicepublicclassOrderService{@AutowiredUnpaidOrderQueueunpaidOrderQueue;publicvoidcreateOrder(Stringorder){System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+"创建订单:"+order);unpaidOrderQueue.addUnpaid(order);}publicbooleanisTimeOut(StringorderId){returntrue;}publicvoidcloseOrder(StringorderId){System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+"关闭订单");}}执行请求,看结果:订单创建后30秒,检测到延迟队列中有任务,调用检测超时方法检测订单未完成后自动关闭订单.除了上面的延迟队列方法,Redisson还提供了另一种优雅关闭订单的方法。方法很简单,就是通过监听即将过期的键值。创建一个继承KeyExpirationEventMessageListener的类,重写其中的onMessage方法,用于监听过期key。一旦缓存过期,就会调用onMessage方法:@ComponentpublicclassRedisExpiredListenerextendsKeyExpirationEventMessageListener{publicstaticfinalStringUNPAID_PREFIX="unpaidOrder:";@AutowiredOrderServiceorderExp;listeneris(RedisMessageListenerContainerlistenerContainer){super(listenerContainer);}@OverridepublicvoidonMessage(Messagemessage,byte[]pattern){StringexpiredKey=message.toString();if(expiredKey.startsWith(UNPAID_PREFIX)){System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+""+expiredKey+"expired");orderService.closeOrder(expiredKey);}}}因为可能有很多key过期事件,所以需要给订单过期key加上前缀,用来判断过期key是否属于订单事件,如果是,则关闭订单操作。然后写一个测试接口,创建订单,接收支付成功的回调结果:@RestController@RequestMapping("order")id;System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+"创建订单:"+orderId);redisTemplate.opsForValue().set(orderId,orderId,30,TimeUnit.SECONDS);returnid;}@GetMapping("fallback")publicvoidsuccessFallback(Stringid){StringorderId=RedisExpiredListener.UNPAID_PREFIX+id;redisTemplate.delete(orderId);}}订单支付成功后,我们一般会收到第三方支付成功party异步回调通知。如果支付完成后收到这个回调,那么我们会主动删除缓存的未支付订单,然后就不会去监听这个订单的orderId过期事件了。但是这种方式有一个缺点,就是只能监听过期的缓存key,无法获取到对应的value。使用延迟队列,可以通过在RBlockingQueue中加入泛型来保存更多的订单信息,比如直接在队列中存储对象:RBlockingQueueblockingFairQueue=redissonClient.getBlockingQueue("orderQueue");RDelayedQueuedelayedQueue=redissonClient。getDelayedQueue(blockingFairQueue);这样我们在从延迟队列中获取的时候,就可以获取到更多我们需要的属性。结合以上两种方式,监控过期更简单,但也有一定的局限性。如果我们只需要判断顺序,那么这个函数就可以满足我们的需求。如果我们需要在过期时获取更多的订单属性,那么使用延迟队列是比较合适的。选择哪一个取决于您的业务场景。