| 关于秒杀。 如何解决这个问题。理解这个问题。 所谓“秒杀”,就是网络卖家发布一些超低价格的商品,所有买家在同一时间网上抢购的一种销售方式。通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动。由于商品价格低廉,往往一上架就被抢购一空,有时只用一秒钟。 秒杀商品通常有两种限制:库存限制、时间限制。 需求: (1)商家提交秒杀商品申请,录入秒杀商品数据,主要包括:商品标题、原价、秒杀价、商品图片、介绍等信息 (2)运营商审核秒杀申请 (3)秒杀频道首页列出秒杀商品(进行中的)点击秒杀商品图片跳转到秒杀商品详细页。 (4)商品详细页显示秒杀商品信息,点击立即抢购实现秒杀下单,下单时扣减库存。当库存为0或不在活动期范围内时无法秒杀。 (5)秒杀下单成功,直接跳转到支付页面(微信扫码),支付成功,跳转到成功页,填写收货地址、电话、收件人等信息,完成订单。 (6)当用户秒杀下单5分钟内未支付,取消预订单,调用微信支付的关闭订单接口,恢复库存。     上面保存订单的方式是先查看Redis中对应商品是否存在,如果存在且数量是否>0如果>0则下单,如果在并发情况下,如果20个人同时在执行如上查询代码这里,而此时对应商品只有一个,则会下20个单,而这20个单一定是有问题的,因为1件商品不可能同时给20个人发货。那么如何解决这种并发问题呢?我们可以用Redis队列实现。     秒杀下单优化 修改pyg-seckill-service的SeckillOrderServiceImpl.java,加入从队列中取数据校验商品是否存在的实现过程,队列中商品存在,则继续下单,否则抛出异常提示已售罄。 | /*** * 创建订单
 * @param seckillid
 * @param userid
 */
 @Override
 public void saveOrder(Long seckillid, String userid) {
 //获取队列中的商品,如果能够获取,则商品存在,可以下单
 //这样可以避免多个用户同时抢购意见商品重复下单
 Long goodsId = (Long) redisTemplate.boundListOps(SysContant.SECKILL_PREFIX + seckillid).rightPop();
 if(goodsId==null){
 throw new RuntimeException("已售罄!");
 }
 
 //获取商品详情
 SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps(SeckillGoods.class.getSimpleName()).get(seckillid);
 //略....
 }
 | 
           上面的方案解决了并发情况下下单操作异常问题,但其实际秒杀中大量并发情况下,这个下单过程是需要很长等待时间的,所以这里我们建议用异步和多线程实现,最好不要让程序处于阻塞状态,而是在用户一下单的时候确认用户是否符合下单条件,如果符合,则开启线程执行,执行完毕之后,用户等待查询结果即可。 当我们在实现秒杀功能时候需要,解决队列排队造成的阻塞相应。       创建CreateOrder.java实现订单下单操作,在面试中常常会被问及多线程应用在项目哪里,这正好是一个很好的案例。 | @Component //@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //采用原型模式
 public class CreateOrder implements Runnable {
 
 @Autowired
 private SeckillGoodsMapper seckillGoodsMapper;
 
 @Autowired
 private RedisTemplate redisTemplate;
 
 @Autowired
 private IdWorker idWorker;
 
 @Override
 public void run() {
 //从Redis中获取排队信息,然后依次下单
 OrderRecode orderRecode = (OrderRecode) redisTemplate.boundListOps(OrderRecode.class.getSimpleName()).rightPop();
 
 if(orderRecode!=null){
 //获取队列中的商品,如果能够获取,则商品存在,可以下单
 //这样可以避免多个用户同时抢购意见商品重复下单
 Long goodsId = (Long) redisTemplate.boundListOps(SysContant.SECKILL_PREFIX + orderRecode.getGoodsId()).rightPop();
 if(goodsId==null){
 //售罄
 //将用户从排队中移除,否则用户不能继续排队
 redisTemplate.boundSetOps(SysContant.SECKILL_USER).remove(orderRecode.getUserid());
 return;
 }
 
 //获取商品详情
 SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps(SeckillGoods.class.getSimpleName()).get(orderRecode.getGoodsId());
 if(seckillGoods!=null){
 //创建订单
 SeckillOrder order = new SeckillOrder();
 order.setId(idWorker.nextId());
 order.setSeckillId(orderRecode.getGoodsId());
 order.setMoney(seckillGoods.getCostPrice());    //秒杀价格
 order.setUserId(orderRecode.getUserid());
 order.setSellerId(seckillGoods.getSellerId());
 order.setCreateTime(new Date());
 order.setStatus("0");   //0未支付,1已支付
 //订单数据存入Reids
 redisTemplate.boundHashOps(SeckillOrder.class.getSimpleName()).put(orderRecode.getUserid(),order);
 
 //Reids数据递减    ----    当库存为1的时候。有并发   加个同步锁。
 seckillGoods.setStockCount(seckillGoods.getStockCount()-1);
 if(seckillGoods.getStockCount()<=0){
 //商品卖完,同步数据库
 seckillGoodsMapper.updateByPrimaryKeySelective(seckillGoods);
 
 //清除缓存
 redisTemplate.boundHashOps(SeckillGoods.class.getSimpleName()).delete(seckillGoods.getId());
 }else{
 //Redis库存递减同步
 redisTemplate.boundHashOps(SeckillGoods.class.getSimpleName()).put(seckillGoods.getId(),seckillGoods);
 }
 
 //抢购人数削减
 redisTemplate.boundValueOps(SysContant.SECKILL_COUNT_GOODSID_PREFIX+orderRecode.getGoodsId()).increment(-1);
 }
 }
 }
 }
 | 
  1.1.  配置线程池 下单保存修改| <!-- 线程池配置
 -->
 <bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" id="executor">
 <!-- 核心线程数,默认为1-->
 <property name="corePoolSize" value="10" />
 <!--最大线程数,默认为Integer.MAX_VALUE-->
 <property name="maxPoolSize" value="50" />
 <!--队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE-->
 <property name="queueCapacity" value="10000" />
 <!--线程池维护线程所允许的空闲时间,默认为60s-->
 <property name="keepAliveSeconds" value="300" />
 <!--线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者-->
 <property name="rejectedExecutionHandler">
 <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
 </property>
 </bean>
 | 
 修改SeckillOrderServiceImpl.java,把之前实现的保存订单修改成启动线程调用 | @Autowired private RedisTemplate redisTemplate;
 
 @Autowired
 private CreateOrder createOrder;
 
 @Autowired
 private ThreadPoolTaskExecutor executor;
 /***
 * 创建订单
 * @param seckillid
 * @param userid
 */
 @Override
 public void saveOrder(Long seckillid, String userid) {
 //获取队列中的商品,如果能够获取,则商品存在,可以下单
 //这样可以避免多个用户同时抢购意见商品重复下单
 
 //获取商品详情
 SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps(SeckillGoods.class.getSimpleName()).get(seckillid);
 if(seckillGoods==null || seckillGoods.getStockCount()<=0){
 throw new RuntimeException("已售罄!");
 }
 
 //判断用户是否正在排队
 Boolean member = redisTemplate.boundSetOps(SysContant.SECKILL_USER).isMember(userid);
 if(member){
 //判断用户是否下单
 Object order = redisTemplate.boundHashOps(SeckillOrder.class.getSimpleName()).get(userid);
 if(order!=null){
 throw new RuntimeException("您有未支付的订单!");
 }
 throw new RuntimeException("正在排队中!");
 }
 
 //当前商品秒杀的人数
 Long seckillusercount = redisTemplate.boundValueOps(SysContant.SECKILL_COUNT_GOODSID_PREFIX + seckillid).increment(0);
 
 //当前商品的剩余个数
 if((seckillGoods.getStockCount().intValue()+200)<=seckillusercount){
 //超过限制,这里的200可以自定义
 throw new RuntimeException("当前商品抢购人数过多");
 }
 
 //下单数据加入缓存
 OrderRecode orderRecode = new OrderRecode(seckillid, userid);
 redisTemplate.boundListOps(OrderRecode.class.getSimpleName()).leftPush(orderRecode);
 
 //排队记录加入缓存
 redisTemplate.boundSetOps(SysContant.SECKILL_USER).add(userid);
 
 //抢购人数记录
 redisTemplate.boundValueOps(SysContant.SECKILL_COUNT_GOODSID_PREFIX+seckillid).increment(1);
 
 //调用线程执行
 executor.execute(createOrder);
 }
 | 
       以下是模拟1000个并发。测试的结果图,说明此案例测试成功。 
 
 |