【转载】【技术选型】订单超时未支付处理

内容主要转载:https://developer.aliyun.com/article/1161311  阿里 订单超时处理的几种方案及分析

DelayQueue->RabbitMQ->RocketMQ->Redis过期监听->定时任务分布式处理(如阿里SchedulerX)

// 阿里内部,几乎所有的业务都使用基于定时任务分布式批处理的超时中心来做订单超时处理,SLA可以做到30秒以内:

背景:

电商场景下,一个订单流程中有许多环节要用到超时处理,包括但不限于:

  • 买家超时未付款:比如超过15分钟没有支付,订单自动取消。
  • 商家超时未发货:比如商家超过1个月没发货,订单自动取消。
  • 买家超时未收货:比如商家发货后,买家没有在14天内点击确认收货,则系统默认自动收货。

关键词:时间轮TimeWheelTimer

 

定时任务:定时轮询数据库,缺点:时效性差,会有一定的延迟;效率低;数据库压力大

被动取消:客户查看时才取消

    缺点:如果用户不进行查询订单的操作,该订单就永远不会被取消。不会取消的订单,也就可能意味着库存可能被占用。
    实际方案:可能是被动取消 + 定时任务的这种组合实现方式。 -- 实现简单

1. JDK自带的延时队列

JDK中提供了一种延迟队列数据结构DelayQueue,其本质是封装了PriorityQueue,可以把元素进行排序。

 

  1. 把订单插入DelayQueue中,以超时时间作为排序条件,将订单按照超时时间从小到大排序。
  2. 起一个线程不停轮询队列的头部,如果订单的超时时间到了,就出队进行超时处理,并更新订单状态到数据库中。
  3. 为了防止机器重启导致内存中的DelayQueue数据丢失,每次机器启动的时候,需要从数据库中初始化未结束的订单,加入到DelayQueue中。

优点:简单,不需要借助其他第三方组件,成本低。

缺点:

  • 所有超时处理订单都要加入到DelayQueue中,占用内存大。
  • 没法做到分布式处理,只能在集群中选一台leader专门处理,效率低。
  • 不适合订单量比较大的场景。

2. RabbitMQ的延时消息

RabbitMQ的延时消息主要有两个解决方案:

  • RabbitMQ Delayed Message Plugin
  • 消息的TTL+死信Exchange

RabbitMQ Delayed Message Plugin是官方提供的延时消息插件,虽然使用起来比较方便,但是不是高可用的,如果节点挂了会导致消息丢失。引用官网原文:

Delayed messages are stored in a Mnesia table (also see Limitations below) with a single disk replica on the current node. They will survive a node restart. While timer(s) that triggered scheduled delivery are not persisted, it will be re-initialised during plugin activation on node start. Obviously, only having one copy of a scheduled message in a cluster means that losing that node or disabling the plugin on it will lose the messages residing on that node.

 消息的TTL+死信Exchange解决方案,先要了解两个概念:

  • TTL:即消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL,如果对队列设置,则队列中所有的消息都具有相同的过期时间。超过了这个时间,我们认为这个消息就死了,称之为死信。
  • 死信Exchange(DLX):一个消息在满足以下条件会进入死信交换机
    • 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。
    • TTL到期的消息。
    • 队列满了被丢弃的消息。

一个延时消息的流程如下图:

  1. 定义一个BizQueue,用来接收死信消息,并进行业务消费。
  2. 定义一个死信交换机(DLXExchange),绑定BizQueue,接收延时队列的消息,并转发给BizQueue。
  3. 定义一组延时队列DelayQueue_xx,分别配置不同的TTL,用来处理固定延时5s、10s、30s等延时等级,并绑定到DLXExchange。
  4. 定义DelayExchange,用来接收业务发过来的延时消息,并根据延时时间转发到不同的延时队列中。

 

  • 优点:可以支持海量延时消息,支持分布式处理。
  • 缺点:
    • 不灵活,只能支持固定延时等级。
    • 使用复杂,要配置一堆延时队列。

3. RocketMQ的定时消息

RocketMQ支持任意秒级的定时消息,如下图所示

image

使用门槛低,只需要在发送消息的时候设置延时时间即可,以java代码为例:

MessageBuilder messageBuilder = null;
Long deliverTimeStamp = System.currentTimeMillis() + 10L * 60 * 1000; //延迟10分钟
Message message = messageBuilder.setTopic("topic")
        //设置消息索引键,可根据关键字精确查找某条消息。
        .setKeys("messageKey")
        //设置消息Tag,用于消费端根据指定Tag过滤消息。
        .setTag("messageTag")
        //设置延时时间
        .setDeliveryTimestamp(deliverTimeStamp) 
        //消息体
        .setBody("messageBody".getBytes())
        .build();
SendReceipt sendReceipt = producer.send(message);
System.out.println(sendReceipt.getMessageId());

RocketMQ的定时消息是如何实现的呢?

RocketMQ中,使用了经典的时间轮算法。通过TimerWheel来描述时间轮不同的时刻,通过TimerLog来记录不同时刻的消息。

TimerWheel中的每一格代表着一个时刻,同时会有一个firstPos指向这个刻度下所有定时消息的首条TimerLog记录的地址,一个lastPos指向这个刻度下所有定时消息最后一条TimerLog的记录的地址。并且,对于所处于同一个刻度的的消息,其TimerLog会通过prevPos串联成一个链表。

image

当需要新增一条记录的时候,例如现在我们要新增一个 “1-4”。那么就将新记录的 prevPos 指向当前的 lastPos,即 “1-3”,然后修改 lastPos 指向 “1-4”。这样就将同一个刻度上面的 TimerLog 记录全都串起来了。

image

 

  • 优点
    • 精度高,支持任意时刻。
    • 使用门槛低,和使用普通消息一样。
  • 缺点
    • 使用限制:定时时长最大值24小时。
    • 成本高:每个订单需要新增一个定时消息,且不会马上消费,给MQ带来很大的存储成本。
    • 同一个时刻大量消息会导致消息延迟:定时消息的实现逻辑需要先经过定时存储等待触发,定时时间到达后才会被投递给消费者。因此,如果将大量定时消息的定时时间设置为同一时刻,则到达该时刻后会有大量消息同时需要被处理,会造成系统压力过大,导致消息分发延迟,影响定时精度。

4. Redis的过期监听

 

5. 定时任务分布式批处理

参考:https://www.cnblogs.com/clarino/p/16157761.html  中中间件方案

 

posted @ 2023-01-10 13:55  飞翔在天  阅读(114)  评论(0编辑  收藏  举报