网站关键字设置,当下 如何做网站赚钱,wordpress 收费 主题,简易制作网站文章目录1.订单的过程分析2.JDK自带的延时队列 (单机)3.RabbitMQ的延时消息 (消息队列方案)4.RocketMQ的定时消息 (消息队列方案)5.Redis过期监听 (Redis方案)6.定时任务分布式批处理 (扫表轮训方案)7.总结1.订单的过程分析 一个订单流程中有许多环节要用到超时处理
买家超时未…
文章目录1.订单的过程分析2.JDK自带的延时队列 (单机)3.RabbitMQ的延时消息 (消息队列方案)4.RocketMQ的定时消息 (消息队列方案)5.Redis过期监听 (Redis方案)6.定时任务分布式批处理 (扫表轮训方案)7.总结1.订单的过程分析 一个订单流程中有许多环节要用到超时处理
买家超时未付款比如超过15分钟没有支付订单自动取消。商家超时未发货比如商家超过1个月没发货订单自动取消。买家超时未收货比如商家发货后买家没有在14天内点击确认收货则系统默认自动收货。
超时订单的结局方式:
扫表轮训懒删除消息队列实现Redis实现
2.JDK自带的延时队列 (单机)
JDK中提供了一种延迟队列数据结构DelayQueue其本质是封装了PriorityQueue可以把元素进行排序。 把订单插入DelayQueue中以超时时间作为排序条件将订单按照超时时间从小到大排序。 起一个线程不停轮询队列的头部如果订单的超时时间到了就出队进行超时处理并更新订单状态到数据库中。 为了防止机器重启导致内存中的DelayQueue数据丢失每次机器启动的时候需要从数据库中初始化未结束的订单加入到DelayQueue中。
优点简单不需要借助其他第三方组件成本低。
缺点 所有超时处理订单都要加入到DelayQueue中占用内存大。 没法做到分布式处理只能在集群中选一台leader专门处理效率低。 不适合订单量比较大的场景。
3.RabbitMQ的延时消息 (消息队列方案) RabbitMQ Delayed Message Plugin 消息的TTL死信Exchange
RabbitMQ Delayed Message Plugin是官方提供的延时消息插件虽然使用起来比较方便但是不是高可用的如果节点挂了会导致消息丢失。
消息的TTL死信Exchange解决方案: 定义一个BizQueue用来接收死信消息并进行业务消费。 定义一个死信交换机(DLXExchange)绑定BizQueue接收延时队列的消息并转发给BizQueue。 定义一组延时队列DelayQueue_xx分别配置不同的TTL用来处理固定延时5s、10s、30s等延时等级并绑定到DLXExchange。 定义DelayExchange用来接收业务发过来的延时消息并根据延时时间转发到不同的延时队列中。
优点可以支持海量延时消息支持分布式处理。 缺点 不灵活只能支持固定延时等级。 使用复杂要配置一堆延时队列。
4.RocketMQ的定时消息 (消息队列方案) 只需要在发送消息的时候设置延时时间即可
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定时消息的实现:
使用了经典的时间轮算法, 通过TimerWheel来描述时间轮不同的时刻通过TimerLog来记录不同时刻的消息。 TimerWheel中的每一格代表着一个时刻同时会有一个firstPos指向这个刻度下所有定时消息的首条TimerLog记录的地址一个lastPos指向这个刻度下所有定时消息最后一条TimerLog的记录的地址。并且对于所处于同一个刻度的的消息其TimerLog会通过prevPos串联成一个链表。 当需要新增一条记录的时候例如现在我们要新增一个 “1-4”。那么就将新记录的 prevPos 指向当前的 lastPos即 “1-3”然后修改 lastPos 指向 “1-4”。这样就将同一个刻度上面的 TimerLog 记录全都串起来了。 优点: 精度高支持任意时刻。 使用门槛低和使用普通消息一样。
缺点 使用限制定时时长最大值24小时。 成本高每个订单需要新增一个定时消息且不会马上消费给MQ带来很大的存储成本。 同一个时刻大量消息会导致消息延迟定时消息的实现逻辑需要先经过定时存储等待触发定时时间到达后才会被投递给消费者。因此如果将大量定时消息的定时时间设置为同一时刻则到达该时刻后会有大量消息同时需要被处理会造成系统压力过大导致消息分发延迟影响定时精度。
5.Redis过期监听 (Redis方案)
删除过期的key的时候, 进行判断状态是否是超时状态, 然后进行关闭订单
Redis支持过期监听也能达到和RocketMQ定时消息一样的能力
1.redis配置文件开启notify-keyspace-events Ex 2.监听key的过期回调
Configuration
public class RedisListenerConfig {BeanRedisMessageListenerContainer container(RedisConnectionFactory factory){RedisMessageListenerContainer containernew RedisMessageListenerContainer();container.setConnectionFactory(factory);return container;}
}Component
public class RedisKeyExpirationListerner extends KeyExpirationEventMessageListener {public RedisKeyExpirationListerner(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}Overridepublic void onMessage(Message message, byte[] pattern) {String keyExpira message.toString();System.out.println(监听到key expiredKey 已过期);}
}在实际生产上不推荐
每当我们对一个key设置了过期时间Redis就会把该key带上过期时间存到过期字典中在redisDb中通过expires字段维护 typedef struct redisDb {dict *dict; /* 维护所有key-value键值对 */dict *expires; /* 过期字典维护设置失效时间的键 */....
} redisDb;过期字典本质上是一个链表 key是一个指针指向某个键对象。 value是一个long long类型的整数保存了key的过期时间。 Redis主要使用了定期删除和惰性删除策略来进行过期key的删除
Redis过期删除是不精准的在订单超时处理的场景下惰性删除基本上也用不到无法保证key在过期的时候可以立即删除更不能保证能立即通知。如果订单量比较大那么延迟几分钟也是有可能的。
Redis过期通知也是不可靠的Redis在过期通知的时候如果应用正好重启了那么就有可能通知事件就丢了会导致订单一直无法关闭有稳定性问题。如果一定要使用Redis过期监听方案建议再通过定时任务做补偿机制。
如果无法删除的话会导致库存数据始终占着, 但是未支付也未取消支付。
6.定时任务分布式批处理 (扫表轮训方案)
开启一个定时任务去扫描订单表, 获取待支付状态的数据, 判断将一些超时状态的数据进行批量修改状态。 通过定时任务不停轮询数据库的订单将已经超时的订单捞出来分发给不同的机器分布式处理 稳定性强基于通知的方案比如MQ和Redis比较担心在各种极端情况下导致通知的事件丢了。使用定时任务跑批只需要保证业务幂等即可如果这个批次有些订单没有捞出来或者处理订单的时候应用重启了下一个批次还是可以捞出来处理稳定性非常高。 效率高基于MQ的方案需要一个订单一个定时消息consumer处理定时消息的时候也需要一个订单一个订单更新对数据库tps很高。使用定时任务跑批方案一次捞出一批订单处理完了可以批量更新订单状态减少数据库的tps。在海量订单处理场景下批量处理效率最高。 可运维基于数据库存储可以很方便的对订单进行修改、暂停、取消等操作所见即所得。如果业务跑失败了还可以直接通过sql修改数据库来进行批量运维。 成本低相对于其他解决方案要借助第三方存储组件复用数据库的成本大大降低。
缺点没法做到精度很高。定时任务的延迟时间由定时任务的调度周期决定。如果把频率设置很小就会导致数据库的qps比较高容易造成数据库压力过大从而影响线上的正常业务。 所以一般需要抽离出超时中心和超时库来单独做订单的超时调度 如何让超时中心不同的节点协同工作拉取不同的数据:
通常的解决方案是借助任务调度系统开源任务调度系统大多支持分片模型比较适合做分库分表的轮询比如一个分片代表一张分表。但是如果分表特别多分片模型配置起来还是比较麻烦的。另外如果只有一张大表或者超时中心使用其他的存储这两个模型就不太适合。
阿里巴巴分布式任务调度系统SchedulerX:
通过实现map函数通过代码自行构造分片SchedulerX会将分片平均分给超时中心的不同节点分布式执行。2. 通过实现reduce函数可以做聚合可以判断这次跑批有哪些分片跑失败了从而通知下游处理。 使用SchedulerX定时跑批解决方案: 免运维、成本低不需要自建任务调度系统由云上托管。 可观测提供任务执行的历史记录、查看堆栈、日志服务、链路追踪等能力。 高可用支持同城双活容灾支持多种渠道的监控报警。 混部可以托管阿里云的机器也可以托管非阿里云的机器。
7.总结
如果对于超时精度比较高超时时间在24小时内且不会有峰值压力的场景推荐使用RocketMQ的定时消息解决方案。
在电商业务下许多订单超时场景都在24小时以上对于超时精度没有那么敏感并且有海量订单需要批处理推荐使用基于定时任务的跑批解决方案。
扫表轮训: 定时任务分布式批处理, 阿里使用SchedulerX懒删除: 通过设置一个数据库的状态, 用户查询订单的时候去判断状态看是否关闭订单消息队列实现: RabbitMQ的ttl和延迟队列, RocketMQ的定时消息Redis实现: 删除策略实现不乐观