压力测试一:只使用mysql的压力测试
测试商品列表接口结果:一秒只支持147.7线程


测试秒杀接口时,QPS与之前差不多,但是出现了超卖现象。说明单用户的判断逻辑在多用户场景下不适用,测试模拟了5000个用户同时秒杀一个10库存的商品,最终超卖了13个,这在更大规模用户秒杀商品时几乎是致命错误。
优化项目
优化一:页面缓存+URL缓存+对象缓存
页面缓存
- 取缓存
- 如果缓存不存在,手动渲染模板
- 结果输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @RequestMapping(value="/to_list",produces="text/html") @ResponseBody public String list(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user) { model.addAttribute("user", user);
String html = redisService.get(GoodsKey.getGoodsList,"",String.class) if(!StringUtils.isEmpty(html)){ return html; }
List<GoodsVo> goodsList = goodsService.listGoodsVo(); model.addAttribute("goodsList", goodsList);
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); } return html; }
|
此处遇到问题:Sping5中SpringWebContext方法过时
因为在thymeleaf.spring5的API中把大部分的功能移到了IWebContext下面,用来区分边界。剔除了ApplicationContext 过多的依赖,现在thymeleaf渲染不再过多依赖spring容器
调用这个即可
1 2
| IWebContext ctx =new WebContext(request,response, request.getServletContext(),request.getLocale(),model.asMap());
|
优化二:页面静态化,前后端分离
弃用thymeleaf,使用原生Html
采用静态化页面
1 2 3 4 5 6 7 8 9
|
spring.sources.add-mappings=true spring.sources.cache-period= 3600 spring.sources.chain.cache=true spring.sources.chain.enabled=true spring.sources.chain.gzipped=true spring.sources.chain.html-application-cache=true spring.sources.static-locations=classpath:/static/
|
解决超卖:
1、数据库代码修改
原代码
1 2
| @Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId}") public int reduceStock(MiaoshaGoods g);
|
加上判断条件 and stock_count > 0,修改后
1 2
| @Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0") public int reduceStock(MiaoshaGoods g);
|
但是这样修改后,仍然不能防止一个用户秒杀到两个商品的情况:一个用户同时发起两个请求并且同时秒杀到商品的情况。和一个用户不能秒杀两个商品的规则相违背
2、所以需要利用数据库的索引,利用唯一索引防止用户插入重复数据,数据库添加索引如下:

以上两步可以完全杜绝商品卖超的情况
总结:1、数据库加唯一索引,防止用户重复购买。2、SQL加库存数量判断:防止库存变成负数。
优化三:静态资源优化
1、JS/CSS压缩,减少流量
2、多个JS/CSS组合,减少连接数
tengine.taobao.org
优化四:CDN优化
CDN就近访问
优化五:接口优化
主要工作:
1、Redis预减库存减少数据库访问
2、内存标记减少Redis访问
3、请求先入队缓冲,异步下单,增强用户体验
思路:减少数据库访问
1、系统初始化,把商品库存数量加载到Redis
2、收到请求,Redis预减库存,库存不足,直接返回,否则进入3
3、请求入队,立即返回排队中
4、请求出队,生成订单,减少库存
5、客户端轮询,是否秒杀成功
使用rabbitmq
引入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
|
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| spring.rabbitmq.host=121.4.60.79 spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.virtual-host=/
spring.rabbitmq.listener.simple.concurrency= 10 spring.rabbitmq.listener.simple.max-concurrency= 10
spring.rabbitmq.listener.simple.prefetch= 1
spring.rabbitmq.listener.simple.auto-startup=true
spring.rabbitmq.listener.simple.default-requeue-rejected= true
spring.rabbitmq.template.retry.enabled=true spring.rabbitmq.template.retry.initial-interval=1000 spring.rabbitmq.template.retry.max-attempts=3 spring.rabbitmq.template.retry.max-interval=10000
spring.rabbitmq.template.retry.multiplier=1.0
|