抽奖项目技术亮点

news2024/9/20 22:43:14

活动是通过秒杀领取的。(即:活动对应着某一商品)
这里超卖指:对于一个活动它的参与量有数量限制,就是活动的库存,当活动的领取数大于活动库存总量,就是超卖
用户秒杀参与活动的资格(领取活动)

表设计

针对这个抽奖系统,会有不同的抽奖活动那就是【活动表】,不同的抽奖规则【抽奖策略表】,对人群的过滤【准入规则表】
然后记录用户信息的【用户表】,一个用户可以参加不同的活动【用户领取活动表】,抽奖完成后会生成该用户的【抽奖单】。

怎样保证幂等性

(1)在用户领取活动表中添加state状态用于记录当前领取的活动有没有执行抽奖。目的是当抽奖过程中发生失败(系统,网络等原因),还没生成抽奖单到数据库中,这时用于保留未使用抽奖的状态,避免又去重复领取一遍活动。
也就是说:用户领取活动后,当前活动抽奖为未执行状态,
抽奖活动开始执行时,先判断活动state,未执行才抽奖,否则不抽。以此避免在抽奖过程中发生失败,该次领取活动失效,又要重新领取活动导致该用户的活动总次数减少。

先说下面这俩(上面这个感觉表达不清楚):
(1)用户参与一次抽奖对应一个抽奖单:这是通过【用户领取活动表】中的领取ID(雪花算法),对应生成抽奖单的UUID,UUID设置了唯一约束,用来保持幂等性

(2)怎么保证kafka重复消费的幂等性?【用到MQ场景:Redis扣减数据写回DB;发奖】
生产者发送每条数据的时候,里面加一个全局唯一的业务id,消费者拿到后,先根据这个id去Redis里查一下之前消费过吗。如果没有消费过,就处理然后将id写入redis。如果消费过了,就不处理,以此保证不重复处理相同的消息。

抽奖单中添加mq_state标识MQ消息发送是否成功,如果发送失败就通过定时任务补偿MQ消息;发送成功就更改mq_state状态。

mq为什么出现非幂等性情况

1、生产者已把消息发送到mq,在mq给生产者返回ack的时候网络中断,故生产者未收到确定信息,认为消息未发送成功网络重连后生产者重发消息,但实际情况是mq已成功接收到了消息,造成mq接收了重复的消息
2、消费者在消费mq中的消息时,mq已把消息发送给消费者,消费者在给mq返回ack时网络中断,故mq未收到确认信息在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者消费了重复的消息。

解决办法
1、mq接收生产者传来的消息:
mq内部会为每条消息生成一个全局唯一、与业务无关的消息id,当mq接收到消息时,会先根据该id判断消息是否重复发送,mq再决定是否接收该消息。

2、消费者消费mq中的消息:
也可利用mq的该id来判断,或者可按自己的规则生成一个全局唯一id,每次消费消息时用该id先判断该消息是否已消费过

项目中哪里使用事务

使用自研路由组件分布式事务如何解决

1.在活动领取流程涉及路由切换的分布式事务,面对这个问题,为了避免同一个事务下,连续操作DAO而多次调用自定义注解的路由切换,导致声明式事务失效。
所以,将数据源的切换放在事务处理前,事务操作通过编程式参与次数表的活动次数扣减写入用户领取活动表连在一起合并为一个事务,保证两次操作的原子性,进行处理。
【这俩表是同一个数据源,只是DAO操作上就添加着路由切换,就会执行,从而导致事务失效】
2.鉴于抽奖系统的实时性要求,希望用户流程体验更加流畅,支撑更大的并发量,没有对整个流程添加过多的或者大块的事务,降低性能,而是采用最终一致性的方式进行处理
3.由在面对秒杀场景时,在分库分表后可以支撑更大的秒杀体量。同时对于单key的秒杀,还采用了滑块分段锁的方式使用redis和MQ进行处理,来提高吞吐量和减少数据库压力。

注:为什么切换路由会使声明式事务失效?
因为虽然路由组件通过AOP计算出了路由,但没有取到,而是复用了Spring事务给我们保存的connection,所以引起了路由失效。
(具体来说有些复杂见 Spring声明式事务引起的路由失效分析 )

解决方式:
解决方法正是在Spring事务开启之前,就手动地计算路由保存到RouterHolder之中,再手动开启Spring事务,这样就能取到正确的路由。

分布式事务是怎么实现的?

seata 两段式提交,但基本大家用的不多。因为要尽可能降低对数据库连接的长时间占用,要做到快速释放连接。
所以基本都是 MQ 和任务补偿做最终一致。
【保证一致性就是保证并发安全】
疑问:
(1)‘MQ 和任务补偿做最终一致?’ MQ怎么解决的分布式事务????
(2)MQ有没有做持久化:. MQ 消息是基于库表记录的任务扫描发送消息的,所以是有对应的持久化处理的。另外也可以创建一个单独的 Task 表,表中专门写 MQ 消息记录,用于发送失败重试等,这样可以统一管理。

怎么解决你和其他服务之间的分布式事务的?

我编的:

(1)秒杀场景下:使用redis decr奖品库存扣减并Setnx设置库存锁兜底保证不超卖,库存扣减写入异步延时队列,并定时任务扫
描扣减库存,缓解数据库压力。
(2)将发奖流程使用MQ异步处理。
(3)同一个库里的不同表间的增添和修改,使用spring的注解声明式事务处理
(4)分库分表后的用户抽奖单,为了保证分布式事务处理使用编程式事务

1.项目设计了那些表

  1. 先介绍业务:抽奖系统作为营销活动平台中的一个环节,承接着活动玩法、积分消耗、奖品发放等系统的纽带,帮助整个业务完成用户的活跃。
  2. 后阐述领域:实现上要尽可能做到职责隔离,对应系统的具体实现上要拆分出:活动、算法、规则、策略、用户、订单等领域
  3. 引入表设计:根据领域驱动中对各个模块的定义,设计数据库表,也就对应了活动表、抽奖策略配置表、准入规则引擎表、用户抽奖单记录表、以及配合这些表数据结构运行的其他表,如:记录用户领取活动表、用户活动参与次数表 等。

针对这个抽奖系统,会有不同的抽奖活动那就是【活动表】,不同的抽奖规则【抽奖策略表】,对人群的过滤【准入规则表】
然后记录用户信息的【用户表】,一个用户可以参加不同的活动【用户领取活动表】,抽奖完成后会生成该用户的【抽奖单】。

1.1 哪些表设置了唯一键

2.为什么自研路由组件

  1. 我们的做法是因为有成熟方案,所以前期就分库分表了。但为了解释服务器空间所以把分库分表的库,用服务器虚拟出来机器安装。这样即不过多的占用服务器资源,也方便后续数据量真的上来了,好拆分。
  2. 市面的路由组件比如 shardingsphere 但过于庞大,还需要随着版本做一些升级。而我们需要更少的维护成本
  3. 我们的路由组件可以分库分表、自定义路由协议,扫描指定库表数据等各类方式。研发扩展性好,简单易用
  4. 自研的组件更好的控制了安全问题,不会因为一些额外引入的jar包,造成安全风险。
  5. 不能为了等到系统到了200万数据,才分库分表,那么工作量会非常大。

我们的这个路由组件,只是针对该抽奖系统的,在将用户的大量抽奖单在保存到数据库中时,通过用户ID计算出对应的库和表,将用户抽奖单使用分库分表保存来减轻数据库压力,

2.1路由怎样实现的

(1)自定义一个注解@DBRouter(key = “uId”),用于放置在需要被数据库路由的DAO操作上。比如新增用户领取活动。
(2)在AOP 切面拦截中,根据用户ID进行相应的数据库路由计算,并且使用扰动函数加强散列,得到一个索引位置后,在根据库表的数量折算出具体落到那个库表中,最后将计算的库表信息保存到线程的ThreadLocal中。
(3) 通过Mybatis 拦截器,拦截 SQL 语句动态修改添加分表信息,再设置回 Mybatis 执行 SQL 中。
具体操作:获取StatementHandler,获取自定义注解判断是否进行分表操作,statementHandler.getBoundSql()语句获取SQL,从Threadlocal中读取目标库表,替换SQL表名,最后通过反射修改SQL语句

扩展编程式事务

如果一个场景需要在同一个事务下,连续操作不同的DAO操作,那么就会涉及到在 DAO 上使用注解 @DBRouter(key = “uId”) 反复切换路由的操作。虽然都是一个数据源,但这样切换后,事务就没法处理了。
解决:这里选择了一个较低的成本的解决方案,就是把数据源的切换放在事务处理前,而事务操作也通过编程式编码进行处理。

3.规则引擎的设计目的

  • 主要作用是解决抽奖场景中个性化运营的处理,如:人群身份标签、交易记录、活动资格等规则的可配置化的交叉使用。
  • 所以基于这样的情况,此规则引擎的设计是一个二叉树判断,实现手段运用到了组合模式、工厂模式等。并为了便于维护和使用,进行了库表对二叉树的抽象设计,树根、节点、子叶,映射为二叉树编码的相关属性信息。

因为用if-else语句去判断是哪种数据比较麻烦且代码量大大增加,对以后的维护增加了难度,所以我们使用组合模式,将对象组合成树形结构。
在这里插入图片描述

搭建规则引擎树,需要的表【规则树总表】【规则树结点表】【规则树结点连线表】,规则树节点放在数据库中方便动态化配置,
每个节点的逻辑就是一个过滤器(作比对),最后交给树结构执行引擎串联节点间的关系,最后将接口交给外部调用
可以在传入信息或者数据库里,拿到比对值然后在树结构节点里做比对,
在执行引擎里,遍历树结构,while(判断叶子结点还是中间结点){拿到中间结点的决策key(就是判断依据age/gender…),得到具体值,放到过滤器中得到下一个节点往左侧走还是右侧走},遍历到叶子结点结束,就能拿到最后的活动号

3.1怎么使用规则引擎过滤的

规则引擎的设计是一个二叉树判断,通过使用组合模式,将判断节点组合成树形结构,就不用使用if-else语句去判断,便于维护和使用。
这个规则引擎包括:logic 逻辑过滤器、engine 引擎执行器。
逻辑过滤器是一个个二叉树的判断结点
引擎执行器就是在遍历树结构,通过从该树的根节点开始 ,while循环判断,是中间结点,就拿到中间结点的判断依据,查询用户的具体属性,然后放到过滤器中得到下一个节点往左侧走还是右侧走,直到到达叶子结点结束,最后得到该用户筛选后能参与的活动ID

DDD的分层架构,那讲下每个领域的实体

  1. 首先如果理论看的多,喜欢问实体。因为大部分理论是说实体对象是充血模型。但如果开发的多知道只把实体看做领域很难编写代码,要把整个领域模块看做充血模型,之后问每个领域模型是如何设计的。
  2. 那么无论怎么问,你只要回答各个领域模型是如何设计的即可。比如Rule规则领域模型,实体对象类有哪些,聚合对象类有哪些,怎么实现的流程,如何提供的服务。

4.抽奖算法

使用的单项随机概率抽奖就是分配好的奖品概率是固定的。
将概率值存放在数组中,根据概率值直接定义中奖结果,比如20%的一等奖中奖率,就开辟100的数组空间20个经过散列后随机分布的下标位置能中一等奖,
抽奖时用户随机在100范围内生成数组的索引+扰动,查找对应位置对应是否有奖,用空间换时间。
(不公布抽奖结果,大量抽奖并发打进来概率是一样,中奖抽空的位置数组设为没奖)

5.使用了模板模式、组合模式、工厂模式解决代码的

1.模板模式处理抽奖流程,
基于模板设计模式,规范化抽奖执行流程。包括:提取抽象类、编排模板流程、定义抽象方法、执行抽奖策略、扣减中奖库存、包装返回结果等。主要就:以抽象类 AbstractDrawBase 编排定义流程,用 DrawExecImpl 做具体抽奖流程实现。
比如抽象类中定义:1. 获取抽奖策略2.判断是否可以进行抽奖3.执行抽奖算法4.包装结果
具体实现类:抽奖过程具体实现。
2.工厂搭建发奖domain
本质:就是为了简化if else判断不同类型使用不同的代码处理, 使用map将不同的类型和对应的代码联系到一起。让代码变得更整洁。
工厂模式:是一种创建型设计模式,在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
工厂模式通过调用的时候提供发奖类型工厂返回对应的发奖服务。通过这样由具体的子类决定返回结果,并做相应的业务处理。

“发放奖品”工厂作用:外部提供一个奖品类型,工厂提供这个奖品类型需要提供什么样的服务去处理。
3.组合模式
组合模式搭建用于量化人群的规则引擎,用于用户参与活动之前,通过规则引擎过滤性别、年龄、首单消费、消费金额、忠实用户等各类身份来量化出具体可参与的抽奖活动。
组合模式就是不用ifelse来判断,而是通过组合节点搭建一棵二叉树,而库表中则需要把这样一颗二叉树存放进去

6.秒杀

在秒杀流程,先扣减Redis缓存的库存,使用incr,decr对redis操作。因为incr\decr操作是原子性的,将对应的key值-1后返回结果,如果结果<0就发生超卖,将这个订单取消。

然后发送一个 MQ 消息对数据库中的库存进行处理。因为 MQ 可以消峰,减少对数据库的压力。

6.1为什么用滑块锁

滑块锁最早是为了恢复库存,但其实还有另外一个作用。
如果缓存失效,key被删除,缓存key从0开始计数,那么你之前已经对key… key_n加锁了,这样可以保证不超卖。避免风险。

6.2秒杀场景下,使用Redis decr奖品库存扣减和SetNx设置库存锁兜底保证不超卖,

因为incr\decr操作时原子性的,将对应的key值-1后返回结果,如果结果<0就发生超卖,将这个订单取消。

6.3为什么还要setnx加个锁兜底呢?

decr 请求操作也可能在请求时发生网络抖动超时返回,这个时候decr有可能成功,也有可能失败。setNx 锁拦截后,会更加可靠。
setNx是对商品编号(活动ID)加锁,一般在确认领取后(插入领取记录)删除锁,如果删除失败可以定时在活动结束后删除。这样并不会导致死锁,虽然这个商品最后没有卖出(活动没领取),最重要的是保证不超卖。
setNx 因为是非独占锁,可以在活动结束后释放;而独占锁在秒杀过程中不好把握线程释放时间,释放的晚了活动用户都走了,释放的早了,流程可能还没处理完。
如果没有锁,可能会超卖。

6.4库存恢复

关于库存恢复,一般这类抽奖都是瞬态的,且redis集群非常稳定。所以很少有需要恢复库存,如果需要恢复库存,那么是把失败的秒杀incr对应的值的key,加入到待消费队列中。等整体库存消耗后,开始消耗队列库存。

6.5独占锁会出现很多问题

线程超时或者系统宕机等意外情况发现,锁会一直被某些线程持有,造成死锁状态。
如果设置了超时时间来解决死锁,超时时间难以把控,且容易出现一个线程删了另一个线程的锁。

异步MQ + 定时任务 来更新库

使用定时任务也是为了避免MQ消费引起并发问题,所以如果并发量较大,使用定时任务处理缓存和数据库库存同步。

我自己的疑惑:

xxl-job怎么还有数据库表单?
Xxl-job是一个分布式任务调度平台。它使用数据库来存储任务调度相关的信息,如任务调度状态、执行日志等。因此,即使你不直接操作数据库表单,xxl-job依然需要数据库来保存这些信息。

如果你指的是需要创建Xxl-job特定的数据库表单,那么你可以在Xxl-job提供的资源中找到SQL脚本。通常,这些脚本会在其源码包中的"doc"目录下的"db"子目录中。你需要根据你使用的数据库类型(如MySQLPostgreSQL等)来选择相应的SQL脚本执行。

7.为什么用Kafka

消息队列主要用于:在分布式系统中存储转发消息。场景:异步处理,应用解耦,流量削峰和消息通讯四个场景。
使用MQ消息的特性,把用户抽奖到发货的流程进行解耦。这个过程中包括了消息的发送库表中状态的更新消息的接收消费发奖状态的处理等。

在抽奖单中加入mq_state字段用来判断是否发送成功,定时任务检查扫描用户的抽奖单看mq_state是否标记为已发送,发送失败的话就需要补偿发送MQ消息,发送成功消费者处理MQ消息,执行发奖。

8.为什么用xxl-job

XXL-JOB是一个分布式任务调度平台,处理需要使用定时任务解决的场景。
主要用在通过定时任务扫描用户的抽奖单看mq_state是否标记为已发送,发送失败的话就需要补偿发送MQ消息,发送成功消费者处理MQ消息,执行发奖。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2095515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一.海量数据实时分析-Doris入门和安装

前言 停了一个月又开始写文章啦&#xff0c;因为公司数据量达到了几十亿&#xff0c;老板需要做实时数据分析&#xff0c;报表看板。这么大的数据量比较好的选择是使用Doris来做&#xff0c;他可以脱离hadoop生态独立使用所以大受企业喜爱&#xff0c;也因为如此就有了这个系列…

【JavaWeb】Http请求与响应

文章目录 Http 请求与响应一、Http 请求格式1、请求行2、请求头3、请求体&#xff08;post请求才有&#xff09; 二、HttpServletRequest1、获取 请求行 信息2、获取 请求头 信息3、获取 请求参数 信息 三、Http 响应格式1、响应行2、响应头3、响应体&#xff08;正文&#xff…

AI大模型应用开发环境配置

目录 一、工具下载 1、Python官网下载 2、Pycharm官网下载 3、Streamlit官网下载 二、升级PIP &#xff08;一&#xff09;检查PIP版本 &#xff08;二&#xff09;在anaconda Prompt命令窗口输入 三、安装openai组件 四、安装streamlit组件 五、启动streamlit 一、工…

Voi滑板车公司助农扶商,着手打造流量板块

Voi滑板车公司助农扶商&#xff0c;着手打造流量板块。 吉林是粮食大省&#xff0c;是全国优质粳稻主产区&#xff0c;现阶段全省水稻年产量600多万吨&#xff08;商品量400万吨左右&#xff09;&#xff0c;占东北三省一区的24%。巍巍长白山、悠悠松江水&#xff0c;辽阔黑土…

Qt:玩转QPainter序列九(文本,文本框,填充)

前言 继续承接序列八 正文 1. drawImage系列函数 绘制图像 inline void drawImage(const QPoint &p, const QImage &image); 作用: 在指定的点 p 上绘制 QImage 图像。图像的左上角将对齐到 p 点。 inline void drawImage(int x, int y, const QImage &image,…

若依 Vue3的前后端分离系统管理 创建

RuoYi 若依官方网站 |后台管理系统|权限管理系统|快速开发框架|企业管理系统|开源框架|微服务框架|前后端分离框架|开源后台系统|RuoYi|RuoYi-Vue|RuoYi-Cloud|RuoYi框架|RuoYi开源|RuoYi视频|若依视频|RuoYi开发文档|若依开发文档|Java开源框架|Java|SpringBoot|SrpingBoot2.0…

【JPCS独立出版】第四届电气工程与计算机技术国际学术会议(ICEECT 2024,9月27-29)

第四届电气工程与计算机技术国际学术会议&#xff08;ICEECT2024&#xff09;将于9月27日-29日在哈尔滨举办。 会议主要围绕"电路与系统"、“电气工程材料”、“计算机视觉”、“计算机技术”等专业研究领域展开讨论。旨在为气工程、计算机技术等领域的专家学者及企业…

Java EE

Java EE 包含JavaSE 增加一些新的API 构建一个后端服务 网页->web服务器->java后端 web后端(javaEE)程序需要运行在服务器中的&#xff0c;这样前端才可以访问得到 服务器&#xff1a;是容器&#xff0c;是连接用户和程序之间的中间件 解释1&#xff1a;一款软件&#…

HBase 部署及shell操作

HBase 数据库 一、HBase 概述1.1 HBase 是什么HBase 的特点 二、HBase 模型及架构2.1 HBase 逻辑模型2.2 HBase 数据模型2.3 HBase 物理模型2.3.1 列簇物理模型2.3.2 Rowkey 字段排序2.3.3 Region 存储到不同节点2.3.4 Region 结构 2.4 HBase 基本架构 三、搭建 HBase 分布式集…

【Linux】线程结束

目录 线程安全和重入 死锁 STL中的容器不是线程安全的 线程安全的单例模式 自旋锁 读者写者问题 线程安全和重入 线程安全&#xff1a;多个线程并发执行同一段代码时&#xff0c;不会出现不同的&#xff08;异常的&#xff09;结果&#xff0c;我们就说线程是安全的。常见…

如何学好文件操作,快来看这篇文章(沉淀中)!!!!

文章目录 1. 为什么使用文件&#xff1f;2. 什么是文件&#xff1f;2.1 程序文件2.2 数据文件2.3 文件名 3. ⼆进制文件和文本文件&#xff1f;4. 文件的打开和关闭4.1 流和标准流4.1.1 流4.1.2 标准流 4.2 文件指针4.3 文件的打开和关闭 5. ⽂件的顺序读写5.1 顺序读写函数介绍…

jQuery库

注明&#xff1a;本文参考自&#xff1a;jQuery - 白月黑羽 (byhy.net) jQuery安装 Download jQuery | jQuery下载到本地 ps: script标签中的src属性&#xff1a;表示包含要执行的代码的外部文件位置 <!DOCTYPE html> <html lang"en"><head><s…

unity游戏开放:标记物体 一目了然

Unity游戏开发:标记物体,让开发变得一目了然 “好读书&#xff0c;不求甚解&#xff1b;每有会意&#xff0c;便欣然忘食。” 本文目录&#xff1a; Unity游戏开发 Unity游戏开发:标记物体,让开发变得一目了然前言1. 什么是Tag&#xff1f;2. Unity中如何添加和管理Tag步骤1&am…

vue如何引入element-ui

2.x用element-ui 3.x用element-plus https://blog.csdn.net/weixin_41207479/article/details/127066333 引入element-ui的三种方式

点餐API接口对接的过程中需要注意哪些问题

以下是点餐 API 接口对接的一般步骤&#xff1a; 选择合适的点餐 API 服务提供商&#xff1a;市面上有不少提供点餐 API 的平台。你需要根据自身业务需求、预算、接口的稳定性和性能、技术支持等因素来综合考量选择。注册与申请&#xff1a;在选定 API 服务提供商后&#xff0…

数据响应式

响应式原理 课堂主题 1.利用defineProperty实现数据劫持2.利用ES6中proxy实现数据劫持3.实现数据驱动视图更新&#xff0c;实现数据响应4.发布订阅模式 知识点 defineProperty&#xff1b;Proxy代理数据劫持发布订阅观察者模式与发布订阅数据响应式 defineProperty Objec…

Junit单元测试入门

目录 一、单元测试 1.1 基本概念 1.2 以往测试存在的问题和不足 二、快速入门 2.1 基本步骤 2.2 基本使用示例&#xff08;vscode为例&#xff09; 2.2 断言机制&#xff08;重要&#xff09; 2.3 其它注解 一、单元测试 1.1 基本概念 针对最小单元的测试&#xff0c…

集成电路学习:什么是CPU中央处理器

一、CPU&#xff1a;中央处理器 CPU&#xff0c;全称Central Processing Unit&#xff0c;即中央处理器&#xff0c;是计算机系统的核心部件&#xff0c;负责执行程序指令&#xff0c;完成数据的算术运算或逻辑运算等任务。它是计算机中最重要的硬件之一&#xff0c;相当于计算…

【Qt 事件】—— 详解Qt事件处理

目录 &#xff08;一&#xff09;事件介绍 &#xff08;二&#xff09;事件的处理 &#xff08;三&#xff09;按键事件 3.1 单个按键 3.2 组合按键 &#xff08;四&#xff09;鼠标事件 4.1 鼠标单击事件 4.2 鼠标释放事件 4.3 鼠标双击事件 4.4 鼠标移动事件 4.5…

【Redis】Redis 典型应⽤ - 缓存 (cache)

Redis 典型应⽤ - 缓存 cache 什么是缓存使⽤ Redis 作为缓存缓存的更新策略1) 定期⽣成2) 实时⽣成 缓存预热, 缓存穿透, 缓存雪崩 和 缓存击穿关于缓存预热 (Cache preheating)关于缓存穿透 (Cache penetration)关于缓存雪崩 (Cache avalanche)关于缓存击穿 (Cache breakdown…