背景

商品的库存为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数据节点修改回调事件异常");
                }
            }
    }
}

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐