抽奖项目技术亮点

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

表设计

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

怎样保证幂等性

(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.rhkb.cn/news/414097.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu通过smba访问华为设备

文章目录 ubuntu通过smba访问华为设备华为设备设置ubuntu设置访问测试 ubuntu通过smba访问华为设备 华为设备设置 华为设备在华为分享一栏下有共享至电脑的选项&#xff0c;打开即可&#xff0c;这里会创建用户名和密码进入设置 -> 关于手机/平板电脑 -> 状态信息&…

HCS-网络服务

一、华为云Stack网络服务概览 1.网络服务包括&#xff1a;虚拟私有云、弹性负载均衡、弹性IP、网络ACL、虚拟专用网络、云专线、VPC终端节点、云解析 2.华为云Stack网络服务全景图&#xff1a; 二、云上通用网络服务 1.虚拟私有云 虚拟私有云&#xff08;Virtual Private Clo…

基于yolov8的道路病害道路裂缝道路坑洞检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的道路病害检测系统&#xff0c;特别是针对道路裂缝和坑洞的检测&#xff0c;是一种高效、准确的智能解决方案。该系统利用YOLOv8这一先进的深度学习算法&#xff0c;通过深度神经网络对图像进行特征提取和分类&#xff0c;实现对道路病害的实时检测和…

华为AC旁挂二层组网配置详解:从DHCP部署到无线业务配置,完成网络搭建

组网需求 AC组网方式&#xff1a;旁挂二层组网。 DHCP部署方式&#xff1a; AC作为DHCP服务器为AP分配IP地址。 防火墙作为DHCP服务器为STA分配IP地址。 业务数据转发方式&#xff1a;直接转发。 网络拓扑图 对于旁边路直接转发&#xff0c;优点就是数据流量不经过AC&…

【C++ Primer Plus习题】8.3

问题: 解答: #include <iostream> #include <string> #include <cctype> using namespace std;void function(string& str) {for (int i 0; i < str.size(); i){str[i]toupper(str[i]);} }int main() {string str;while (true){cout << "…

82.给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。实现返回已排序的链表

删除排序链表中的重复元素 II 一、题目描述 82. 删除排序链表中的重复元素 II 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。 示例 1: 输入:head = [1,2,3,3,4,4,5] 输出:[1,2,5] 示例 2: 输入:hea…

谈一谈JVM的GC(垃圾回收)

JVM&#xff08;Java Virtual Machine&#xff09;的GC&#xff08;Garbage Collection&#xff0c;垃圾回收&#xff09;是Java语言的一个重要特性&#xff0c;它负责自动管理内存&#xff0c;释放那些不再被使用的对象所占用的内存空间。以下是对JVM GC的详细介绍&#xff1a…

Python爬虫案例四:爬取某个博主的所有文章保存成PDF格式

引入&#xff08;将图片保存成PDF格式&#xff09;&#xff1a; 测试链接&#xff1a; https://zq.bookan.com.cn/?tdetail&id21088&ct1&is31042341&rid4658&#xff08;图书馆图片保存PDF&#xff09;&#xff0c;前提是装库&#xff0c;pip install img2pdf…

VTK平面切割

文章目录 一、vtkClipPolyData二、CapClip三、SolidClip四、vtkClipClosedSurface 本文的主要内容&#xff1a;简单介绍VTK中通过平面切割模型的相关功能。 哪些人适合阅读本文&#xff1a;有一定VTK基础的人。 一、vtkClipPolyData VTK官网描述&#xff1a; vtkClipPolyData使…

网络各层的安全实例:从物理层到应用层的保护

在前面的各节中&#xff0c;我们学习了利用密码学技术实现机密性、完整性、数字签名和实体鉴别等安全服务的基本方法。在本节中&#xff0c;我们将讨论这些方法在网络各层的具体应用实例。这些安全应用实例涉及从物理层到应用层的所有层次。 1. 为什么需要在网络各层提供安全服…

GoLang:Go语言开发环境的配置

Go语言 Go语言开发环境的配置 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/a…

【Matlab】时间序列模型(ARIMA)

文章目录 前言一、示例二、代码实现----Matlab全部数据的平稳性检验ADF检验图检验法 划分训练集平稳性检验确定 p&#xff0c;q结果分析和模型检验模型预测 前言 接上一篇博客&#xff0c;用 Matlab 完成代码编写。 【学习笔记】时间序列模型(ARIMA) 一、示例 已知一个上市公…

Pandas 9-绘制柱状图

1. 准备数据 首先&#xff0c;需要准备一个DataFrame。 import pandas as pd # 创建一个DataFrame data { Name: [Alice, Bob, Charlie, David], Age: [24, 27, 22, 32], City: [New York, Los Angeles, Chicago, Houston], Score: [85, 92, 78, 88]} df pd.…

sql-labs46-50通关攻略

第46关 一.查询数据库 http://172.16.1.142/Less-46/?sort1%20and%20updatexml(1,concat(0x7e,(select%20database()),0x7e),1)--http://172.16.1.142/Less-46/?sort1%20and%20updatexml(1,concat(0x7e,(select%20database()),0x7e),1)-- 二.查表 http://172.16.1.142/Les…

软件测试 | 测试用例

测试用例&#xff08;Test Case&#xff09;是为了实施测试而向被测试的系统提供的一组集合&#xff0c;这组集合包含&#xff1a;测试环境&#xff0c;测试步骤&#xff0c;测试数据&#xff0c;预期结果等要素。 设计测试用例原则⼀&#xff1a; 测试用例中⼀个必需部分是对…

【微机原理】指令JZ和JNZ的区别

&#x1f31f; 嗨&#xff0c;我是命运之光&#xff01; &#x1f30d; 2024&#xff0c;每日百字&#xff0c;记录时光&#xff0c;感谢有你一路同行。 &#x1f680; 携手启航&#xff0c;探索未知&#xff0c;激发潜能&#xff0c;每一步都意义非凡。 JZ&#xff08;Jump …

php特性刷题

93 上面注释的是一些配置信息 然后包含flag.php页面 高亮显示 如果&#xff0c;先判断是否存在GET传参的参数num&#xff0c;如果弱比较等于4476&#xff0c;就会输出“no non no !” 如果包含字母那么就错误&#xff08;包含大小写&#xff09; 判断变量 $num 是否等于 4…

QEMU - user network

Documentation/Networking - QEMUQEMU/KVM中的网络虚拟化--Part2 User Networking | Xiaoye Zhengs blog (zxxyy.github.io)QEMU Network — ARM SoC Device Assignment Notes documentation (cwshu.github.io)slirp / libslirp GitLabGitHub - virtualsquare/libvdeslirp: li…

Python中排序算法之选择排序

选择排序算法是对《Python中排序算法之冒泡排序》中提到的冒泡排序算法的改进。 1 选择排序原理 选择排序是在参加排序的所有元素中找到数值最小&#xff08;或最大&#xff09;的元素&#xff0c;如果它不是左侧第一个元素&#xff0c;就使它与左侧第一个元素中的数据相互交…

Mysql8利用binlog实现数据恢复

文章目录 1binlog基本概念2 binlog相关常用命令3 binlog工具mysqlbinlog4 测试数据准备&导入数据5 模拟误删表6 数据恢复方式说明7 数据恢复分析(偏移量方式恢复)8 数据恢复9 验证10 数据恢复的局限性11 总结 1binlog基本概念 binlog即binary log&#xff0c;二进制日志文件…