秒杀系统项目分析

java秒杀系统


前言

秒杀系统,主要功能
1. 用户登录功能+对密码的加密处理
2. 商品主页,商品详情页的页面展示
3. 用户下订单的库存写入功能
4. 用户秒杀的功能处理
5. 利用redis处理页面静态化问题,加快存储速度
6. 处理高并发下可能存在的库存超卖问题
7. 利用消息中间件来进一步提高效率

一、数据库的设计

数据库表有: 用户表 商品表 秒杀商品表 订单表 秒杀订单表
但是秒杀订单表只有三个字段 user_id good_id order_id 这样设计我们后期判断是否存在库存的时候会方便很多

二、用户登录和MD5密文处理

    1. 用户登录
        这个的设计其实还是很容易的,我们的用户名是 手机号, 这样我们只需要根据这个手机号去mysql数据中找就可以了,然后去匹配密码 对比两者是否相同,相同就好说了。那么登陆成功,否则失败。
    2. 密文处理 
        密文处理用的是MD5,也就是知道的,我们会对 用户输入的密码 和 固定的sal进行一个拼接然后使用md5得到x,
        然后用这个x和随机生成的salt拼接然后再进行一次md5得到y 这次我们将y和那个随机的salt放进去数据库中

其中MD5用的是DigestUtils.md5

三、商品秒杀功能开发

在商品详情页goods_detail.html 有一个onclick函数 = "doMiaosha()”

functionm diMiaosha(){
   $.ajax{
      url:"/miaosha/do_miaosha",
      type:"POST",
      data:{
          goodsId:${"goodId"}.val(),
      }
  }
}
.
.
.

我们用POST表单提交数据,主要提交的是对应的商品号ID,由于展示的时候我们的数据主要是从数据库拿的,因此正常这个商品id号码肯定是在数据库中的。我们需要根据这个商品id号码去商品表查看是否stock库存是否大于0, <= 0就是抛出异常

然后我们还要去秒杀订单表判断,是否被秒杀订单表有三个字段 user_id good_id order_id
如果秒杀订单表中 搜索good_id and user_id可以搜索到,那么则秒杀失败,都则秒杀成功

这是需要对商品表中的库存–,同时还要对订单表 和 秒杀订单表数据的插入

数据的插入
@Insert(“insert into miaosha_order (user_id, goods_id, order_id)” values ())
数据的插入都用@Insert注解

上述的数据库操作,我们对于stock - 1以及对订单表和秒杀订单表的插入都要用事务来操作,可以加入springboot提供的注解 @Transactional

四、压测工具的使用——Jmeter

开始步骤:去下载的文件下的bin目录,在终端输入 sh jmeter命令就可以打开了
1.
使用步骤:

  1. 我们首先对测试计划邮件创建一个新的线程组,然后再线程数,Ramp-Up 循环册数中设置我们这里的测试是3000个线程 在10s时间内完成
  2. 接下来对创建的线程组右键创建一个 http请求默认值,然后再web服务器中 对协议输入https 服务器名称,以及端口号
    在这里插入图片描述
    3 接下来是http请求,这一步是我们在control中写入的代码,我们中注解@RequestMappint("/miaosha")
    留下的这些访问路径。
    4 最后是一个结果查询,我们通常查看聚合报告就行了,因此我们需要建立一个聚合报告,来查看压测最后的结果,比如重要的一个吞吐量的问题。
    完成上面这些步骤就只需要在idea上运行,然后单机压测就可以了

五 页面缓存和对象缓存

完成了上面这些,一个基本的秒杀功能就已经结束了,但是我们还需要对高并发进行处理,接下来的优化是针对多用户点击秒杀的处理手法

5.1页面缓存

首先我们知道一个页面相对来说数据是比较多的,就用第一个页面来说,那么有比较多的商品,然后这些商品的信息都是需要去渲染的,一个商品就有 价格,库存,详细描述,图片等等信息,而一个商品主页有比较多的商品那么直接从mysql数据中取值,代价是比较大的。
所以第一个优化是对这些页面渲染优化

对于商品主页我们不再用springboot提供的渲染方法,我们利用thyleaf来进行渲染,这样方便我们取值,具体操作是:
对一个用户,我们首先是从redis中取值,我们知道redis有一个很大的好处,可以非常方便存储string类型的字符串,因此我们首先会redis.get(Gookey.getGoods, key, String.class)

第一步:
   为了完成这一步我们需要建立一个基础类 BaseKey,这个类中有两个属性一个是time 一个是key,然后用GoodsKey去继承这个类,并且建立连个属性我们直接赋值默认
   public static GoodsKey getGoodsList = new GoodsKey(60, "gl");
   public static GoodsKey getGoodsDetali = new GoodsKey(60, "gd");
  这是因为redis在设置值的时候 不是有set 和 setex两个方法来设置值么,我们可以一个作为key 一个作为过期时间time

第二步:
    我们刚刚开始肯定是没有缓存的,那么肯定是无法从redis数据中获取值,那么string html = null 那么不会返回执行下一步
    我们会直接从商品表中获取数据,将所有的商品全部从数据库中读取出来,然后利用model.addAttribute("goodsList", goodsList)注入到里面去方便前段调用展示
    然后调用用Thymeleaf来进行渲染, 函数是: thymeleafViewResolver.getTemplateEngine().process("商品列表页面",ctx) 这个ctx可以由springboot来提供,是SpringWebContext,这时候我们生成的html那么就要存入我们的redis页面了方便下次调用的时候不需要从mysql拿 而是从redis缓存来。
采用这种手法的话,其实东西还是很多的,比如需要商品详情页我们也可以采用同样的方法。

5.2 对订单秒杀的一个过程缓存描述 --对象数据的缓存

我们点击了秒杀订单后,这个按钮操作,然后借用ajax就会进去后端的do_mioasha这个方法。这方面也是通过@RquestMapping注解来做到的。
第一步 @RequestParam(“goodsId”)有一个id参数传递过来,我们会这个去商品表中查看,我们将返回的数据直接用GoodsVo来表示

第二步: 就是去查看库存,调用goodsvo.getstock()方法就可以了 如果 <= 0 那么秒杀失败

第三步 : 我们正常我们需要去查看秒杀订单表中是否有这 user_id 和 good_id这个数据,如果有则失败,这里我们也可以从redis中去查看,key = user_id + goods_id的一个拼接,当然如果我们想要的话,在秒杀成功的时候也要在redis中插入这个字段key - value

第四步: 如果都没有那么库存-- ,同时写入订单(写入订单的时候可以将user_id + goods_id作为key 写入redis数据库)
那么对象缓存也就查不到完成了

六 并发安全问题

接下来是解决并发安全问题,比如超卖问题 和 一个用户在短时间内同时下单两次的问题

  1. 解决超卖问题:就是当库存还有1的时候,那么同时来了两个请求,在多线程的情况下此时都判断库存 >0 ,那么都会去执行减去库存,并且写入订单的操作,这是不合理的,,为了解决这个问题 我们第一个可以用synchronized来对这个秒杀方法上锁,但是这样可以做到安全,但是性能会降低,因为秒杀那个短时间很多人再用,那么可以用数据库来帮忙,我们在下减去库存的时候,额外加一个如果stock > 0 才能 – 。那么必然只有先的可以操作,然后后的一个因为失败,又加上秒杀加入了@Transaction,那么必然全部返回失败。
  2. 一个用户多次请求,就是一个用户不能秒杀两次。这个很容易我们队 秒杀订单表设置唯一索引,也就是user_id 和 goods_id设置唯一索引。

七 Redis + RabbitMQ优化

7.1 Redis缓存优化

关于下订单的时候,因为是秒杀订单,所以肯定会遇到大量的用户,我们为了防止大量请求来访问数据库,我们队MiaoshaController实现了InitializingBean接口,同时去实现afterPropertiesSet方法来进行最开始的初始化,在这个方法里面,我们会首先读取商品表,然后将库存读到redis中对应的 gd_商品id号码,value = stock库存。

有了这以后,我如果某个商品的库存只有10个,突然来100个请求,那么就会在redis数据库中进行预减,那么如果stock小于0,就会去返回失败了

7.2 RabbitMQ优化

我们用的RabbitMQ模式是direct模式 生产者和消费者,具有相同的交换机名称(Exchange)、交换机类型和相同的密匙(routingKey),那么消费者即可成功获取到消息 只有一个队列

如果订单中不存在这个user_id 和 goods_id的话,那么我们就会用到RabbitMQ来进行异步下单:

  1. 首先去创建一个 MiaoshaMessage mm 主要属性是 user 和 gooid_id 我们将这个信息放到消息队列中,因为这个是bean 所以还需要用到类型转化,最后用AmqpTemplate.convertAndsend()将信息发送给消息端。
  2. 消息端受到消息,就回去判断库存(这个时候去mysql中),如果小于0,失败,如果大于0,那么仍然要去商品秒杀表中看看有没有存在,如果都没有那么执行减库存,写入订单表。 这一步同时有两个操作在执行: 请求出队,生成订单,较少库存; 另一个是 客户端轮训,判断是否秒杀成功,我这里用ajax来,和setTimeout()来做到一个不断的调用函数来轮询。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值