项目总结.

1.DDD结构基础

  1. 包含:
    接口层、应用层、领域层、基础层;通用包、接口层、接口定义。
    接口层:实现RPC接口定义,引入应用层服务,封装具体的接口。
    应用层:逻辑包装、编排、任务,领域事件的发布和订阅。
    领域层:封装具体的业务功能的实现,它们是聚合、充血的

    • model,用于提供vo、req、res 和 aggregates 聚合对象
    • repository,提供仓储服务,其实也就是对Mysql、Redis等数据的统一包装
    • service,是具体的业务逻辑实现,对外提供接口
      基础层:数据库、Redis、ES
      通用包:通用的对象、常量、枚举、异常
      接口定义:描述RPC接口文件,用于打包后外部引入pom配置
  2. 为什么要定义RPC接口层:
    因为使用 RPC 框架的时候,需要对外提供描述接口信息的Jar包让外部调用方引入,所以RPC 需要暴露出来,而 DDD 的系统结构又比较耦合,所以进行了模块化的分离。

  3. 知识总览
    在这里插入图片描述
    表信息包括:活动表、奖品表、策略表、规则表、用户参与表、中奖信息表等

在这里插入图片描述
在这里插入图片描述

  1. Dubbo怎么用:
    提供方这里打包成jar包,调用方这里引入pom,用注解@reference注入,就可以调用了。

2. 抽奖领域

口述:抽奖这个领域是可以独立配置和使用的领域模块,没有引入活动id,可以满足不同业务场景的灵活调用,使用了策略模式根据传入的策略选择不同的抽奖算法。比如单体概率、整体概率
划分了两张表,策略配置表和策略明细表,来满足不同业务场景的灵活的调用,比如说:有些业务场景直接进行抽奖反馈中奖信息发送给用户,但还有一些场景用户下单支付才满足抽奖条件,对应的奖品是需要延时到账的,避免用户在下单后又进行退单,这样造成了刷单的风险。

  1. 策略配置表和策略明细表,它们的关系是1v n。

  2. 两种抽奖策略算法
    单项概率抽奖和整体概率抽奖:为了配合不同的玩法,当1个奖品被抽空了以后,那么再抽奖时,是剩余的奖品总概率均匀分配,还是保持剩余奖品的中奖概率

  3. 抽奖算法实现:
    单体概率:采用的斐波那契散列算法,散列效果比hashmap更好,O(1)复
    杂度下完成
    总体概率:随机0-1值,看一下这个随机的概率在哪个奖品区间,表示中哪个奖
    在这里插入图片描述

  4. 模板模式处理抽奖流程
    使用模板的设计模式,规范抽奖的执行流程,包括:校验抽奖策略有没有初始化(单体概率的话需要初始化,使用斐波那契算法来初始化概率数组)、获取不在抽奖范围内的奖品(库存为空、风控、临时调整)、执行抽奖(策略模式:根据传入的策略选择不同的实现,执行抽奖拿到奖品id,扣减库存将strategy_detail下奖品的剩余库存 - 1{使用redis分布式锁,有更好的并发性})、包装中奖结果。

3. 发奖领域

有四种奖品发放的方式,1:文字描述、2:兑换码、3:优惠券、4:实物奖品,使用简单工厂,将全部发奖方式放入map中(@PostConstruct完成初始化),在发奖时根据查到的奖品的类型到工厂取对应的发奖方式。

4.活动领域

活动创建、活动状态处理、用户领取活动

  • 活动创建:创建活动涉及到多张表(活动信息、奖品信息、策略信息、策略明细),要在一个事务下进行落库。
  • 活动状态的处理:活动状态的处理用到了状态模式进行处理,活动状态包括(1编辑、2提审、3撤审、4通过、5运行(审核通过后worker扫描状态)、6拒绝、7关闭、8开启),在抽象类中定义了所有状态流转的抽象方法,子类也就是所有的状态类重写里面的方法,它们各自都有自己的状态的改变逻辑,比如说编辑状态可以变为提审状态、可以变为撤审、关闭状态,那只有这些可变的状态才会进入DB,进行活动状态的修改,不可转变的状态就不会进入,定义好这些状态类后,将它们放入map中,在对外提供的接口中,根据当前状态在map中拿到对应的状态类,就实现了状态的改变。
  • 活动状态的处理:xxl-job定时扫描活动的状态,活动审核通过后将活动状态变为运行中,活动过期的话将活动状态由运行中改为关闭。xxl(执行器和任务管理器,执行器扫描到任务就是用@xxljob直接标注的方法然后执行)
  • 用户领取活动:使用模板模式,抽象类定义模板,模板流程包括:1. 查看有没有未执行抽奖的活动,有的话说明用户已经领取了活动但是抽奖失败(user_take_activity 用户领取活动记录表state = 0,领取了但抽奖过程失败的,可以直接返回领取结果继续抽奖) 2. 活动的校验(日期、库存、状态)、3. 扣减活动库存、4. 添加用户领取信息(扣减个人已参与次数、添加用户的领取信息)、封装结果(返回策略id,用于后续抽奖)。扣减个人已参与次数(用户参与次数表)和添加用户的领取信息(用户参与记录表)是要保证事务性的,为了保证在多个DAO上使用路由并且保证事务不失效这里采用的是比较简单的方法:吧切换路由的操作放在事务开始前,事务也采用编程式事务。
    • 易错点:扣减库存与添加用户领取信息是在两个库中,按道理来说扣减活动库存失败了应该恢复掉用户的领取信息,这里的话采用redis扣减库存,不能保证完全恢复正确,其实类似于超卖,不能让用户多领取,可以接受一个活动没用户领取。
    • redis分布式锁扣减库存:当大量的用户参与活动,那么这个就属于秒杀场景。使用redis分布式锁来处理,没有直接锁活动,因为这样如果一个用户失败了那后面的用户就不停的等待。所以采用了活动ID+库存扣减后的值,在拿到锁后,执行第四步:添加用户的领取信息(用户参与次数表、用户参与记录表),最后删除掉Key(保证不超卖,但不能保证一定完全消耗库存),再发送MQ将缓存中的库存同步到DB中(MQ 的发送,只发生在用户首次领取活动时,如果是已经领取活动但因为抽奖等流程失败,二次进入此流程,则不会发送 MQ 消息,消费 MQ 就是到DB中将活动库存 - 1,实际场景业务体量较大,可能也会由于MQ消费引起并发,对数据库产生压力,所以如果并发量较大,可以把库存记录缓存中,并使用定时任务进行处理缓存和数据库库存同步,减少对数据库的操作次数原因:因为worker可以控制对DB的操作次数,减少对数据库操作)。
    • 在这里插入图片描述

5. 规则领域

组合模式:组合模式来搭建用户参与的规则,通过过滤性别、年龄、首单消费、消费金额、忠实用户等各类身份来量化出具体可参与的抽奖活动。组合模式就像一颗二叉树,DB中要存这颗树,包括节点信息、树根信息、边的信息;

口述组合模式来规则化用户可参与的活动::
组合模式针对处理的是一颗树形结构,我们用组合模式来规则化用户可参与的活动,其实就是筛选用户身份标签,找到符合参与的活动号。拿到活动号后,就可以参与到具体的抽奖活动中了。
活动号的信息存储在叶子节点中,非叶子节点是一系列的判断条件,比如年龄、性别、等等,由这些筛选条件可以组合成一棵树的结构,比如我们筛选条件是男性大于20岁,首先根据年龄这个节点的判断,我们走了左分支,左分支是男右分支是女,然后再从左分支中的年龄这个判断节点,再判断年龄,比如年龄大于20岁走的是右分支,这样逐层的进行条件的过滤,直到找到叶子节点,当前非叶子节点的条件的化还可以有其他的,最后经过层层筛选找到叶子节点,也就找到了活动的信息,表示用户能够参与的活动,实现了对用户的筛选,匹配出所对应的活动,然后参加活动执行抽奖。

其实这里的组合模式的化也就比较明了了,过滤器:定义一个接口声明节点的过滤方法,这个过滤方法就是按照当前节点的筛选条件,然后选出走左分支还是右分支,比如说年龄过滤性别过滤。
执行器:这部分的话其实就是对的处理了,获得到根节点,从根节点开始,每一个节点都调用logic 逻辑过滤器,决定当前节点应该怎么走,直到一步一步走到叶子节点,我们实现了对用户的一个层层筛选,最后得到用户满足什么什么活动,因为其实叶子节点中就存了活动的信息,之后用户拿到了活动信息后就能参与活动,执行抽奖逻辑了。

7. 支撑领域

包括:ID的生成、分库分表、vo2dto方法、Redis

  1. ID生成:使用策略模式将三种生成ID的算法统一包装,由调用方决定使用那种生成ID的策略,ID可以用在订单号、策略ID、活动id,包括雪花算法(hutool)、日期算法(仅支持很小的调用量,用于生成活动配置类编号,保证全局唯一)、随机算法
  2. 分库分表:使用EnvironmentAware 解析配置,在自动配置类中创建数据源,在aop切面的拦截中,拦截需要分库分表的方法,分库分表的逻辑是类似于hashMap,将路由字段进过扰动函数计算出索引,再折算到具体的库表,当然这部分东西也不一定放在切面中,可以做成一个bean,可以配合编程式事务,分库数、分表数存到ThreadLocal中,用于传递在方法调用过程中可以提取到索引信息。
    Mybatis 拦截器处理分表,通过拦截 SQL 语句动态修改添加分表信息,再设置回 Mybatis 执行 SQL 中,分表这部分主要就是在表后加入分表索引来确定是哪个表。

应用层编排

功能:调用领域服务功能,编排抽奖过程,包括:对于活动的领取、抽奖的操作、中奖结果的保存、MQ处理发奖流程。
在用户领取活动前会进行人群的过滤,量化决策,使用组合模式筛选出可参与的活动,对于活动的领取,首先查一下有没有领取了活动但是执行抽奖失败的活动单,有的话拿到这个活动对应的策略id,再由该策略经过不同的概率算法计算出获奖的信息,再保存中奖的结果:保存的时候要同时修改两张表:用户领取活动记录表(state改为1)和用户奖品表。使用编程式事务控制。

中奖结果保存成功后,MQ再处理发奖,发送中奖的信息,发送消息进行回调的处理,发送成功后更新DB中mq_state为1,发送消息失败后更新DB中mq_state为2,worker再进行补偿处理,worker补偿处理其实还会处理mq_state为0的状态,它是由于发送消息失败并且更新DB也失败的情况,这种的话几率就比较低,消费消息这边拿到消息后根据消息中的发奖类型找到对应的奖品发货工厂,处理奖品的发送并更新发货的状态,当消息消费失败或者更新库表失败会进行重试。

worker补偿处理:worker扫描mq_state(2库四表),因为要扫描不同的库、不同的表,在路由组件中添加手动设置路由到库和表的方法,然后开启扫描mq_state状态,状态为2和0要消息补偿,为2表示发送消息失败,为0表示MQ没发送,超过30分钟也要对其补偿。

在这里插入图片描述

MQ发奖:
在这里插入图片描述

接口层:门面模式

以 DDD 设计的结构框架,门面模式包装RPC接口层抽奖的接口(就是rpc接口层定义,接口层实现),在接口层和应用层需要做防污处理,防止对象直接暴露外界,使用MapStruct 对象转换

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值