微信抢红包过期失效实战案例
程序员文章站
2022-06-23 22:30:42
前言 微信红包业务,发红包之后如果24小时之内没有被领取完就自动过期失效。 架构设计 业务流程 老板发红包,此时缓存初始化红包个数,红包金额(单位分),并异步入库。 红包数据入延迟队列,唯一标识+失效时间 红包数据出延迟队列,根据唯一标识清空红包缓存数据、异步更新数据库、异步退回红包金额 代码案例 ......
前言
微信红包业务,发红包之后如果24小时之内没有被领取完就自动过期失效。
架构设计
业务流程
老板发红包,此时缓存初始化红包个数,红包金额(单位分),并异步入库。
红包数据入延迟队列,唯一标识+失效时间
红包数据出延迟队列,根据唯一标识清空红包缓存数据、异步更新数据库、异步退回红包金额
代码案例
这里我们使用java
内置的delayqueue
来实现,delayqueue
是一个*的blockingqueue
,用于放置实现了delayed
接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。
老板发了10个红包一共200人民币,假装只有9个人抢红包。
发红包,缓存数据进入延迟队列:
/** * 有人没抢 红包发多了 * 红包进入延迟队列 * 实现过期失效 * @param redpacketid * @return */ @apioperation(value="抢红包三",nickname="爪哇笔记") @postmapping("/startthree") public result startthree(long redpacketid){ int skillnum = 9; final countdownlatch latch = new countdownlatch(skillnum);//n个抢红包 /** * 初始化红包数据,抢红包拦截 */ redisutil.cachevalue(redpacketid+"-num",10); /** * 初始化红包金额,单位为分 */ redisutil.cachevalue(redpacketid+"-money",20000); /** * 加入延迟队列 24s秒过期 */ redpacketmessage message = new redpacketmessage(redpacketid,24); redpacketqueue.getqueue().produce(message); /** * 模拟 9个用户抢10个红包 */ for(int i=1;i<=skillnum;i++){ int userid = i; runnable task = () -> { /** * 抢红包 判断剩余金额 */ integer money = (integer) redisutil.getvalue(redpacketid+"-money"); if(money>0){ result result = redpacketservice.starttwoseckil(redpacketid,userid); if(result.get("code").tostring().equals("500")){ logger.info("用户{}手慢了,红包派完了",userid); }else{ double amount = doubleutil.divide(double.parsedouble(result.get("msg").tostring()), (double) 100); logger.info("用户{}抢红包成功,金额:{}", userid,amount); } } latch.countdown(); }; executor.execute(task); } try { latch.await(); integer restmoney = integer.parseint(redisutil.getvalue(redpacketid+"-money").tostring()); logger.info("剩余金额:{}",restmoney); } catch (interruptedexception e) { e.printstacktrace(); } return result.ok(); }
红包队列消息:
/** * 红包队列消息 */ public class redpacketmessage implements delayed { private static final datetimeformatter f = datetimeformatter.ofpattern("yyyy-mm-dd hh:mm:ss"); /** * 默认延迟3秒 */ private static final long delay_ms = 1000l * 3; /** * 红包 id */ private final long redpacketid; /** * 创建时间戳 */ private final long timestamp; /** * 过期时间 */ private final long expire; /** * 描述信息 */ private final string description; public redpacketmessage(long redpacketid, long expireseconds) { this.redpacketid = redpacketid; this.timestamp = system.currenttimemillis(); this.expire = this.timestamp + expireseconds * 1000l; this.description = string.format("红包[%s]-创建时间为:%s,超时时间为:%s", redpacketid, localdatetime.ofinstant(instant.ofepochmilli(timestamp), zoneid.systemdefault()).format(f), localdatetime.ofinstant(instant.ofepochmilli(expire), zoneid.systemdefault()).format(f)); } public redpacketmessage(long redpacketid) { this.redpacketid = redpacketid; this.timestamp = system.currenttimemillis(); this.expire = this.timestamp + delay_ms; this.description = string.format("红包[%s]-创建时间为:%s,超时时间为:%s", redpacketid, localdatetime.ofinstant(instant.ofepochmilli(timestamp), zoneid.systemdefault()).format(f), localdatetime.ofinstant(instant.ofepochmilli(expire), zoneid.systemdefault()).format(f)); } public long getredpacketid() { return redpacketid; } public long gettimestamp() { return timestamp; } public long getexpire() { return expire; } public string getdescription() { return description; } @override public long getdelay(timeunit unit) { return unit.convert(this.expire - system.currenttimemillis(), timeunit.milliseconds); } @override public int compareto(delayed o) { return (int) (this.getdelay(timeunit.milliseconds) - o.getdelay(timeunit.milliseconds)); } }
红包延迟队列:
/** * 红包延迟队列 */ public class redpacketqueue { /** 用于多线程间下单的队列 */ private static delayqueue<redpacketmessage> queue = new delayqueue<>(); /** * 私有的默认构造子,保证外界无法直接实例化 */ private redpacketqueue(){} /** * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 */ private static class singletonholder{ /** * 静态初始化器,由jvm来保证线程安全 */ private static redpacketqueue queue = new redpacketqueue(); } //单例队列 public static redpacketqueue getqueue(){ return singletonholder.queue; } /** * 生产入队 * 1、执行加锁操作 * 2、把元素添加到优先级队列中 * 3、查看元素是否为队首 * 4、如果是队首的话,设置leader为空,唤醒所有等待的队列 * 5、释放锁 */ public boolean produce(redpacketmessage message){ return queue.add(message); } /** * 消费出队 * 1、执行加锁操作 * 2、取出优先级队列元素q的队首 * 3、如果元素q的队首/队列为空,阻塞请求 * 4、如果元素q的队首(first)不为空,获得这个元素的delay时间值 * 5、如果first的延迟delay时间值为0的话,说明该元素已经到了可以使用的时间,调用poll方法弹出该元素,跳出方法 * 6、如果first的延迟delay时间值不为0的话,释放元素first的引用,避免内存泄露 * 7、判断leader元素是否为空,不为空的话阻塞当前线程 * 8、如果leader元素为空的话,把当前线程赋值给leader元素,然后阻塞delay的时间,即等待队首到达可以出队的时间,在finally块中释放leader元素的引用 * 9、循环执行从1~8的步骤 * 10、如果leader为空并且优先级队列不为空的情况下(判断还有没有其他后续节点),调用signal通知其他的线程 * 11、执行解锁操作 */ public redpacketmessage consume() throws interruptedexception { return queue.take(); } }
红包延迟队列过期消费,监听任务:
/** * 红包延迟队列过期消费 */ @component("redpacket") public class taskrunner implements applicationrunner { private final static logger logger = loggerfactory.getlogger(taskrunner.class); @autowired private redisutil redisutil; executorservice executorservice = executors.newsinglethreadexecutor(r -> { thread thread = new thread(r); thread.setname("redpacketdelayworker"); thread.setdaemon(true); return thread; }); @override public void run(applicationarguments var){ executorservice.execute(() -> { while (true) { try { redpacketmessage message = redpacketqueue.getqueue().consume(); if(message!=null){ long redpacketid = message.getredpacketid(); logger.info("红包{}过期了",redpacketid); /** * 获取剩余红包个数以及金额 */ int num = (int) redisutil.getvalue(redpacketid+"-num"); int restmoney = (int) redisutil.getvalue(redpacketid+"-money"); logger.info("剩余红包个数{},剩余红包金额{}",num,restmoney); /** * 清空红包数据 */ redisutil.removevalue(redpacketid+"-num"); redisutil.removevalue(redpacketid+"-money"); /** * 异步更新数据库、异步退回红包金额 */ } } catch (interruptedexception e) { e.printstacktrace(); } } }); } }
适用场景
淘宝订单到期,下单成功后60s之后给用户发送短信通知,限时支付、缓存系统等等。
演示
在application
中有接口演示说明,你可以在抢红包 red packet controller
接口中输入任何参数进行测试,也可以配合数据库稍加修改即可作为生产环境的抢红包功能模块。
源码
https://gitee.com/52itstyle/spring-boot-seckill
上一篇: 推荐一款高效的处理延迟任务神器