设计一个抽奖系统

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

文章目录

  • 抽奖系统
    • 需求概述
    • 设计思路
      • 总库表梳理
      • 核心领域开发之抽奖领域
        • 库表梳理
        • 结构梳理
        • 核心介绍
      • 核心领域开发之发放奖品领域
        • 结构梳理
      • 核心领域开发之活动领域
        • 流程梳理
        • 结构梳理
      • ID生成与分库分表
      • 应用层编排
      • 规则引擎设计
      • MQ解耦发货流程
      • 定时任务扫描
      • 扫描库表补偿发货单MQ消息
      • 分布式锁扣减
      • 总体思路
  • 优化思路
    • 机器环境配置:
    • 一、定义 REST 接口进行压测使用
    • 二、Postmain 模拟调用
    • 三、梯度压测
    • 四、数据库连接异常
    • 五、引入 Druid 数据源
    • 六、分布式锁和抽奖的后续流程
    • 七、添加索引
    • 八、Arthas 分析接口响应时长并将查库操作存入 Redis
    • 九、磁盘和网络带宽导致负载增加
    • 十、增加 Tomcat 线程数

抽奖系统

需求概述

如果设计一个抽奖系统,如何设计一个高并发的秒杀系统?

这类项目在网上其实很多,但是实际的工作流到底是什么呢?难不成只有简单的数据库操作和逻辑判断吗?实际的工作流都有哪些呢?站在整体上来看都需要哪些呢?下面就以一个项目来讲解下都有什么?

设计思路

总库表梳理

在这里插入图片描述

核心领域开发之抽奖领域

库表梳理

在这里插入图片描述

结构梳理

在这里插入图片描述

核心介绍

其实可以看到,该部分其实是DDD结构中的一个单独的领域,主要是用来走抽奖逻辑。那么实际上仅仅对于抽奖这件事来说,其实就是抽奖策略的设计。通过策略包装里面的doDraw方法选择合适的策略进行抽奖。那么核心流就是策略都有哪些?

实际上关于这方面的策略主要有 总体策略 和 单项策略。

单项策略就是说加入某个奖品抽完了,我们不需要重新计算概率,如果刚好抽到了没有的奖品,那么就相当于没抽到

而总体概率加入说没有商品了,需要重新计算现有商品的概率。

核心领域开发之发放奖品领域

这一步算的上是,抽出奖品后以什么样的规则进行发放了。

结构梳理
lottery-domain
└── src
    └── main
        └── java
            └── cn.itedus.lottery.domain.award
                ├── model
                ├── repository
                │   ├── impl
                │   │   └── AwardRepository
                │   └── IAwardRepository
                └── service
                    ├── factory
                    │   ├── DistributionGoodsFactory.java
                    │   └── GoodsConfig.java
                    └── goods
                        ├── impl
                        │   ├── CouponGoods.java
                        │   ├── DescGoods.java
                        │   ├── PhysicalGoods.java
                        │   └── RedeemCodeGoods.java
                        ├── DistributionBase.java
                        └── IDistributionGoodsc.java

关于 award 发奖领域中主要的核心实现在于 service 中的两块功能逻辑实现,分别是:goods 商品处理factory 工厂

goods:包装适配各类奖品的发放逻辑,虽然我们目前的抽奖系统仅是给用户返回一个中奖描述,但在实际的业务场景中,是真实的调用优惠券、兑换码、物流发货等操作,而这些内容经过封装后就可以在自己的商品类下实现了。

factory:工厂模式通过调用方提供发奖类型,返回对应的发奖服务。通过这样由具体的子类决定返回结果,并做相应的业务处理。从而不至于让领域层包装太多的频繁变化的业务属性,因为如果你的核心功能域是在做业务逻辑封装,就会就会变得非常庞大且混乱。

核心领域开发之活动领域

流程梳理

在这里插入图片描述

实际上活动的创建在实际的工作流中必须涉及到这些步骤,那么基于这些步骤就可以设计具体的代码结构

结构梳理
lottery-domain
└── src
    └── main
        └── java
            └── cn.itedus.lottery.domain.activity
                ├── model
                ├── repository
                │   └── IActivityRepository
                └── service
                    ├── deploy
                    ├── partake [待开发]
                    └── stateflow
                        ├── event
                        │   ├── ArraignmentState.java
                        │   ├── CloseState.java
                        │   ├── DoingState.java
                        │   ├── EditingState.java
                        │   ├── OpenState.java
                        │   ├── PassState.java
                        │   └── RefuseState.java
                        ├── impl
                        │   └── StateHandlerImpl.java
                        ├── AbstractState.java
                        ├── IStateHandler.java
                        └── StateConfig.java

ID生成与分库分表

关于 ID 的生成因为有三种不同 ID 用于在不同的场景下;

  • 订单号:唯一、大量、订单创建时使用、分库分表
  • 活动号:唯一、少量、活动创建时使用、单库单表
  • 策略号:唯一、少量、活动创建时使用、单库单表

所以针对订单号的这种情况,需要考虑分库分表的实现思路

综合来说具体的实现思路如下:设计一个简易版的数据库路由-CSDN博客

应用层编排

在这里插入图片描述

这个图其实就简单的将主题的思路都囊括出来了,当有了各种活动以后,需要对当前用户能否参与活动进行检验,如状态、日期、库存、参与次数等。然后进入抽奖部分,按照不同的策略进行抽奖,最终做到落库,在这里后续的设计打算使用MQ进行解耦分离。

规则引擎设计

应用层编排的设计的具体思路其实还是需要知道一开始的活动才行的,但是假如说活动有很多,怎么知道要参加什么活动呢?

所以需要一种规则引擎

在这里插入图片描述

通过这种规则引擎的方式来选取不同的活动,然后在执行后续的逻辑。

MQ解耦发货流程

在这里插入图片描述

使用消息队列必须要考虑的是发送成功或者失败,以后重复消费的问题。

首先需要开启幂等。然后发送端如果发送失败的话,更新表,这个表中存储的是发送成功或者失败的状态。如果发送失败的话,将来需要使用定时任务进行回调,而图中下半部分的MQ是否消费成功,则是手动开启了消费确认。

定时任务扫描

按照上面说的,如果任务发送MQ失败了,需要用定时任务进行回调,就是使用这个,现在比较常见的有xxl-job,这里我们使用自研的一个:设计一个简易版本的分布式任务调度系统-CSDN博客

扫描库表补偿发货单MQ消息

在这里插入图片描述

按照定时任务扫描,如果成功了就发送MQ,没有成功的话,就继续等待下一次定时任务扫描即可。

分布式锁扣减

在这里插入图片描述

  • 在抽奖系统中引入 Redis 模块,优化用户参与抽奖活动。因为只要有大量的用户参与抽奖,那么这个就属于秒杀场景。所以需要使用 Redis 分布式锁的方式来处理集中化库存扣减的问题,否则在 TPS 达到1k-2k,就会把数据库拖垮。
  • 在设计秒杀流程时,优化锁的颗粒度力度,不要把锁直接放到活动编号上,这样在极端临界情况下会出现秒杀解锁失败,导致库存有剩余但不能下单的情况。所以需要增加锁的颗粒度,以滑动库存剩余编号的方式进行加锁,例如 100001_1、100001_2、100001_3,以此类推,具体看代码实现。
  • 增加缓存扣减库存后,发送 MQ 消息进行异步更新数据库中活动库存,做最终数据一致性处理。这一部分如果你的系统并发体量较大,还需要把 MQ 的数据不要直接对库更新,而是更新到缓存中,再由任务最阶段同步,以此减少对数据库表的操作

即使是使用 Redis 分布式锁,我们也不希望把锁的颗粒度放的太粗,否则还是会出现活动有库存但不能秒杀,提示“活动过于火爆”。那么我们就需要按照活动编号把库存锁的颗粒度缩小,实际操作也并不复杂,只是把活动ID+库存扣减后的值一起作为分布式锁的Key,这样就缩小了锁的颗粒度。

其中有一个比较关键的就是扣减库存后,在各个以下的流程节点中,如果有流程失败则进行缓存库存的恢复操作。

总体思路

在这里插入图片描述

优化思路

前置知识:项目压测优化实践思路-CSDN博客

机器环境配置:

1、阿里云服务器三台 4c8G 100mbps 带宽

在这里插入图片描述

2、一台中间件机器

在这里插入图片描述

3、一台监控机器 prometheus、influxdb 、grafana

在这里插入图片描述

4、一台应用机器 jdk 、lottery 、arthas、 nedo_export

在这里插入图片描述

一、定义 REST 接口进行压测使用

在这里插入图片描述

二、Postmain 模拟调用

Postmain 模拟调用 RESTFUL 接口 需关注响应时长和返回数据包大小,因为数据包大小会影响到带宽占用。根据响应时长可以算出压测中线程组的执行总时长

在这里插入图片描述

三、梯度压测

梯度压测(逐渐增加并发,观察系统的负载,找到系统的临界点)

在这里插入图片描述

线程数:根据接口的响应时间来决定 如果很短 就可以用很少的线程 反之更多的线程循环次数:接口响应时间 * 循环次数 = 执行样本的时间(s) 可以控制所有线程组在多久的时间内执行完成

线程数循环次数样本数
5400020000
10400040000
20400080000
254000100000
304000120000
354000140000
404000160000
454000180000
504000200000

总线程总数:275

总循环次数:40000 次

总样本数:1,040,000

四、数据库连接异常

开始压测:MySQL 直接报错

问题 1:数据库连接异常

在这里插入图片描述

引入 DBRouter 待数据源配置的 用 master 分支的代码(需要在 dbrouter 的 master 分支加一个 druid 依赖否则会报如下错误)

Caused by: java.lang.ClassNotFoundException: com.alibaba.druid.pool.DruidDataSource
at java.net.URLClassLoader.findClass(URLClassLoader.java:387) ~[na:1.8.0_371]
at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[na:1.8.0_371]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355) ~[na:1.8.0_371]
at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[na:1.8.0_371]
at java.lang.Class.forName0(Native Method) ~[na:1.8.0_371]
at java.lang.Class.forName(Class.java:264) ~[na:1.8.0_371]
at
cn.bugstack.middleware.db.router.config.DataSourceAutoConfig.createDataSource(DataSourceAu
toConfig.java:103) ~[db-router-spring-boot-starter-1.0.2-SNAPSHOT.jar:1.0.2-SNAPSHOT]

加入 Druid 依赖,用的是 Master 分支代码 需要加一个 Druid 数据源 否则启动

在这里插入图片描述

在 Lottery 的父依赖中排除指定依赖

在这里插入图片描述

五、引入 Druid 数据源

压测 2:修改 Druid 数据源后直接压测

系统负载:1 分钟负载 2.1 5 分钟负载 1.3 15 分钟负载 0.7;说明系统可以处理过来系统的负载和 CPU 的核数有关,对于 4 核 CPU 来说 当负载大于 4 就需要介入查看。CPU 使用和内存占用不高

在这里插入图片描述

每个样本的汇总报告:系统的 TPS 持续增加

在这里插入图片描述

RT:最开始响应时间长 后来持续平稳,应该是最开始的样本 完成了抽奖的整个流程,后续的请求返回的都是系统无库存,在 Redis 层拦截

在这里插入图片描述

TPS

在这里插入图片描述

活动线程数

在这里插入图片描述

总结:看监控信息显示,负载不高,响应时间在最开始的时候稍长 后续逐渐平稳,TPS 持续增加,异常率很低。推测是在 Redis 层库存校验时进行了拦截返回,几乎后面所有的请求响应结果都是无库存。

{"code":"0001","info":"活动剩余库存非可用","drawAwardInfo":null}

六、分布式锁和抽奖的后续流程

压测三:基于压测二的总结,接下来压测 获取分布式锁和抽奖的后续流程,【建议先跑两个线程组看看,接口的响应时间,在决定启动多个线程执行多少次,尽量避免直接启动压测二的样本】

(1)Jmeter 测试计划

在这里插入图片描述

(2)样本和线程数

线程数循环数样本数
510005000
10100010000

(3)聚合报告

看聚合报告 RT 的相应时间都在 100ms 以上,所以适合用 少线程 多循环的方式执行(原因是因为低时延)

在这里插入图片描述

prometheus 监控,整个系统资源使用率 不高 ,负载不高 IO 也不高

在这里插入图片描述

TPS:

在这里插入图片描述

RT:响应时间越来越长

在这里插入图片描述

CPU:利用率很低

在这里插入图片描述

七、添加索引

压测三改进:

(1)对应响应时间长的接口,压测的时候可以增加多个线程数 ,循环次数减

(2)由于操作数据库比较多查询较多,建立字段索引列,加快查询效率

创建联合索引,user_take_activity 和 user_take_activity_count 添加联合索引,activity 表可以给 activity_id 加一个单列索引(数据量较少,暂时看不出效果)

在这里插入图片描述

索引执行效果

在这里插入图片描述

样本:

线程数循环次数样本数
200204000
300206000

聚合报告:吞吐量上来了是上一轮压测的 8 倍多 但是执行响应时间还是很长,平均值还在708ms 左右 是上一轮的 7 倍左右

在这里插入图片描述

RT:

在这里插入图片描述

总结:

通过响应时间 可以确定是采用多线程 少循环次数 还是 少线程 多循环次数

增加索引值 ,提高查询效率,TPS 有所回升,所以我们需要确定代码中那块的执行时间比较长,进而优化,借助阿里的 arthas 性能分析

八、Arthas 分析接口响应时长并将查库操作存入 Redis

监控 doDrawProcess 找到耗时的 doPartake 方法

trace cn/itedus/lottery/application/process/draw/impl/ActivityDrawProcessImpl doDrawProcess

在这里插入图片描述

监控 doPartake 方法

trace cn/itedus/lottery/domain/activity/service/partake/IActivityPartake doPartak

查询账单接口响应时间 100MS 左右 生成领取记录接口在 224ms 左右

在这里插入图片描述

优化查询账单接口:将查库操作改为查 Redis

修改后的代码:

在这里插入图片描述

压测后的结果:RT 有所减少 TPS 有所增加

在这里插入图片描述

Arthas 返回结果:查询账单接口减少 50ms

在这里插入图片描述

九、磁盘和网络带宽导致负载增加

增加线程数压测

样本数:

线程数循环次数样本数
20020040000
40020080000
600200120000
800200160000
1000200200000

聚合报告: TPS 平均在 1500 左右 RT 平均在 472

在这里插入图片描述

prometheus 监控显示 系统 1 分钟负载已达到 5.4 说明系统中有大量的排队请求,系统已经请求不过来了,这个时候就需要优化了,因为已经达到系统的瓶颈了,带宽也达到最大值,说明带宽也影响了系统的处理能力,CPU 和内存正常

在这里插入图片描述

磁盘 IO 使用率很高、网络带宽不足或延迟 造成系统的负载很高 但是 CPU 和内存正常

在这里插入图片描述

TPS: 很稳定

在这里插入图片描述

RT:响应时间在增加

在这里插入图片描述

十、增加 Tomcat 线程数

Tomcat 线程数为 400 默认 200

在这里插入图片描述

prometheus 监控显示 负载有所降低

在这里插入图片描述

聚合报告:

在这里插入图片描述

RT:

在这里插入图片描述

TPS:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux操作】国产Linux服务管理操作

【Linux操作】国产Linux服务管理操作 前言SAMBA配置服务器端1. 安装相关包2. 配置/etc/samba/smb.conf,在此文件末尾添加如下内容,并保存退出。3. 创建/home/share并更改权限4. 启动samba服务 客户端• Windows客户端• 麒麟客户端 Telnet1、telnet语法2…

Spring IOC 之加载 BeanDefinition

1、前言 前面的文章我们已经对IOC之Spring统一资源加载策略有了一定的了解,本文我们将探讨Spring IOC 加载 BeanDefinition的整个过程。 我们先先看一段熟悉的代码: ClassPathResource resource new ClassPathResource("bean.xml"); // &l…

区域入侵/区域人数统计AI边缘计算智能分析网关V4如何修改IP地址?

智能分析网关V4是TSINGSEE青犀推出的一款AI边缘计算智能硬件,硬件采用BM1684芯片,集成高性能8核ARM A53,主频高达2.3GHz,INT8峰值算力高达17.6Tops,FB32高精度算力达到2.2T,硬件内置了近40种AI算法模型&…

高精度算法笔记

目录 加法 减法 乘法 除法 高精度加法的步骤&#xff1a; 1.高精度数字利用字符串读入 2.把字符串翻转存入两个整型数组A、B 3.从低位到高位&#xff0c;逐位求和&#xff0c;进位&#xff0c;存余 4.把数组C从高位到低位依次输出 1.2为准备 vector<int> A, B, C…

DataXCloud部署与配置[智数通]

静态IP设置 # 修改网卡配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33# 修改文件内容 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic IPADDR192.168.18.130 NETMASK255.255.255.0 GATEWAY192.168.18.2 DEFROUTEyes IPV4_FAILURE_FATALno IPV6INIT…

【深度学习入门】深度学习基础概念与原理

*&#xff08;本篇文章旨在帮助新手了解深度学习的基础概念和原理&#xff0c;不深入讨论算法及核心公式&#xff09; 目录 一、深度学习概述 1、什么是深度学习&#xff1f; 2、深度学习与传统机器学习的区别 3、深度学习的应用领域 二、深度学习基本原理 1、神经网络的…

RF自动化环境安装+自动化实例解析

RF定义&#xff1a; 通用型的 自动测试框架&#xff0c; 绝大部分的软件的的自动化系统都可以采用它。 特点&#xff1a; 测试数据文件&#xff08;Test Data&#xff09;对应一个个的测试用例。测试数据文件里面使用的功能小模块叫关键字&#xff0c;由测试库&#xff08;T…

Vue3组件库开发 之Button(1)

需求分析&#xff1a; Button 组件大部分关注样式&#xff0c;没有交互 根本分析可以得到具体的属性列表&#xff1a; type:不同的样式(Default,Primary,Danger,Info,Success,Warning) plain:样式的不同展现模式boolean round:圆角boolean circle:圆形按钮&#xff0c;适合图标…

工具推荐 |Devv.ai — 最懂程序员的新一代 AI 搜索引擎

介绍 伴随 GPT 的出现&#xff0c;我们可以看到越来越多的 AI 产品&#xff0c;其中也不乏针对程序员做的代码生成工具。 今天介绍的这款产品是一款针对中文开发者的 AI 搜索引擎&#xff0c;Devv.ai 使用 Devv.ai 的使用非常简单&#xff0c;就是传统的搜索场景&#xff…

「简明教程」轻松掌握 MongDB 流式聚合操作

「简明教程」轻松掌握 MongDB 流式聚合操作 信息科学中的聚合是指对相关数据进行内容筛选、处理和归类并输出结果的过程。MongoDB 中的聚合是指同时对多个文档中的数据进行处理、筛选和归类并输出结果的过程。数据在聚合操作的过程中&#xff0c;就像是水流过一节一节的管道一…

C++ 程序文档生成器(doxygen)使用说明

程序文档&#xff0c;是每个程序员必看文档&#xff0c;在日常业务开发中&#xff0c;难免会封装一些组件。没有很好的组件文档&#xff0c;再好的组件都是废物&#xff0c;。因此大型业务中&#xff0c;文档和思维导图&#xff0c;两个都是必备&#xff01; 一、注释风格 …

【面试合集】说说微信小程序的支付流程?

面试官&#xff1a;说说微信小程序的支付流程&#xff1f; 一、前言 微信小程序为电商类小程序&#xff0c;提供了非常完善、优秀、安全的支付功能 在小程序内可调用微信的API完成支付功能&#xff0c;方便、快捷 场景如下图所示&#xff1a; 用户通过分享或扫描二维码进入商…

[Python进阶] 正则表达式的验证

8.2 正则表达式的验证 正则表达式的语法很令人头疼&#xff0c;即使对经常使用它的人来说也是如此。由于难于读写&#xff0c;容易出错&#xff0c;所以找一种工具对正则表达式进行测试是很有必要的。 8.2.1 本地验证 通过Regex Tester这款软件可以在本地对正则表达式进行验…

机器学习算法实战案例:Informer实现多变量负荷预测

文章目录 机器学习算法实战案例系列答疑&技术交流1 实验数据集2 如何运行自己的数据集3 报错分析 机器学习算法实战案例系列 机器学习算法实战案例&#xff1a;确实可以封神了&#xff0c;时间序列预测算法最全总结&#xff01; 机器学习算法实战案例&#xff1a;时间序列…

Linux shell编程学习笔记39:df命令

0 前言1 df命令的功能、格式和选项说明 1.1 df命令的功能1.2 df命令的格式1.3 df命令选项说明 2 df命令使用实例 2.1 df&#xff1a;显示主要文件系统信息2.2 df -a&#xff1a;显示所有文件系统信息2.3 df -t[]TYPE或--type[]TYPE&#xff1a;显示TYPE指定类型的文件系统信…

AIGC实战——像素卷积神经网络(PixelCNN)

AIGC实战——像素卷积神经网络 0. 前言1. PixelCNN 工作原理1.1 掩码卷积层 1.2 残差块2. 训练 PixelCNN3. PixelCNN 分析4. 使用混合分布改进 PixelCNN小结系列链接 0. 前言 像素卷积神经网络 (Pixel Convolutional Neural Network, PixelCNN) 是于 2016 年提出的一种图像生成…

LINUX基础培训九之网络管理

前言、本章学习目标 了解LINUX网络接口和主机名配置的方法熟悉网络相关的几个配置文件了解网关和路由熟悉网络相关的命令使用 一、网络IP地址配置 在Linux中配置IP地址的方法有以下这么几种&#xff1a; 1、图形界面配置IP地址&#xff08;操作方式如Windows系统配置IP&…

机器学习:线性回归模型的原理、应用及优缺点

一、原理 线性回归是一种统计学和机器学习中常用的方法&#xff0c;用于建立变量之间线性关系的模型。其原理基于假设因变量&#xff08;或响应变量&#xff09;与自变量之间存在线性关系。 下面是线性回归模型的基本原理&#xff1a; 模型拟合&#xff1a; 通过最小二乘法&…

1、机器学习模型的工作方式

第一步,如果你是机器学习新手。 本课程所需数据集夸克网盘下载链接:https://pan.quark.cn/s/9b4e9a1246b2 提取码:uDzP 文章目录 1、简介2、决策树优化3、继续1、简介 我们将从机器学习模型如何工作以及如何使用它们的概述开始。如果你以前做过统计建模或机器学习,这可能感…

【Web】CTFSHOW 文件上传刷题记录(全)

期末考完终于可以好好学ctf了&#xff0c;先把这些该回顾的回顾完&#xff0c;直接rushjava&#xff01; 目录 web151 web152 web153 web154-155 web156-159 web160 web161 web162-163 web164 web165 web166 web167 web168 web169-170 web151 如果直接上传php文…