先从我们最原始的秒杀结构开始。 设置一个商品表:id 商品名 价格 库存 介绍 设置一个订单表:订单id 商品id 库存
以下是我们用户在点击购买时生成订单的过程: 下单接口通过传入商品id执行下单操作。
1.先查出商品库存,如果库存少于0则返回“商品库存已售完”。 2.库存不为0,创建秒杀订单。 3.最后再在数据库中进行库存减1。 开始秒杀相关优化:
乐观锁(状态控制) 在我们的减库存sql语句不能再写传统的基础写法,这里要使用乐观锁来保证并发场景下的线程安全!(状态控制:数据库控制的行锁,不会出现超卖的bug) update product set stock=stock-1 where id = #{id} and stock > 0 此时我们先通过jmeter进行测压(数据10000条购买信息,库存100) 测试结果吞吐量:330qps 2. 使用缓存 1.初始化缓存,在我们系统加载完就将我们的库存全部加载到redis中。 2.创建订单前先走缓存redis,判断商品库存是否足够,商品够则进行订单创建,不够直接返回“商品已售完”。 * 此时我们先通过jmeter进行测压(数据10000条购买信息,库存100) 测试结果吞吐量:1615qps 注意: 这里代码还存在问题 1.当我们的订单出现异常时说明订单创建失败,此时库存应该还原(加1库存,放置少卖) 2.因为异常出现库存要加一,所以我们库存最后一件卖出后程序会将redis的库村数置0,所以当卖出最后一件时我们应该吧redis库存还原为1 此时再进行测压:(因为操作流程更多吞吐量略微减少) 测试结果吞吐量:999qps 3. 抵挡不必要的信息进入缓存 我们这里设置的并发量是10000,但是我们的库存只有100.所以只有100个请求进入redis是有效的,所以我们可以设置参数来抵挡无用请求进入redis 1.通过concerrenthashmap设置一个参数来判断当前商品是否售卖完 泛型内容为:商品id,是否售卖完。 2.这里我们已经可初步通过map来判断是否还需要进入redis判断,同样这里存在一个小问题就在,如果前面有订单失败,库存加1,此时应该修改map让新的请求可以访问redis!在catch中如果出异常就删除掉标记 此时再进行测压: 测试结果吞吐量:2100qps 注意: redis是单线程操作不会出现线程安全问题,同样支持分布式操作,但是con’cerrenthashmap虽然是线程安全但是没办法支持分布式操作。4. 使用zookeeper实现分布式操作 具体实现就是将我们的map集合在put操作的时候同时加入一份到zookeeper中。 zookeeper可以通过监听器被所有web应用实例监听到。 具体实现就不进行展示了。
