针对秒杀项目做的一些优化

Posted 赵jc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了针对秒杀项目做的一些优化相关的知识,希望对你有一定的参考价值。

秒杀


做了一个秒杀项目,并对其做了一定的优化!

业务逻辑

数据库的设计

  • 为什么将秒杀的商品单独建一张表(秒杀表)而不再商品表添加一个字段来判断呢,因为今天可能是秒杀,明天可能是9.9包邮,这样的话导致商品表越来越难以维护

一些全局配置

  • 全局异常处理

  • 错误信息描述

JSR303参数验证(手机号)

使用JSR对参数进行验证(这里以手机号为例)



登录

  • 用ThreadLocal存储用户的信息,多线程情况下保证用户的安全(收到请求,并且到相应完成都是一个请求(一个线程)所以使用ThreadLocal)

  • 商品页、商品详情页 需要判断session状态所以我们实现一个ArgumentResolver(controller会带很多参数(Respone、Request),框架会回调这个ArgumentResolvers方法,遍历controller的参数并进行赋值)

  • 进行登录验证,有时候手机端token并不是放在cookie中传递,而直接放在参数中传递(兼容性)

  • 秒杀场景肯定不可能一个服务器:第一个用户session放在第一个服务器,用户第二次登录session放在了第二台服务器,此时导致session失效,所以需要用一个redis单独管理我们的session(分布式session)


  • 但容器当中的session过期规则是最后一次的访问时间加上过期时间,所以我们需要重新设置cookie

秒杀

秒杀主要分为以下几步

第一步:判断商品库存是否充足
第二步:判断是否已经秒杀到了

以下三步属于原子操作,需要放在一个事务中进行
第三步:减库存
第四步:下订单
第五步:写入秒杀订单

关于超卖问题的解决

  • 数据库加唯一索引:防止用户重复购买

  • SQL加库存数量判断:防止库存变为负数

优化

优化前QPS:5000x10 1306
优化后QPS:5000x10 2260
优化的核心思路便是:减少对数据的访问
这里没有设计数据库的分库分表。

缓存优化

Redis的封装

  • 使用JedisPool池化技术方便多次使用,并对Redis进行封装,方便使用

  • redis一个key获取一个value,只用string来当key的话不利于维护,所以引入了对应的前缀keyPrefix,每个模块对应自己的keyPrefix方便管理,通过自己模块的变量不同来进行区分


页面缓存

将整个页面在Redis中缓存,减少对数据库的查询和页面的渲染次数

  • 取缓存

  • 手动渲染模板(使用ThymeleafViewResolver来帮助我们手动渲染页面)

  • 结果输出

对象缓存

将秒杀用户的对象缓存起来,方便使用

客户端的缓存(页面静态化+前后端分离)

不再使用thymeleaf,而使用AJAX和jQuery(高级的有vue.js)


接口优化

用rabbitmq将同步下单改为异步下单

  • 系统初始化,把商品库存数量加载到Redis中(实现InitializingBean接口,重写afterPropertiesSet将商品的库存加载到Redis缓存中)

  • 收到请求,Redis预减库存,库存不足,直接返回,否则进入队列(但如果一次性来很多请求,假如库存有10个,但一次性来了100个请求,11以后的请求都回去预减库存,但此时库存已经为0了,所以加了一个localGoodsOverMap来做内存标记减少对redis的访问)



  • 请求入队,立即返回排队中,此时并不是秒杀成功(就像12306抢票,正在抢票中,请等待,并不表示一定会成功,也有可能失败)

  • 请求出队,生成订单,减少库存(核心:异步下单)在receive当中真正去判断库存、创建订单


  • 客户端轮询,是否秒杀成功(与上一步是并行操作)

安全方面

明文密码两次MD5处理

因为使用的是http协议,所以在网络传输时有一定的安全问题,所以使用了MD5加密的方式,但现在有很多MD5反推到工具,所以使用了两次MD5+salt(密匙)的方式来进行加密。

  • 第一步:客户端存储一个固定的salt,用户输入密码后,用salt和MD5加密后的密码用form表单的方式传送给后端
  • 第二步:后端拿到客户端传来的密码后,在使用用户自己填的salt和MD5进行二次加密
  • 将两次加密后的密码存储到数据库中


秒杀接口地址隐藏

思路:秒杀开始前,先去请求接口获取秒杀地址

  • 前端先通过后端获取一个任意的字符串来当做path(createSeckkillPath),之后的秒杀请求都需要带上这个path参数


  • 改造秒杀接口(带上pathvariable参数),后端检查前端的path和之前返回给前端的path是否相同,如果相同才执行以下的逻辑

接口限流

使用了注解加拦截器的方式来限制访问次数

以上是关于针对秒杀项目做的一些优化的主要内容,如果未能解决你的问题,请参考以下文章

秒杀系统架构优化思路

58沈剑:秒杀系统架构优化思路

秒杀系统设计五-如何提升性能

如何设计一个秒杀系统----学习总结

我该如何设计秒杀系统? | 张志君

谈如何设计一个秒杀系统(重点)