秒杀项目总结

项目技术

前端

Thymeleaf+BootStrap+Jquery

后端

springBoot(2.6.1)+MyBaitsPlus(3.5.0)+Lombok+MySQL(5.7)

中间件

RabbitMQ(3.8.5)+Redis(6.2.1)

项目架构图

在这里插入图片描述

项目亮点

  • 使用分布式Session技术,可以让多台服务器同时响应
  • 使用页面静态化,加快用户访问速度,提高QPS,缓存页面至浏览器,前后端分离降低服务器压力
  • 使用redis做缓存提高访问速度和并发量,减少数据库压力,利用内存标记减少redis的访问
  • 使用消息队列完成异步下单,提升用户体验,削峰和降流
  • 安全性优化:双重md5密码校验,秒杀接口地址的隐藏,接口限流防刷,数学公式验证码

秒杀流程

  • 用户登录进入商品列表页面,静态资源在redis进行缓存
  • 点击商品进入商品详情页面,静态资源在redis缓存,使用Ajax获取验证码等动态信息
  • 点击秒杀, 将验证码结果和商品ID传给后端,如果结果正确。动态生成随机串UUID,结合用户ID和商品ID存入redis,并将path传给前端。前端获取path后,再根据path地址调用秒杀服务
  • 服务端获取path参数,redis查找缓存是否存在
  • 如果存在,并且Redis库存大于0,预减Redis库存,查看是否生成订单,防止重复抢购,如果订单不存在,将请求存入消息队列
  • 从消息队列中取消息:获取商品ID和用户ID,判断数据库库存,然后下单
  • 下单:减少库存,生成订单
  • 前端轮询订单生成结果,50ms继续轮询或者秒杀是否成功和失败

项目完整流程

一.项目框架搭建

1、搭建SpringBoot环境
2、集成集成Thymeleaf,RespBean
3、Mybaits连接数据库

二.分布式会话

1、用户登录功能实现

  • 创建用户表
  • 明文密码两次MD5加密
    原因:用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双 重保险。
  • 参数校验
    a、 使用 validation 简化代码,减少健壮性的判断
    b、自定义@IsMobile注解,进行手机号码格式的判断
    c、对传入的LoginVO参数添加@Valid注解
  • 全局异常处理
    创建GlobalException和GlobalExceptionHandler类进行异常处理

2、Redis实现分布式Session

  • 准备CookieUtil以及UUIDUtili工具类
  • 使用UUID为每个用户生成ticket并存储到session
    方法一:使用SpringSession实现,将session存储在Redis上
    方法二:将用户信息存入到Redis,使用cookie获取用户信息
三、功能开发

1、数据库设计

  • 创建商品表、秒杀商品表、订单表、秒杀商品订单表

2、商品列表页和商品详情页

  • 为了展示秒杀商品的详情创建GoodsVo类,包括价格、库存、起始时间和截止时间

3、秒杀功能实现

  • 在控制层判断库存、若库存数小于1返回秒杀失败页面
  • 若库存不为0,在控制层判断是否重复抢购,重复抢购返回秒杀失败页面
  • 若未重复抢购,秒杀商品表在服务层先减少库存,生成秒杀订单,再返回订单页面

4、优化登录功能

  • 创建UserArgumentResolver类实现HandlerMethodArgumentResolver接口,在每次登陆前检查cookie中是否存有token,若存在直接登录
四、系统压测
  • 系统压测使用JMeter进行测试 线程数为10000 循环10

1、Jmeter简单使用

  • WinodwS环境
    • 创建线程组:步骤:添加–> 线程(用户) --> 线程组
    • 创建HTTP请求默认值,步骤:添加–> 配置元件 --> HTTP请求默认值
    • 添加测试接口,步骤:添加 --> 取样器 --> HTTP请求
    • 查看输出结果,步骤:添加 --> 监听器 --> 聚合报告/图形结果/用表格察看结果
    • 启动即可在监听器看到对应的结果
  • Linux环境
    • 在Windows上录好jmx
    • 命令行:sh jmeter.sh -n -t XXX.jmx -l result.jtl
    • 把result.jtl导入到jmeter

2、配置不同用户测试

  • 准备配置文件config.txt
  • 添加 --> 配置元件 --> CSV Data Set Config
  • 添加 --> 配置元件 --> HTTP Cookie管理器
  • 修改HTTP请求用户信息并查看结果

3、压测秒杀接口

  • 使用UserUitl工具类往数据库插入5000用户,并且调用登录接口获取token,写入config.txt
  • 配置秒杀接口测试
  • windows优化前:QPS1700 并且存在超卖问题
五、页面优化

1、页面缓存+URL缓存+对象缓存

  • 秒杀的瓶颈在于数据库,所以要加上各种粒度的缓存,最大的是页面缓存、最小的是对象缓存

  • 页面缓存

    • 将商品列表页和商品详情页放入static目录下
    • Redis中获取页面,如果不为空,直接返回页面
    • 若缓存中没有则手动渲染,利用thymeleaf模板,然后将页面加入缓存,并返回渲染页面
    • 设置页面缓存时间为60s
  • URL缓存(商品详情页)

    • 与页面缓存步骤基本一致,但是需要取缓存和加缓存时要加入参数,GoodsId
  • 对象缓存(User对象)

    • 从redis中取出缓存
    • 若缓存不存在进入数据进行查找,并加入缓存
  • 为保证数据库与redis中的一致性,对数据库的每一次操作清空redis并更新数据

2、页面静态化+前后端分离

  • 商品详情页+订单详情页进行页面静态化

  • 使用纯html页面+Ajax请求json数据后再填充页面
    3、防超卖

  • 减库存情况

    • 解决方法:在减库存时判断库存是否足够
  • 同一用户同时秒杀多件商品情况

    • 为数据库建立唯一索引避免重复抢购,并将秒杀订单信息存入Redis,方便判断是否重复抢购时进行查询

4、CDN优化

  • CDN是内容分发网络,相当于缓存,只是部署在全国各地,当用户发起请求时,会找最近的CDN获取资源

5、总结

  • 经过页面优化以后,windos环境压测QPS为2800。
  • 并发大的瓶颈在于数据库,所以解决办法是加各种缓存:从浏览器开始,做页面的静态化,将静态页面缓存在浏览器中;请求到达网站之前可以部署一些CDN,让请求首先访问CDN;然后是页面缓存、URL缓存、对象缓存。
六、接口优化

1、RabbitMQ交换机

  • Fanout模式

    • 不处理路由键,只需要简单的将队里绑定到交换机上
      发送到交换机的消息都会被转发到与该交换机绑定的所有队列上
      Fanout交换机转发消息是最快的
  • Direct模式

  • 所有发送到Direct Exchange的消息被转发到RouteKey中指定的Queue
    注意:Direct模式可以使用RabbitMQ自带的Exchange:default Exchange,所以不需要将Exchange进行任何绑定(binding)操作,消息传递时,RouteKey必须完全匹配才会被队列接收,否 则该消息会被抛弃。
    重点:routing key与队列queues 的key保持一致,即可以路由到对应的queue中。

  • Topic模式

    • 所有发送到Topic Exchange的消息被转发到所有管线RouteKey中指定Topic的Queue上Exchange将RouteKey和某Topic进行模糊匹配,此时队列需要绑定一个Topic
      对于routing key匹配模式定义规则举例如下:
      routing key为一个句点号 . 分隔的字符串(我们将被句点号. 分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
      routing key中可以存在两种特殊字符 * 与# ,用于做模糊匹配,其中* 用于匹配一个单词, # 用
      于匹配多个单词(可以是零个)
  • Headers模式

    • 不依赖routingkey,使用发送消息时basicProperties对象中的headers匹配队列
      headers是一个键值对类型,键值对的值可以是任何类型
      在队列绑定交换机时用x-match来指定,all代表定义的多个键值对都要满足,any则代表只要满足 一个可以了

2、优化思路

  • 核心为减少数据库的访问次数
  • 系统初始化,将商品的库存数量加载到Redis中
  • 收到请求,Redis预减库存。库存不足,直接返回,否则进入第三步
  • 请求入队,立即返回排队中(异步下单)
  • 请求出队,生成订单,减少库存,将订单写入Redis
  • 客户端轮询,是否秒杀成功

3、Redis操作库存减少数据库访问

  • SeckillController继承InitializingBean实现afterPropertiesSet方法把商品库存加载到Redis
  • Redis预减库存并判断库存
    • 优化1: 在预减库存中进行内存标记,使用一个Map,将商品ID设置为false,当买空时,设为true;然后每次不是直接访问Redis进行库存查询,而是对商品ID进行条件判断
    • 优化2:使用lua脚本为Redis操作添加分布式锁

4、rabbitMQ异步流量削峰-Topic模式

  • 在Redis发出库存减少请求后,将请求放入消息队列,判断完毕后进行下单操作
七、安全优化

1、秒杀接口地址隐藏

  • 虽然前端页面在秒杀未开始时秒杀按钮设置为不可用,但是有可能用户通过前端js代码找到秒杀地址在秒杀未开始时直接访问,秒杀接口隐藏的目的是用户通过js获取到的秒杀地址并不能让其完成秒杀功能
  • 步骤
    1. 秒杀开始之前使用MD5加密并使用UUID生成唯一值存入Redis
    2. 秒杀时从Redis取出地址值进行验证,通过后执行秒杀

2、数学公式验证码

  • 接口防刷,并分散用户的请求降低并发。
  • 步骤
    1. 秒杀开始前在控制层生成验证码并存入Redis中
    2. 秒杀时从Redis取出验证码进行验证,通过后执行秒杀

3、接口限流

  • 简单限流-计数器算法(漏桶算法 、令牌桶算法)
  • 步骤
    1. 自定义@AccessLimit注解用于接口限流
    2. 当一个用户访问接口时,把访问次数写入缓存,并设置有效期
    3. 一分钟之内如果用户访问,则缓存中的访问次数加一,如果次数超限进行限流操作
    4. 如果一分钟内没有超限,缓存中数据消失,下次再访问时重新写入缓存
    5. 自定义拦截器继承HandlerInterceptorAdapter类将每次请求进行拦截
    6. 在控制层为秒杀接口加上注解
八、项目可改进的地方
  1. 改进限流算法
  • 将计数器限流算法改为漏桶/令牌桶算法进行限流
  1. 超发问题
  • 经压测,当并发量足够大时可能会出现超发问题
  1. 改进优化策略
  2. 为项目增加更多功能如支付、购物车等,并考虑使用微服务进行项目升级
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值