订单到期关闭如何实现
典型回答 在电商、支付等系统中,一般都是先创建订单(支付单),再给用户一定的时间进行支付,如果没有按时支付的话,就需要把之前的订单(支付单)取消掉。这种类似的场景有很多,还有比如到期自动收货、超时自动退款、下单后自动发送短信等等都是类似的业务问题。 订单的到期关闭的实现有很多种方式,分别有: 1、被动关闭(不推荐) 2、定时任务(推荐,适合时间精确度要求不高的场景) 3、DelayQueue(不推荐,基于内存,无法持久化) 4、时间轮(不推荐,基于内存,无法持久化) 5、kafka(MQ 方案不推荐,大量无效调度) 6、RocketMQ延迟消息(MQ 方案不推荐,大量无效调度) 7、RabbitMQ死信队列(MQ 方案不推荐,大量无效调度) 8、RabbitMQ插件(MQ 方案不推荐,大量无效调度) 9、Redis过期监听(不推荐,容易丢消息) 10、Redis的ZSet(不推荐,可能会重复消费) 11、Redisson(推荐,可以用) 实现的复杂度上(包含用到的框架的依赖及部署): Redisson > RabbitMQ插件 > RabbitMQ死信队列 > RocketMQ延迟消息 ≈ Redis的zset > Redis过期监听 ≈ kafka时间轮 > 定时任务 > Netty的时间轮 > JDK自带的DelayQueue > 被动关闭 不同的场景中也适合不同的方案: 自己玩玩:被动关闭 单体应用,业务量不大:Netty的时间轮、JDK自带的DelayQueue、定时任务 分布式应用,业务量不大:Redis过期监听、RabbitMQ死信队列、Redis的zset、定时任务 分布式应用,业务量大、并发高:Redisson、RabbitMQ插件、kafka时间轮、RocketMQ延迟消息、定时任务 业务量特别大:被动关闭+定时任务(结合分片任务+多线程+生产者消费者+时间轮等方案) 总体考虑的话,考虑到成本,方案完整性、以及方案的复杂度,还有用到的第三方框架的流行度来说,个人比较建议优先考虑定时任务、Redisson+Redis、RabbitMQ插件、RocketMQ延迟消息等方案。 但是,如果考虑到订单到期关闭的业务特点,如果在订单量特别大的时候,MQ其实并不适合: ✅为什么不建议使用MQ实现订单到期关闭? 扩展知识 一、被动关闭 在解决这类问题的时候,有一种比较简单的方式,那就是通过业务上的被动方式来进行关单操作。 简单点说,就是订单创建好了之后。我们系统上不做主动关单,什么时候用户来访问这个订单了,再去判断时间是不是超过了过期时间,如果过了时间那就进行关单操作,然后再提示用户。 这种做法是最简单的,基本不需要开发定时关闭的功能,但是他的缺点也很明显,那就是如果用户一直不来查看这个订单,那么就会有很多脏数据冗余在数据库中一直无法被关单。 还有一个缺点,那就是需要在用户的查询过程中进行写的操作,一般写操作都会比读操作耗时更长,而且有失败的可能,一旦关单失败了,就会导致系统处理起来比较复杂。 所以,这种方案只适合于自己学习的时候用,任何商业网站中都不建议使用这种方案来实现订单关闭的功能。 二、定时任务 定时任务关闭订单,这是很容易想到的一种方案。 具体实现细节就是我们通过一些调度平台来实现定时执行任务,任务就是去扫描所有到期的订单,然后执行关单动作。 这个方案的优点也是比较简单,实现起来很容易,基于Timer、ScheduledThreadPoolExecutor、或者像xxl-job这类调度框架都能实现,但是有以下几个问题: 1、时间不精准。 一般定时任务基于固定的频率、按照时间定时执行的,那么就可能会发生很多订单已经到了超时时间,但是定时任务的调度时间还没到,那么就会导致这些订单的实际关闭时间要比应该关闭的时间晚一些。 2、无法处理大订单量。 定时任务的方式是会把本来比较分散的关闭时间集中到任务调度的那一段时间,如果订单量比较大的话,那么就可能导致任务执行时间很长,整个任务的时间越长,订单被扫描到时间可能就很晚,那么就会导致关闭时间更晚。 3、对数据库造成压力。 定时任务集中扫表,这会使得数据库IO在短时间内被大量占用和消耗,如果没有做好隔离,并且业务量比较大的话,就可能会影响到线上的正常业务。 4、分库分表问题。 订单系统,一旦订单量大就可能会考虑分库分表,在分库分表中进行全表扫描,这是一个极不推荐的方案。 这些问题的解决方案如下: ✅定时任务扫表的方案有什么缺点? ...