面试官问生成订单30分钟未支付则自动取消该如何实现
发布于 2023-09-11 17:26:27 阅读()作者:147小编
已有9352成功领取POS机
面试官问生成订单30分钟未支付则自动取消该如何实现
引言
在开发中,往往会遇到一些关于延时任务的需求。例如
生成订单30分钟未支付,则自动取消
生成订单60秒后,给用户发短信
对上述的任务,我们给一个专业的名字来形容,那就是延时任务。那么这里就会产生一个问题,这个延时任务和定时任务的区别究竟在哪里呢?一共有如下几点区别
定时任务有明确的触发时间,延时任务没有
定时任务有执行周期,而延时任务在某**触发后一段时间内执行,没有执行周期
定时任务一般执行的是批处理操作是多个任务,而延时任务一般是单个任务
下面,我们以判断订单是否超时为例,进行方案分析
方案分析1.数据库轮询思路
该方案通常是在小型项目中使用,即通过一个线程定时的去扫描数据库,通过订单时间来判断是否有超时的订单,然后进行update或delete等操作
实现
博主当年早期是用quartz来实现的-实习那会的事-,简单介绍一下
m*en项目引入一个依赖如下所示
org.quartz-schedulergroupId>quartzartifactId>2.2.2version>dependency>
调用Demo类MyJob如下所示
packagecom.rjzheng.delay1;importorg.quartz.JobBuilder;importorg.quartz.JobDetail;importorg.quartz.Scheduler;importorg.quartz.SchedulerException;importorg.quartz.SchedulerFactory;importorg.quartz.SimpleScheduleBuilder;importorg.quartz.Trigger;importorg.quartz.TriggerBuilder;importorg.quartz.impl.StdSchedulerFactory;importorg.quartz.Job;importorg.quartz.JobExecutionContext;importorg.quartz.JobExecutionException;
publicclassMyJobimplementsJob{publicvoidexecute-JobExecutionContextcontext-throwsJobExecutionException{System.out.println-"要去数据库扫描啦。。。"-;}
publicstaticvoidmain-String[]args-throw***ception{//创建任务JobDetailjobDetail=JobBuilder.newJob-MyJob.class-.withIdentity-"job1","group1"-.build--;//创建触发器每3秒钟执行一次Triggertrigger=TriggerBuilder.newTrigger--.withIdentity-"trigger1","group3"-.withSchedule-SimpleScheduleBuilder.simpleSchedule--.withIntervalInSeconds-3-.repeatForever---.build--;Schedulerscheduler=newStdSchedulerFactory--.getScheduler--;//将任务及其触发器放入调度器scheduler.scheduleJob-jobDetail,trigger-;//调度器开始调度任务scheduler.start--;}}
运行代码,可发现每隔3秒,输出如下
要去数据库扫描啦。。。优缺点
优点:简单易行,支持集群操作
缺点:
-1-对服务器内存消耗大
-2-存在延迟,比如你每隔3分钟扫描一次,那最坏的延迟时间就是3分钟
-3-***设你的订单有几千万条,每隔几分钟这样扫描一次,数据库损耗极大
2.JDK的延迟队列思路
该方案是利用JDK自带的DelayQueue来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入DelayQueue中的对象,是必须实现Delayed接口的。
DelayedQueue实现工作流程如下图所示
其中Poll--:获取并移除队列的超时元素,没有则返回空
take--:获取并移除队列的超时元素,如果没有则wait当前线程,直到有元素满足超时条件,返回结果。
实现
定义一个类OrderDelay实现Delayed,代码如下
packagecom.rjzheng.delay2;
importj*a.util.concurrent.Delayed;importj*a.util.concurrent.TimeUnit;
publicclassOrderDelayimplementsDelayed{
privateStringorderId;privatelongtimeout;
OrderDelay-StringorderId,longtimeout-{this.orderId=orderId;this.timeout=timeoutSystem.nanoTime--;}
publicintcompareTo-Delayedother-{if-other==this-return0;OrderDelayt=-OrderDelay-other;longd=-getDelay-TimeUnit.NANOSECONDS--t.getDelay-TimeUnit.NANOSECONDS--;return-d==0-?0:--d
//返回距离你自定义的超时时间还有多少publiclonggetDelay-TimeUnitunit-{returnunit.convert-timeout-System.nanoTime--,TimeUnit.NANOSECONDS-;}voidprint--{System.out.println-orderId"编号的订单要删除啦。。。。"-;}}
运行的测试Demo为,我们设定延迟时间为3秒
packagecom.rjzheng.delay2;
importj*a.util.ArrayList;importj*a.util.List;importj*a.util.concurrent.DelayQueue;importj*a.util.concurrent.TimeUnit;
publicclassDelayQueueDemo{publicstaticvoidmain-String[]args-{//TODOAuto-generatedmethodstubListlist=newArrayList--;list.add-"00000001"-;list.add-"00000002"-;list.add-"00000003"-;list.add-"00000004"-;list.add-"00000005"-;DelayQueuequeue=newDelayQueue--;longstart=System.currentTimeMillis--;for-inti=0;i
输出如下
00000001编号的订单要删除啦。。。。After3003MilliSeconds00000002编号的订单要删除啦。。。。After6006MilliSeconds00000003编号的订单要删除啦。。。。After9006MilliSeconds00000004编号的订单要删除啦。。。。After12008MilliSeconds00000005编号的订单要删除啦。。。。After15009MilliSeconds
可以看到都是延迟3秒,订单被删除。此处推荐一下微信小程序J*a精选面试题,考虑找工作的朋友可以看看,内涵大量面试题,支持随时随地刷题。
优缺点
优点:效率高,任务触发时间延迟低。关于j*a进阶技术路线:https://www.yoodb.com/
缺点:
-1-服务器重启后,数据全部消失,怕宕机
-2-集群扩展相当麻烦
-3-因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常
-4-代码复杂度较高
3.时间轮算法思路
先上一张时间轮的图-这图到处都是啦-
时间轮算法可以类比于时钟,如上图箭头(指针)按某一个方向按固定频率轮动,每一次跳动称为一个tick。这样可以看出定时轮由个3个重要的属性参数,ticksPerWheel(一轮的tick数),tickDuration(一个tick的持续时间)以及timeUnit(时间单位),例如当ticksPerWheel=60,tickDuration=1,timeUnit=秒,这就和现实中的始终的秒针走动完全类似了。
如果当前指针指在1上面,我有一个任务需要4秒以后执行,那么这个执行的线程回调或者消息将会被放在5上。那如果需要在20秒之后执行怎么办,由于这个环形结构槽数只到8,如果要20秒,指针需要多转2圈。位置是在2圈之后的5上面(20%81)
实现
我们用Netty的HashedWheelTimer来实现
给Pom加上下面的依赖
io.nettygroupId>netty-allartifactId>4.1.24.Finalversion>dependency>
测试代码HashedWheelTimerTest如下所示
packagecom.rjzheng.delay3;
importio.netty.util.HashedWheelTimer;importio.netty.util.Timeout;importio.netty.util.Timer;importio.netty.util.TimerTask;
importj*a.util.concurrent.TimeUnit;
publicclassHashedWheelTimerTest{staticclassMyTimerTaskimplementsTimerTask{booleanflag;publicMyTimerTask-booleanflag-{this.flag=flag;}publicvoidrun-Timeouttimeout-throw***ception{//TODOAuto-generatedmethodstubSystem.out.println-"要去数据库删除订单了。。。。"-;this.flag=false;}}publicstaticvoidmain-String[]argv-{MyTimerTasktimerTask=newMyTimerTask-true-;Timertimer=newHashedWheelTimer--;timer.newTimeout-timerTask,5,TimeUnit.SECONDS-;inti=1;while-timerTask.flag-{try{Thread.sleep-1000-;}catch-InterruptedExceptione-{//TODOAuto-generatedcatchblocke.printStackTrace--;}System.out.println-i"秒过去了"-;i;}}}
输出如下
1秒过去了2秒过去了3秒过去了4秒过去了5秒过去了要去数据库删除订单了。。。。6秒过去了优缺点
优点:效率高,任务触发时间延迟时间比delayQueue低,代码复杂度比delayQueue低。
缺点:
1-服务器重启后,数据全部消失,怕宕机
2-集群扩展相当麻烦
3-因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常
更多关于redis相关面试题,公众号J*a精选,回复J*a面试,获取redis全套相关面试题资料。
4.redis缓存思路一
利用redis的zset,zset是一个有序集合,每一个元素-member-都关联了一个score,通过score排序来取集合中的值。
zset常用命令
添加元素:ZADDkeyscoremember[[scoremember][scoremember]…]
按顺序查询元素:ZRANGEkeystartstop[WITHSCORES]
查询元素score:ZSCOREkeymember
移除元素:ZREMkeymember[member…]
测试如下
>推荐下自己做的Springboot的用户权限管理项目:
#添加单个元素
redis>ZADDpage_rank10google.com-integer-1
#添加多个元素
redis>ZADDpage_rank9baidu.com8bing.com-integer-2
redis>ZRANGEpage_rank0-1WITHSCORES1-"bing.com"2-"8"3-"baidu.com"4-"9"5-"google.com"6-"10"
#查询元素的score值redis>ZSCOREpage_rankbing.com"8"
#移除单个元素
redis>ZREMpage_rankgoogle.com-integer-1
redis>ZRANGEpage_rank0-1WITHSCORES1-"bing.com"2-"8"3-"baidu.com"4-"9"
那么如何实现呢?我们将订单超时时间戳与订单号分别设置为score和member,系统扫描第一个元素判断是否超时,具体如下图所示
实现一packagecom.rjzheng.delay4;
importj*a.util.Calendar;importj*a.util.Set;importredis.clients.jedis.Jedis;importredis.clients.jedis.JedisPool;importredis.clients.jedis.Tuple;
publicclassAppTest{privatestaticfinalStringADDR="127.0.0.1";privatestaticfinalintPORT=6379;privatestaticJedisPooljedisPool=newJedisPool-ADDR,PORT-;
publicstaticJedisgetJedis--{returnjedisPool.getResource--;}
//生产者,生成5个订单放进去publicvoidproductionDelayMessage--{for-inti=0;i
//消费者,取订单publicvoidconsumerDelayMessage--{Jedisjedis=AppTest.getJedis--;while-true-{Setitems=jedis.zrangeWithScores-"OrderId",0,1-;if-items==null||items.isEmpty---{System.out.println-"当前没有等待的任务"-;try{Thread.sleep-500-;}catch-InterruptedExceptione-{//TODOAuto-generatedcatchblocke.printStackTrace--;}continue;}intscore=-int---Tuple-items.toArray--[0]-.getScore--;Calendarcal=Calendar.getInstance--;intnowSecond=-int--cal.getTimeInMillis--/1000-;if-nowSecond>=score-{StringorderId=--Tuple-items.toArray--[0]-.getElement--;jedis.zrem-"OrderId",orderId-;System.out.println-System.currentTimeMillis--"ms:redis消费了一个任务:消费的订单OrderId为"orderId-;}}}
publicstaticvoidmain-String[]args-{AppTestappTest=newAppTest--;appTest.productionDelayMessage--;appTest.consumerDelayMessage--;}}
此时对应输出如下
RabbitMQ和Kafka到底如何择选
可以看到,几乎都是3秒之后,消费订单。
然而,这一版存在一个致命的硬伤,在高并发条件下,多消费者会取到同一个订单号,我们上测试代码ThreadTest
packagecom.rjzheng.delay4;
importj*a.util.concurrent.CountDownLatch;
publicclassThreadTest{privatestaticfinalintthreadNum=10;privatestaticCountDownLatchcdl=newCountDownLatch-threadNum-;staticclassDelayMessageimplementsRunnable{publicvoidrun--{try{cdl.await--;}catch-InterruptedExceptione-{//TODOAuto-generatedcatchblocke.printStackTrace--;}AppTestappTest=newAppTest--;appTest.consumerDelayMessage--;}}publicstaticvoidmain-String[]args-{AppTestappTest=newAppTest--;appTest.productionDelayMessage--;for-inti=0;inewThread-newDelayMessage---.start--;cdl.countDown--;}}}
输出如下所示
if规则执行器
显然,出现了多个线程消费同一个**的情况。
解决方案
-1-用分布式锁,但是用分布式锁,性能下降了,该方案不细说。
-2-对ZREM的返回值进行判断,只有大于0的时候,才消费数据,于是将consumerDelayMessage--方法里的
if-nowSecond>=score-{StringorderId=--Tuple-items.toArray--[0]-.getElement--;jedis.zrem-"OrderId",orderId-;System.out.println-System.currentTimeMillis--"ms:redis消费了一个任务:消费的订单OrderId为"orderId-;
修改为
if-nowSecond>=score-{StringorderId=--Tuple-items.toArray--[0]-.getElement--;Longnum=jedis.zrem-"OrderId",orderId-;if-num!=null&&num>0-{System.out.println-System.currentTimeMillis--"ms:redis消费了一个任务:消费的订单OrderId为"orderId-;
在这种修改后,重新运行ThreadTest类,发现输出正常了
思路二
该方案使用redis的KeyspaceNotifications,中文翻译就是键空间机制,就是利用该机制可以在key失效之后,提供一个回调,实际上是redis会给客户端发送一个消息。是需要redis版本2.8以上。
实现二
在redis.conf中,加入一条配置
notify-keyspace-event***
运行代码如下
packagecom.rjzheng.delay5;
importredis.clients.jedis.Jedis;importredis.clients.jedis.JedisPool;importredis.clients.jedis.JedisPubSub;
publicclassRedisTest{privatestaticfinalStringADDR="127.0.0.1";privatestaticfinalintPORT=6379;privatestaticJedisPooljedis=newJedisPool-ADDR,PORT-;privatestaticRedisSubsub=newRedisSub--;
publicstaticvoidinit--{newThread-newRunnable--{publicvoidrun--{jedis.getResource--.subscribe-sub,"__keyevent@0__:expired"-;}}-.start--;}
publicstaticvoidmain-String[]args-throwsInterruptedException{init--;for-inti=0;i
staticclassRedisSubextendsJedisPubSub{'http://www.jobbole.com/members/wx610506454'>@OverridepublicvoidonMessage-Stringchannel,Stringmessage-{System.out.println-System.currentTimeMillis--"ms:"message"订单取消"-;}}}
输出如下
可以明显看到3秒过后,订单取消了
ps:redis的pub/sub机制存在一个硬伤,**内容如下
原:BecauseRedisPub/Subi*ireandforgetcurrentlythereisnowaytousethi*eatureifyourapplicationdemandsreliablenotificationofevents,thatis,ifyourPub/Subclientdisconnects,andreconnectslater,alltheeventsdeliveredduringthetimetheclientwasdisconnectedarelost.翻:Redis的发布/订阅目前是即发即弃-fireandforget-模式的,因此无法实现**的可靠通知。也就是说,如果发布/订阅的客户端断链之后又重连,则在客户端断链期间的所有**都丢失了。因此,方案二不是太推荐。当然,如果你对可靠性要求不高,可以使用。优缺点
优点:
-1-由于使用Redis作为消息通道,消息都存储在Redis中。如果发送程序或者任务处理程序挂了,重启之后,还有重新处理数据的可能性。
-2-做集群扩展相当方便
-3-时间准确度高
缺点:
-1-需要额外进行redis维护
5.使用消息队列
我们可以***用rabbitMQ的延时队列。RabbitMQ具有以下两个特性,可以实现延迟队列
RabbitMQ可以针对Queue和Message设置x-message-tt,来控制消息的生存时间,如果超时,则消息变为deadletter
lRabbitMQ的Queue可以配置x-dead-letter-exchange和x-dead-letter-routing-key(可选)两个参数,用来控制队列内出现了deadletter,则按照这两个参数重新路由。结合以上两个特性,就可以模拟出延迟消息的功能,具体的,我改天再写一篇文章,这里再讲下去,篇幅太长。
优缺点
优点:高效,可以利用rabbitmq的分布式特性轻易的进行横向扩展,消息支持持久化增加了可靠性。
缺点:本身的易用度要依赖于rabbitMq的运维.因为要引用rabbitMq,所以复杂度和成本变高
作者:hjm4702192blog.csdn.net/hjm4702192/article/details/80519010
公众号“J*a精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!
最近有很多人问,有没有读者交流群!加入方式很简单,公众号J*a精选,回复“加群”,即可入群!
(微信小程序):3000道面试题,包含J*a基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!
特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注。
文章有帮助的话,点在看,转发吧!
光遇未支付订单多久自动取消
当下了订单后10分钟或30分钟未支付,订单会自动取消,具体是如何实现的呢?本文使用最常用的几种方式,只说明关键的部分,已30分钟为例。回到顶部下单时,订单状态是侍支付。将订单编号作为Key,下单的时间戳作为Va|ue,设置过期时间是30分钟。
如何使用php、html及消息队列实现订单超时自动关闭订单
从php脚本方面解决的话,那就是每分钟用ajax请求一次php脚本,检查订单状态和当前时间,30分钟后检查到订单无异,测php更新订单到关闭状态,这个地方需要用到setTimeout了,循环执行请求,但是缺点是页面必能关闭,关闭了js就不执行,所以这个方法不太可取,30分钟时间太长,不可能不进行其他页面行为。
使用Mysql 定时**任务(推荐你使用),语法体
create?event?myevent
on?schedule?at?current_timestamp?+?interval?1?hour?(周期或者时间点)
do
update?myschema.mytable?set?mycol?=?mycol?+?1;?????(执行的sql 详细使用可以参考:https://segmentfault.com/a/1190000005142597,很清楚淘宝订单未付款多久关闭怎么设置
天猫未付款订单取消时间:淘宝订单买家自拍下商品之时起二十四小时内未付款的,交易自动关闭。
其他特殊交易类型的订单:
1、聚划算30分钟未付款的,交易自动关闭。
2、淘抢购15分钟未付款的,交易自动关闭。
3、天天特价活动30分钟未付款的,交易自动关闭。
淘宝订单除了等待自动关闭,还可以手动关闭,下面介绍手动关闭淘宝未付款订单的方法:
1、首先用电脑打开淘宝首页,然后点击登录。
2、登录完成后,把鼠标移到我的淘宝,在弹出来的功能表中选择已买到的宝贝。
3、打开页面后,找到未付款的订单,然后点击取消订单。
4、在弹出来的页面中选择关闭理由,然后点击确定。
5、完成支付关闭。
TP6队列think-queue(延时队列/自动取消订单/php自动操作)
什么是延时队列?顾名思义:首先它要具有队列的特性,再给它附加一个延迟消费队列消息的功能,也就是说可以指定队列中的消息在哪个时间点被消费。
延时队列在项目中的应用还是比较多的,尤其像电商类平台订单成功后,在30分钟内没有支付,自动取消订单
外卖平台发送订餐通知,下单成功后60s给用户推送短信。
如果订单一直处于某一个未完结状态时,及时处理关单,并退还库存
淘宝新建商户一个月内还没上传商品信息,将冻结商铺等
……
上边的这些场景都可以应用延时队列解决。
1.安装think-queue
2.安装redis
3.配置,项目\config\queue.php
4.创建队列和推送
新增 \app\job\test.php 控制器,在该控制器中添加 fire 方法
fire方法是消息队列默认调用的方法
5.在项目中进行调用延时队列方法
6.添加监听 - 重要必要有这个监听,Queue::later才有效(三)延迟队列DelayQueue实现订单自动取消
DelayQueue :,1)j*a自带延时获取元素, 无界 阻塞队列,2)队列 内部用PriorityQueue实现 。? ? ?创建元素时可 指定多久 才能从队列中获取当前元素。期满才从队列中 提取 ,没到延时时间, 阻塞 当前线程。
泛型队列,继承Delayed,需重写getDelay和compareTo方法。
1.public class DelayQueue
2.public int compareTo (T o); 往DelayQueue 加入数据 执行,根据返回值判断位置。排得越 前,越先被消费
3. long getDelay (TimeUnit unit);判断消息是否到期。负数,已到期,可读。
优点: j*a自带,轻量级,使用简单
缺点: 存储 内存中 ,服务器 重启 会造成数据 丢失 ,配合redis使用。数量大用mq
订单类,实现Delayed接口
unit.convert(this.createdTime.toInstant(ZoneOffset.of("+8")).toEpochMilli()+expireTime-System.currentTimeMillis(),TimeUnit.MILLISECONDS);
DelayQueue 分布式 环境中就会 重复执行;所以加redis:
每次生成订单时, 同时向 redis setnx 设定该未支付订单,
每次查询待支付订单时须从 redis 中也查一遍,
redis 不存在该订单,改为已取消。
AB 两个队列,A 队列设置 消息过期时间 , 没有消费者 ,A 过期自动转发到 B , B 队列消费者 取消 。
https://www.jianshu.com/p/2ef8646fe8f7
https://www.v2ex.com/amp/t/407801相关文章推荐
-
银盛通pos机官方热线() 09-03
-
银盛通pos机办理流程() 09-23
-
银盛通pos机无法刷卡(银盛通突然刷不了卡) 09-21
-
银盛通pos机申请平台() 08-31
-
银盛通pos机如何操作() 09-07
-
银盛通pos机点了消费() 09-03
-
银联盛付通pos机() 09-05
-
银盛通pos机不签名() 09-01
-
银盛通pos机怎么查询() 09-11
-
国产大灰机C919商业首飞领导先坐 08-28
-
周二最新版早上好祝福语 08-28
-
法版优博与伊利金领冠哪个好重点为大家介绍这款奶粉 09-06
-
银盛通pos机刷卡激活(银盛pos机怎么激活) 09-05
-
1l0erz4d 09-13
-
超级盛pos怎么连接无线网,超级盛pos安装流程 03-27
-
家用吸氢机在哪里买如何选择正规渠道购买家用吸氢机 08-31
-
暖气片的连接方式有哪些应该如何连接 09-02