营销平台.


库表配置:首先配置strategy,这里可以配置黑名单和权重、白名单,如果配置了黑名单,相应的在strategy_rule中配置黑名单规则,如果配置了权重也得在strategy_rule中配置权重规则,表示积分达到什么要求在什么奖品范围内抽奖,对应的积分的这部分奖品概率初始化map会在strategy_award中按照strategy_rule中配置的奖品范围过滤掉一些奖品进行概率的初始化;相应的奖品配置在strategy_award配置,这里包含一个策略下的所有奖品,有的奖品是没有限制的,有的奖品需要解锁,这时候根据奖品的rule_model到strategy_rule表中查询配置的值,是2次?6次?再进过filter过滤。

库表改动:strategy_award中存储的规则是树的规则,根据奖品的rule值,是一次解锁?6次解锁?每个奖品对应一个决策树treeid,这课决策树下包含节点信息,连线信息。

strategy_award表:
在这里插入图片描述
rule_tree_node表:
在这里插入图片描述
rule_tree_node_line表
在这里插入图片描述

1. 需求文档

在 OpenAi 大模型中添加抽奖模块,增强用户活跃的使用粘性。

  • 用户可以在消费购买、模型对话中获得到一个对应的积分。
  • 之后用户可以使用这个积分参与抽奖,如果没有积分每天也都有一个免积分的抽奖次数,增强用户的使用。

需求流程:在这里插入图片描述

2. 策略权重概率装配

完成对抽奖策略的装配:代码重构,理解方法的重载
原先只是先查库,查到策略对应的奖品明细,对各个奖品进行概率装配,也就是概率初始化(放到数组随机打乱,存到redismap中),而现在,因为有积分的玩法,一个用户不只是在该策略对应的奖品下进行抽奖(初始化的奖品概率map),当用户积分满足4000时它可以免费抽一次,而这一次不是简单的进行一次抽奖,这次用户可以在库表中配置的奖品id列表中进行抽奖,也就是可以抽好一点的奖品,那这时候就需要对奖品概率初始化方法进行重构,对于满4000积分的抽奖,重新初始化概率map,并在这个map里面抽奖。代码的重构,很大意义上是对方法的重载,原先的方法功能上要扩充,在充分利用原来代码的基础上扩增方法的功能,就像上面例子,对概率的初始化,原先只有一种方式,重构后变成两种,在功能层面还是同一个方法但是功能扩充了。在抽奖的使用上,在普通抽奖上用的是一个map,在积分抽奖是另一个map,对外接口中提供两种方法,也是方法的重载,参数不一样。
本节还有拆分接口的操作,一个接口用来装配,一个接口用来调度,以满足接口单一职责功能
本节会在实体对象中添加属于实体本身的功能,这样会让代码更加干净,也会体现出充血的作用
在这里插入图片描述

3.抽奖前置规则过滤

抽奖前置规则:使用到工厂和策略,根据strategy表中配置的规则(黑名单、积分权重),用户首先进行抽奖前置规则过滤,先黑名单过滤:如果这个用户是黑名单直接返回一个配置好的奖品(在strategy_rule表中配置的,根据strategy_id和rule_model查询出rule_value也就是规则值,这个规则值就配置了黑名单的用户信息),进行了黑名单过滤再进行由积分值过滤抽奖的奖品id范围, 也就是解析到rule_value下配置的积分的抽奖范围(4000:102,103,104,105 5000:102,103,104,105,106,107),由用户此时的积分满足哪个范围进行过滤,之后在过滤后的这个范围内抽奖。

    1. 黑名单过滤:黑名单用户配置在库表中,使用redis维护
    1. 积分抽奖
      用户执行抽奖时会判断是否已经超过N积分,如果超过N积分则可以在限定范围内进行抽奖。同时如果用户是黑名单范围的羊毛党用户,则只返回固定的奖品ID

这一章节的实现中则会涉及到工厂和策略定义出来的规则模型,并满足后续的规则扩展。再通过模板模式定义出抽奖的基本过程,来使用抽奖规则。

黑名单:比对抽奖用户的 ID 是否在数据库表配置的黑名单范围100:user001,user002,user003 一般在大厂的开发中,这里会配置人群ID,使用 Redis 进行维护。人群是量化工程师通过数据标签计算出来的结果。

4. 抽奖中规则过滤

为了刺激用户多次的参与抽奖,增加了解锁奖品的玩法,抽奖前三次1-6奖品,抽奖到达三次后解锁1-7奖品,抽奖6次后解锁1-9奖品。
在设计抽奖系统时,松耦合很关键,就像spring源码中拆解一个Bean对象为不同的阶段,这里可以理解为吧抽奖拆解为不同时间段的过程,这样可以在各个节点添加所需的功能流程,这样的设计更加便于后续的功能的迭代。

思考:吧抽奖拆解为抽奖前、中、后三个阶段,抽奖前的规则过滤,使用一个前filter,它会过滤掉黑名单和权重(积分),业务上根据这个filter返回的结果来执行后续的操作,如果是黑名单,直接返回一个差的奖品;经过前置过滤后,用户正常抽奖,生成随机数,在redis中map中找奖品id,再拿到这个奖品后,会经过抽奖中filter,这个filter会进行奖品解锁的判断,看一下用户是不是达到这个奖品最低的解锁次数了,抽奖主业务上根据这个filter的结果,看一下是不是被filter拦截了,拦截了就不能返回这个奖品了,走抽奖后逻辑,返回兜底的奖品,没有拦截的话可以进行后续流程,进入到抽奖后filter,这个filter也是看一下这个奖品有没有库存,库存不够了进行拦截,主业务上根据后filter的结果来执行后续的操作,如果这个奖品被拦截了,走兜底奖品,没有拦截可以正常返回这个奖品。

这样设计的目的是每个阶段各司其职,耦合低,方便后续的扩展,比如说,我们新增了玩法,只需要配置相应的库表,业务上只需要在对应的filter上进行拦截判断,根据filter是否拦截可以进行后续多样化的玩法,维护成本低。

5. 使用责任链模式改造抽奖前规则:黑名单、权重、默认抽奖

抽奖的前置规则:可以抽象为一种策略行为:黑名单抽奖策略、权重抽奖策略、白名单抽奖策略。对于这种情况用责任链 + 工厂处理。
在这里插入图片描述

责任链模式:使用责任链模式其实也是为了将抽奖系统的业务进行解耦松耦合,比如说由黑名单的过滤到权重到默认,其实可以看为一条链表的操作,每个责任链节点它有权处理自己的逻辑,也有权交给下一个节点去处理,就类似于链表,首先定义一个接口,像logic自己的逻辑方法、next下一个节点、以及appendNext添加下一个节点到责任链中,其实这里我们配置的rule在库中,有多少个rule创建多少个责任链的节点,每个节点处理自己的rule,并且有权利交给下一个next节点处理后续的逻辑,抽奖的前置规则使用责任链 + 工厂配合,工厂动态的根据库表中配置的rule来完成责任链链条的链接,就是将每个节点的next链接到下一个节点,最后返回这个责任链,对于使用上来说,我们只需要在工厂中拿到这个链条,执行接口中的logic方法,对于内部细节,不需要过多了解,也就实现了抽奖系统的松耦合。
注意:责任链并发安全问题,在这里插入图片描述

6. 组合模式处理抽奖中、抽奖后流程

抽奖策略的前置规则:过滤是顺序一条链的,有一个成功就可以返回。适合责任链。

那么对于抽奖中到抽奖后的规则:设计到断开的链路关系,单独的责任链是不能满足的,这里采用组合模式的决策树模型设计,创建出树型结构的调用链路关系,当然使用for循环规律也可以,这样代码就臃肿。

组合模式:

  • 规则节点:各个rule对应的规则实现类:lock次数规则、库存规则、兜底
  • 执行引擎:处理这颗链路树
  • 决策树工厂:管理规则节点的注入决策树引擎的创建。在使用的时候,可以通过 openLogicTree 方法获取执行器。执行器提供了规则的执行操作。

在库存规则中,使用redission延迟队列 + spring定时任务的方式(也可以xxljob),缓慢消耗队列数据来更新库表数据变化(因为存放数据是延迟存放,消费也是定时任务,这样会对数据库的库表更新的压力会降低很多,不会产生大量的竞争)。
库存的数据和概率map初始化是在装配中实现的,刚开始就加载到redis中。防止超卖是用setNx配合库存滑块实现。

组合 模式 题外话:对于树的处理,对每个节点拿到对应的规则实现类,可以基于自己节点了业务逻辑,执行过滤,再根据处理的结果返回值在根据这个节点边的信息进行过滤,边的过滤信息记录与节点返回值对应;还可以根据这个节点的业务逻辑,根据这个节点的边了记录的过滤信息,再看满足哪条边的过滤进行选边,实现过滤。

流程:先在strategy_award中拿到奖品对应的数(比如所锁6次数),先经过次数锁节点,判断用户次数够不够,够:次数锁节点放行到库存节点,不够:次数锁节点放行到兜底节点;若到库存节点:库存节点判断库存够不够,够:库存节点接管,放回奖品id,不够:库存节点放行到兜底节点;若到兜底节点,会根据兜底配置的奖品返回,总的流程实现正常抽奖后对奖品ID的规则过滤。

在拿到奖品ID后如果这个奖品有lock,则走组合过滤,其中,lock节点判断这个用户是否够次数的逻辑:
拿到这个奖品ID,根据这个奖品的策略ID,在raffle_activity表中查询出该策略对应的活动(策略ID : 活动ID = 1 : 1),比如说是规定每天抽够次数才能解锁,那就会根据userID+activityID+Day到这个用户的活动日库存下查询出今天已经参与的次数,再判断这个奖品绑定的解锁次数够不够。
用户的活动总账户、日账户、月账户增减的逻辑:在上游Sku充值到对应的活动总账户上配置的次数,当用户参与抽奖时,先领取活动,对应的就是扣减总账户额度 + 更新月、日账户次数 + 创建活动抽奖单(这个抽奖单上有抽奖的状态,一个抽奖单对应一次抽奖,当抽奖结果落库后回更抽奖单中抽奖的状态。)

思考:什么时候用责任链什么时候用组合?
主要看我们库表中配置的过滤规则,是否是有差异性的,如果没有差异性,用责任链,如果有差异性,用组合。
比如:策略的黑名单、权重、正常抽奖,所有的策略都是走这样的流程,用责任链
但是奖品的rule,每个奖品它比如说有抽6次解锁、随机积分、兜底奖品。每个奖品走的rule不一样,如果是lock规则,看次数够不够进而走到库存或者兜底,那如果兜底那就没后续了,走到库存就要看有没有库存,有就没后续了没有就走兜底;当然这是奖品rule为lock形式,如果奖品没有lock限制,那就走库存过滤,那如果奖品rule是兜底,那直接没后续过滤了,这种奖品rule不同它的过滤链不固定,用组合模式。

注意点:单例的责任链和原型的责任链。
单例责任链:对于活动的配置,所有活动都走一个流程,不会因为活动差异化的配置不同而走不同的流程,用单例
原型责任链:根据库表的配置规则不一样可能走不同的流程,比如策略下的配置,可能在不同的策略下配置的规则不一样而走不同的链路,这时候就需要根据这个策略下配置的规则形成自己的责任链,保存到内存使用。

7. 活动库表设计

活动领域是一个含有活动部署、活动账户充值、用户参与活动的多个核心子领域组成
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

活动配置表:抽奖活动表、参与次数表
三个用户领取表:活动下单记录、活动次数账户、账户次数流水

  • 抽奖活动表:配置了用户参与一个活动的时候,需要进行的必要信息判断。时间、库存、状态等。
  • 参与次数表,单独分离出来。这样更方便后续基于不同的次数编号,做扩展。比如兑换一个新的抽奖次数。
  • 活动下单记录表,用户参与活动,则需要先创建一笔订单记录。如果用户抽奖中有失败流程,也可以基于订单的状态,用户重新发起抽奖,也不会额外占用库存记录。
  • 活动次数账户表,记录着一个用户在一个活动的可参与次数数据,也就是个人活动账户。
  • 账户次数流水表,每一笔对账户变动的记录,无论是任何的方式的变动,都要有一条流水。

解释:用户参与抽奖可以理解为买东西下单的操作(下单、账户、流水),下单后才会有抽奖的资格,也就是用户领取活动后才会有抽奖的资格,经过活动信息的校验以及个人活动账户的可用的活动次数校验后,生成活动抽奖单。

改动:解耦活动库存个人库存sku活动商品配置。以此方式支持后续用户发生的签到、打卡、支付等方式获得的抽奖次数。
在这里插入图片描述

在这里插入图片描述


思考:

  • 分布式事务:很多场景下不会使用分布式事务,因为性能低,而是使用MQ + worker来做最终一致性处理。
  • 多线程:其实在分布式环境下,单机多线程会压榨单机的性能,所以分布式下将其分散到不同的服务器上,也是实现多线程。单机下使用多线程场景:线程池(池化的技术)、接口的聚合、分页场景下数据DB到ES。

8. 分库分表组件

为什么使用分库分表:将用户的行为数据分库分表,分库分表是分布式架构中数据存储的常用的一种方案,本身就很成熟,如果早期设置为单表的后期扩展为分库分表会有非常大的迁移成本和工程的改造成本,所以不是等到数据量上来了再分库分表,而是开始就分库分表,第一分库分表技术成熟,第二早期可以采用虚拟机的方式,将物理机差分为虚拟机,各个应用作为主备,不会占用额外的物理资源,那后期数据量上来了,扩展的话只需要调整虚拟机的分配就可以了,也会很容易。

分库分表路由key怎么选择?
选取与该表有强的相关性绑定性的,因为我们查的时候要根据key路由到库表,所以这些路由key的选择要用那些经常根据什么什么字段来查的,这样的话就可以定位到具体的库表,以及像涉及到多张表的事务处理的话,选取的key要能路由到同一个库中,比如说:订单、流水、账户,根据suerId路由,可以保证它们路由到同一个库,这样一个数据库连接来实现事务。

个人活动账户表的一个活动库存对应一条流水和一条订单。
在这里插入图片描述

9.抽奖活动流水入库

在这里插入图片描述

流程分析:在【打卡、签到、分享、对话、积分兑换、支付】等行为动作下,创建出活动订单,给用户的活动账户【日、月】充值可用的抽奖次数。
比如说用户完成签到,这时候上游会传过来签到SKU、用户id、防重字段,由这三个字段创建出用户的订单记录和账户记录,订单记录包含上游传过来的防重字段,是唯一索引,保证幂等(防止创建两次);账户表中用userID和activityID创建唯一索引,如果账户不存在则创建新账户,如果账户存在则更新(更新就是增加用户的抽奖次数),用这个唯一索引来保证幂等。更新账户和新增订单在一个 事务中。

10 责任链处理活动,MQ处理SKU库存一致性

在这里插入图片描述
责任链涉及到两部分,一个节点做校验,另一个节点做库存扣减以及更新,这里会涉及一个缓存库存和数据库库存一致性问题。采用的延迟队列做趋势更新,另外RabbitMQ 在库存消耗空以后发送 MQ 消息,直接更新清空最终库存,保持缓存与数据库的一致性。
趋势更新需要一个一个更新,所以在库存扣减到0时直接发MQ消息更新DB库存为0并且移除延时队列未来得及更新的消息。

前面奖品的库存也是同理,策略里奖品的库存也做一个最终mq更新。

11 领取活动库表设计

在这里插入图片描述
流程分析:用户在参与抽奖前,需要领取活动,活动这部分,是用户做完任务【签到】会受到上游的sku,sku表中会有活动的消息【库存、该sku规则下对应活动的奖励次数】,相应的会创建活动订单表和创建或更新个人账户表(活动账户),活动订单表里面记录了奖励次数、个人账户表中也新增了次数。上面是上游sku发送过来对应的活动业务,之后进入抽奖环节,首先用户需要领取活动,在抽奖订单表中做记录,并且也需要在活动账户日库存中记录,这两部分需要事务,成功后,开始执行抽奖,拿到奖品ID后,要进行用户中奖流水的记录,并且同时发送MQ消息(发货),为了保证抽奖流水记录成功必须成功发送消息,在流水记录写入的同时将消息写入task任务表,两个表事务,MQ发送成功后更新task状态,发送失败后由worker补偿扫描重新发送MQ消息,之后MQ消息成功处理后更新用户中奖流水的发货状态。

注意小细节:在抽奖完后拿到中奖结果,但是可能中奖结果写入库失败,这时候需要在奖品库存扣减完后也写入task任务表,发送MQ消息(MQ消息是中奖结果),也是MQ发送成功更新task任务表,写入失败会由worker扫描task任务表进行补偿,确保奖品库存减少了必须将中奖结果保存到库(MQ实现),MQ消费者进行中奖结果的保存(中奖流水、task任务表、MQ)。

12 领取活动扣减账户额度

在这里插入图片描述
用户完成签到—>拿到签到sku,库存校验,成功后创建活动订单流水(sku的透传唯一id,保证幂等)、并且在个人活动账户表记录(首次插入、后面更新。创建订单流水和个人活动账户表记录两表要事务控制);之后进行抽奖,用户先领取活动,创建对应的抽奖订单( 更新总账户、月账户、日账户、写入抽奖订单四表保证事务)。若有未抽奖的抽奖单,则直接拿,不用新建抽奖单了。

13 写入中奖记录和任务补偿发送MQ

在这里插入图片描述
流程:拿到结果,写入中奖记录与task表并且更新活动抽奖单中抽奖的状态为used,表示这个抽奖单已经用过了,这三部在一个事务中,再发MQ,发送成功和失败在task表记录MQ状态,再由worker扫描补偿。

从用户中奖到发奖,发奖需要调用发奖接口,用MQ做异步解耦,MQ成功处理后更新中奖流水中的奖品发放的状态。这里在拿到中奖结果后,需要对中奖结果落库,并且中奖结果落库了必须得发一个MQ来驱动后续的发奖流程,这里中奖结果落库和发对应的MQ要做事务,这里采用的是task任务表和中奖记录在数据库层面做了事务,这两部分落库成功后紧接着发送MQ,发送成功和失败都要更新task中mq的状态,再由任务补偿:扫描任务表(分库的,指定路由key):dbrouter中threadlocal中获取分库数,for循环扫描所有库,查到需要重发的消息后开多线程发送消息(多线程四种拒绝策略:1. 丢弃任务抛异常。 2. 丢弃任务不抛异常 3. 将任务队列最早的任务删除自己加入 4. 主线程执行这个任务)。

小细节:写入奖品记录和写入task表事务保证,这里怎么来保证写入中奖记录幂等的?
每次抽奖都会有一个领取活动抽奖订单,里面有字段订单id,也就是每个订单ID只会参与一次抽奖,拿到抽奖结果进行落库,只会写入一条中奖记录,因为这条记录里面订单ID是唯一约束,数据库层面保证幂等。

定时任务扫描库表(task)和消息发送都使用异步线程的方式进行处理。
MQ 发送可能会存在更新数据库超时的情况后,多次发送MQ消息【实际中情况很低】,但虽然有MQ多发送,不过也没关系,因为所有的操作都是有唯一ID来保证幂等的。

问题:哪个唯一ID?保证什么幂等?
自己想法:消息唯一ID,保证发奖幂等,不能同一个消息发两个奖品。

解释:为什么不直接调发奖方法?
因为一些奖品的方法并不是都是在抽奖系统,而是各种 RPC/HTTP 接口来发放,用户只需要知道自己已中奖即可,不需要等待其他接口返回,因为耗时。用户可以点击详情,查看自己的中奖结果。

14 编写controller抽奖接口:抽奖活动流程串联

流程分析:

  1. 首先在用户完成签到、转发、等上游sku触发,增加个人活动账户表中活动参与次数
  2. 用户参与抽奖:首先领取活动(个人账户表次数减少),领取活动后创建活动抽奖订单(抽奖单包含抽奖状态,表示这个抽奖单 用没用过抽奖)
  3. 用户参与抽奖:其次进行抽奖(一个抽奖单对应一次抽奖),拿到这个活动关联的策略ID,进行抽奖(责任链过滤拿到奖品ID,组合模式根据这个奖品对应的规则配置,走决策树,来看能不能拿到这个奖品)。
  4. 用户参与抽奖:抽奖结果落库(写入中奖记录、task任务表、更新抽奖单中抽奖状态:因为一个抽奖单只能一次抽奖,奖品落库后更新抽奖状态为已使用),三表事务控制,之后发MQ消息,进行奖品发放。

15 用户行为返利入账

用户行为触达后,比如签到、支付,每种行为都在库表中有相关的返利配置,是N对N的,sign:配置 = N : N,查到对应的SKU配置后,组建聚合对象(订单流水、task任务表),订单流水有外部透传的唯一ID充当数据库层面的唯一索引,事务保证订单流水和Task任务表,之后补偿任务扫描Task任务表来确保消息能够发送。

问题:怎么保证消息不丢不重?
不丢:用Task任务表保证,如果消息没有发送成功,则补偿任务扫描Task任务表来确保发送成功,具体来说,将订单流水和Task任务表做事务,并且订单流水有外部透传唯一ID充当DB层的唯一索引,确保订单流水落库消息也落库(Task任务表记录)
不重:重复应该是要消费端保证,根据唯一消息ID来判断,第一次则消费,第二次则不消费,redis中判断。
在这里插入图片描述

日常的返利会根据用户所完成的行为动作来触达,这包括;打卡、签到、连签、支付、开户、交易、信贷、拉新等各类的动作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值