目录
秒杀系统
秒杀过程:
用户登录—>点击秒杀—->成功,则减库存,下订单—->失败,则返回
但因为秒杀的时候,系统瞬间访问量很大,对数据库和服务器都有很大的访问压力
优化思路
页面优化
- 页面缓存+URL缓存+对象缓存
- 页面静态化,前后端分离
- 静态资源优化
- CDN优化
接口优化
- Redis预减库存减少数据库访问
- 内存标记减少 Redis访问
- RabbitMQ队列缓冲,异步下单,增强用户体验
- 访问 Nginx水平扩展
安全优化
- 秒杀地址隐藏
- 数学公式验证码
- 接口防刷
思路实现
对象缓存
可以在第一次访问对象,比如user的时候就放入Redis以后再访问接口的时候,直接从Redis中取出- 即可
页面缓存
利用SpringWebContent
和ThymeleafViewResolver
进行手动渲染,同样的放入Redis缓存中
SpringWebContext ctx = new SpringWebContext(request,response,
request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
//手动渲染
html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
if(!StringUtils.isEmpty(html)) {
redisService.set(GoodsKey.getGoodsList, "", html);
}
页面静态化
利用ajax,调用后端接口,前后端交互通过JSON
进行,前端页面则抽出页面
spring.resources.add-mappings=true
spring.resources.cache-period= 3600
spring.resources.chain.cache=true
spring.resources.chain.enabled=true
spring.resources.chain.gzipped=true
spring.resources.chain.html-application-cache=true
spring.resources.static-locations=classpath:/static/
静态资源优化
- JS/CSS 压缩,减少流量
- 多个JS/CSS组合,减少连接数
- CDN就近访问
内存标记减少Redis的访问
// 通过实现InitializingBean,一开始启动的时候就把商品信息放入Redis中,当进行减库存的时候,如果库存小于0,则把对应goodsId的值改为true
public void afterPropertiesSet() throws Exception {
List<GoodsVo> goodsList = goodsService.listGoodsVo();
if(goodsList == null) {
return;
}
for(GoodsVo goods : goodsList) {
redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());
localOverMap.put(goods.getId(), false);
}
}
RabbitMQ进行异步下单
// 整个流程就变为了
1. 预先将要秒杀的商品的库存加入redis中,GoodKey. (但每次访问数据库还可以再进行一次优化,将物品设置个状态位,是否可用)
2. 访问的时候,如果查询到redis库存的信息小于0,则直接返回错误
3. 判断是否已经秒杀到了,如果秒杀到了,则说明之前抢到了,不能进行重复提交
4. 没有秒杀到 构造message,放入MQ队列中,
MQ的处理逻辑
1. 创建消息的发送者,和接收者
发送者逻辑:
1. 对用户和秒杀物品进行发送,顺便打个Log
接收者逻辑:
1. 得到物品id,用户,判断是否在redis中存在商品,数据库中库存是否足够,以及是否秒杀到了
2. 减库存数据库是否成功,成功则创建一个order订单,否则让redis中的goodsid的状态位设置为禁用
// 客户端进行轮询,判断是否秒杀成功
也就是数据库中是否有用户的订单,3个状态:排队中,成功,失败
没有则再判断Redis中的内存标记
超卖问题
- 数据库加唯一索引:防止用户重复购买
- SQL加库存数量判断:防止库存变成负数
秒杀地址隐藏
思路:
秒杀开始之前,先去请求接口获取秒杀地址
- 接口改造,带上 PathVariable参数
- 添加生成地址的接口
秒杀收到请求,先验证 PathVariable
涉及path请求路径,先得到接口的请求路径 首先判断用户是否登录了(useragrument参数检验) 判断验证码是否正确 创建秒杀的地址 UUID+salt的方式取创建,并存入redis中 返回地址 再去请求真正的地址 /{path}/do_miaosha 同样是判断用户是否登录了 然后验证path(去redis中进行验证) 内存标记,减少redis1访问 预减库存(incr) < 0 则报错 ..