从前端用户行为将抽奖流程拆分
抽奖前刷新页面就会触发查询奖品列表(用于展示九宫格中的奖品信息)、查询策略抽奖权重规则(用于展示抽奖阶梯,即总抽奖次数达到一定次数后必中某些奖品)、查询用户积分值和可用抽奖次数以及是否签到(用于展示会员卡信息),这些操作对于用户而言都是无感知的,刷新页面就会被动触发
之后就是点击策略装配按钮,来装配抽奖概率、权重规则、奖品库存等信息(目前是点击按钮触发,后续准备将其改为刷新页面自动装配)
点击积分兑换抽奖次数,后台进行校验,sku库存校验,发奖,库存扣减等一系列操作
点击抽奖,后台开始运作,根据概率范围选择抽奖算法是O(1)或O(log(n)),抽奖过程中,包含了黑名单规则、权重规则、默认规则的责任链,次数锁校验、库存处理、兜底奖励处理的规则决策树,大致流程是用户点击抽奖后,后台首先判断用户是否是黑名单用户,如果是则被拦截在黑名单规则给用户发库里配置的黑名单奖励,否则继续判断用户是否处于权重规则,即用户的总抽奖次数是否达到了抽奖阶梯的标准,若达到标准则被权重接管,给用户必中奖品,若没有权重接管,则继续往下走默认抽奖,默认抽奖就是普通抽奖,若触发了默认抽奖,则进行规则树的过滤,判断用户的日抽奖次数是否达到了某个值,若未达到则直接走兜底奖励(除了被锁住的奖品其他的奖品都配置成兜底奖品),若次数足够,则进行库存扣减,如果没库存了,那么还是走兜底奖励,如果有库存,才可以返回被锁住的奖品
注意被权重接管仅有一次,就是达到权重值的那一次,之后再进行抽奖就是走正常流程
抽奖环节的重点知识有:设计模式(责任链模式处理抽奖前、规则树模型处理抽奖中、模板模式串联责任链和规则树、整个框架中大都包含了抽象工厂模式)、抽奖后的库存处理(不超卖库存规则)、抽奖算法的选择(O(1)算法、O(log(n))算法中的for循环、二分查找、多线程等)
setNx 锁的目的是兜底,比如活动配置有 10 个库存,消耗开始 9、8、7、6 但因为一些问题,无论是redis还是其他系统导致的,运营需要重新调整恢复库存。但这个时候恢复错了为9个,但已经消耗到6个。那么 8、7、6 就会产生新的加锁key,这个加锁key会被redis已经加锁的key拦截,避免超卖。因为加锁不是竞争,不耗费性能但可以做兜底,是个不错的选择。【实际中系统运行最容易出问题的点,就是运营配置问题和调整活动】
从领域区分
活动领域
armory活动预热装配
partake创建活动抽奖参与订单
product商品sku服务,用于查询当前活动ID下,创建的sku商品(涉及积分兑换抽奖次数的部分)
quota抽奖活动账户额度服务,又分
policy策略分为返利,直接到账(无需支付)、积分兑换抽奖次数(需要支付)
rule活动规则
将活动配在数据库中,可控制活动的开启和关闭,以及配置活动类型,在创建活动抽奖参与订单时会先去数据库查询活动状态和活动日期,然后查询未被使用的活动订单记录,再就是对账户和订单的构建,然后将这两者聚合起来存入数据库
rule活动规则包括活动sku下单(sku库存)、活动信息、活动时间、活动状态这一系列校验和扣减,可以看出这是一个固定的流程,因此抽出一个责任链的结构来实现该流程
关于库存扣减,扣减的实际上是redis缓存里的库存,在扣减成功后,还需要写入延迟队列,延迟消费更新库存记录到数据库,这部分操作采用RBlockingQueue阻塞队列、RDelayedQueue延迟队列,这里是延迟的方法消息到Redis队列中,以此来减缓消费,同时是双重减缓,一个是延迟队列,一个是定时的任务调度,给延迟队列设置一个3秒后才会被队列处理的任务,保证能够减缓消费
很精妙的设计,使用redis的延迟队列+rabbitmq的监听队列
为什么不用mq来实现延迟队列功能?
因为mq在同一个队列中必须是按顺序进行消费的,而我们使用Redis来做延迟队列,在遇到redis缓存中库存消耗完的情况,可以直接触发任务来发送mq消息设置数据库库存为0,但mq就只能一个一个消费到最后一个库存为0,这样一对比谁更占优势就很明显了
policy交易策略中,不同类型的交易策略实现类,通过构造函数注入到 Map 中,以供后续流程中获取策略类型是需支付类或无需支付类
mq主要用于
监听活动sku库存消耗为0
监听积分账户调整成功消息,进行交易商品发货
监听用户行为返利消息
监听用户奖品发送消息,发奖
XXL-JOB主要用于
扫描数据库中的task表来发送任务信息给MQ
更新活动sku库存(从Redis队列里更新到数据库)
更新奖品消耗库存(从Redis队列里更新到数据库)
奖品领域
该领域涉及发奖部分,流程就是:首先会根据奖品ID去数据库中award表查询奖品的配置信息,根据配置的信息来生成随机积分,然后就是更新用户积分账户,若无用户积分账户则改为新增,下一步就是更新奖品记录,将user_award_record表中对应用户和订单的状态由create改为completed
另一部分则为保存用户中奖记录,流程就是向user_award_record表写入记录,task任务表写入任务记录并设置状态为create,然后将user_raffle_order表中对应的用户及订单状态由create修改为used,就是设定该订单已被使用,最后发送mq消息,若发送成功则直接将任务状态设置为completed反之fail
积分领域
涉及积分方面的问题就是积分调额,增减积分,创建账户积分额度订单
首先查询账户是否存在以及积分额度是否充足,然后就是保存账户积分、保存账户订单,写入任务(给任务状态设为create),发送mq消息,若发送成功则直接将任务状态设置为completed反之fail
返利领域
首先查询返利配置-查询 daily_behavior_rebate 表中配置的返利类型,有以下两种类型
然后创建任务并设置任务状态为create,向task表添加任务,user_behavior_rebate_order表插入数据,同步发送mq消息,若发送成功就将任务表状态修改为completed,发送失败则任务状态修改为fail
从上面可以得出一个结论,task这个表的作用就是为了设置mq消息发送的成功与否,任务领域则专门汇总任务相关的内容,如更新任务表任务状态,将任务类发送mq消息,查询未发送的任务列表(即任务状态为fail或状态为create且距今超过6小时的记录)
待新增:
ERP后台管理:添加黑名单功能,添加权重配置管理(管理日n次抽奖后解锁新奖品,管理总抽奖n次必中奖品权重),添加策略奖品管理(可配置奖品名称,奖品概率,奖品库存(关于增加库存这点,需要设置一下,增加库存之后刷新redis缓存里的库存))
库存的恢复,基于 incr 和 总量 + 恢复量对比,恢复量可以由失败恢复和任务校准当前产生单量和库存差异做对比。- 不过一般实际生产主要保证不超卖,货品是有很多的,今天没处理完,明天继续卖。
redis缓存装配策略时自动清空上一次装配的内容,可能需要添加库存刷新
前台:添加用户登录,添加中奖记录滚轮