抽奖项目技术亮点

2024-09-02 02:12
文章标签 抽奖 亮点 项目 技术

本文主要是介绍抽奖项目技术亮点,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

表设计

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

怎样保证幂等性

(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.chinasem.cn/article/1128709

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧

SpringBoot项目是如何启动

启动步骤 概念 运行main方法,初始化SpringApplication 从spring.factories读取listener ApplicationContentInitializer运行run方法读取环境变量,配置信息创建SpringApplication上下文预初始化上下文,将启动类作为配置类进行读取调用 refresh 加载 IOC容器,加载所有的自动配置类,创建容器在这个过程

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出 在数字化时代,文本到语音(Text-to-Speech, TTS)技术已成为人机交互的关键桥梁,无论是为视障人士提供辅助阅读,还是为智能助手注入声音的灵魂,TTS 技术都扮演着至关重要的角色。从最初的拼接式方法到参数化技术,再到现今的深度学习解决方案,TTS 技术经历了一段长足的进步。这篇文章将带您穿越时

系统架构设计师: 信息安全技术

简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 系统架构设计师: 信息安全技术前言信息安全的基本要素:信息安全的范围:安全措施的目标:访问控制技术要素:访问控制包括:等保

Maven创建项目中的groupId, artifactId, 和 version的意思

文章目录 groupIdartifactIdversionname groupId 定义:groupId 是 Maven 项目坐标的第一个部分,它通常表示项目的组织或公司的域名反转写法。例如,如果你为公司 example.com 开发软件,groupId 可能是 com.example。作用:groupId 被用来组织和分组相关的 Maven artifacts,这样可以避免