商品的库存为200个
实验目的: 200个商品只能生成200个订单 抵抗高并发高流量
如果最终实验结果跟理论值不一致则视为bug
一致的话我们再逐步去优化,让系统可以抵挡高并发,系统的吞吐量有明显的提升
首先我们 不加锁 不加缓存 不加事务
@PostMapping("/{productId}") public AjaxResult seckill(@PathVariable("productId") Integer productId){ try{ orderService.Seckill(productId); }catch (Exception e){ log.error("创建订单失败"+e); return AjaxResult.error("创建订单失败"); } return AjaxResult.success(); }
public void Seckill(Integer productId){ Product product = productService.getProductById(productId); if(product.getStock()<=0){ throw new RuntimeException("商品库存已售完"); } Order order = new Order(); order.setProductId(productId); order.setAmount(product.getPrice()); int i = orderMapper.insertSelective(order); //减库存 int updateNum = productService.deductProductStock(productId); log.info("我是更新后的库存"+updateNum+"=============================="); if(updateNum<=0){ throw new RuntimeException("商品库存已售罄111"); } }
我们可以看到运行完 系统的吞吐量是
商品的库存也变为0
然而订单表却生成了300多个订单 超卖了
很明显这不符合最终的效果 而且还是很大的一个bug(致命性的) 现在我们加上事务
@Transactional public void Seckill(Integer productId){ Product product = productService.getProductById(productId); if(product.getStock()<=0){ throw new RuntimeException("商品库存已售完"); } Order order = new Order(); order.setProductId(productId); order.setAmount(product.getPrice()); int i = orderMapper.insertSelective(order); //减库存 int updateNum = productService.deductProductStock(productId); log.info("我是更新后的库存"+updateNum+"=============================="); if(updateNum<=0){ throw new RuntimeException("商品库存已售罄111"); } }
加上事务之后我们可以看到吞吐量明显下降 为300多
我们在来看订单表 200个订单没有超卖 这实现了理论上的效果 但是300多的吞吐量是不能抵挡高并发的
我们继续来优化
现在我们加上锁
@PostMapping("/{productId}") public AjaxResult seckill(@PathVariable("productId") Integer productId){ if(productSoldOutMap.get(productId) != null){ log.error("商品已售罄"); return AjaxResult.error("商品已售完"); } Long stock = stringRedisTemplate.opsForValue().decrement(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId); if(stock<0){ productSoldOutMap.put(productId,true); Long increment = stringRedisTemplate.opsForValue().increment(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId); log.info("=======================stock"+increment); return AjaxResult.error("商品已售完"); } try{ orderService.Seckill(productId); }catch (Exception e){ 创建订单少卖 还原库存 stringRedisTemplate.opsForValue().increment(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId); //修改标识 if(productSoldOutMap.get(productId) != null){ productSoldOutMap.remove(productId); } log.error("创建订单失败"+e); return AjaxResult.error("创建订单失败"); } return AjaxResult.success(); }
我们现在来看看吞吐量达到了4000多
我们再看看生成的订单
生成了200个订单是没有问题的
接下来我们配上负载均衡2台
发现吞吐量不是提升不是很明显
因为用了ConcurrentHashMap来存放是否售完的标记 所以需要使用ZooKeeper在分布式环境中同步变量
上ZooKeeper
@PostMapping("/{productId}") public AjaxResult seckill(@PathVariable("productId") Integer productId) throws InterruptedException, KeeperException { if(productSoldOutMap.get(productId) != null){ log.error("商品已售罄"); return AjaxResult.error("商品已售完"); } Long stock = stringRedisTemplate.opsForValue().decrement(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId); String zkSoldOutProductPath = Constants.getZKSoldOutProductPath(productId); if(stock<0){ productSoldOutMap.put(productId,true); log.error("=====================商品售{}完标记",productId); Long increment = stringRedisTemplate.opsForValue().increment(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId); log.info("=======================stock"+increment); if(zooKeeper.exists(zkSoldOutProductPath,true)==null){ zooKeeper.create(zkSoldOutProductPath,"true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); } //监听zk售完标记节点 zooKeeper.exists(zkSoldOutProductPath,true); return AjaxResult.error("商品已售完"); } try{ orderService.Seckill(productId); }catch (Exception e){ // 创建订单少卖 还原库存 stringRedisTemplate.opsForValue().increment(Constants.REDIS_PRODUCT_STOCK_PREFIX + productId); //修改标识 if(productSoldOutMap.get(productId) != null){ productSoldOutMap.remove(productId); } if(zooKeeper.exists(zkSoldOutProductPath,true)!=null){ zooKeeper.setData(Constants.getZKSoldOutProductPath(productId),"false".getBytes(),-1); } log.error("创建订单失败"+e); return AjaxResult.error("创建订单失败"); } return AjaxResult.success(); }
@Configuration @Slf4j public class ZooKeeperWatcher implements Watcher, ApplicationContextAware { private static ApplicationContext applicationContext; private ZooKeeper zooKeeper; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ZooKeeperWatcher.applicationContext = applicationContext; } @Override public void process(WatchedEvent event) { if (Event.EventType.None == event.getType() && null == event.getPath()) { log.info("=====================zookeeper连接成功"); if(zooKeeper==null){ zooKeeper = applicationContext.getBean(ZooKeeper.class); } try{ if(zooKeeper.exists(Constants.ZK_PRODUCT_SOLD_OUT_FLAG,false)==null){ zooKeeper.create(Constants.ZK_PRODUCT_SOLD_OUT_FLAG,"".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); } }catch (Exception e){ e.printStackTrace(); } } else if (event.getType() == Event.EventType.NodeDataChanged) { //zk目录节点数据变化通知事件 try { String path = event.getPath(); String soldOutFlag = new String(zooKeeper.getData(path,true,new Stat())); log.info("zookeeper数据节点修改变动,path={},value={}",path,soldOutFlag); if("false".equals(soldOutFlag)){ String productId = path.substring(path.lastIndexOf("/")+1,path.length()); OrderController.getProductsoldOutMap().remove(productId); } } catch (Exception e) { log.error("zookeeper数据节点修改回调事件异常"); } } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)