秒杀模块的实现

需求分析

所谓“秒杀”,就是网络卖家发布一些超低价格的商品,所有买家在同一时间网上抢购的一种销售方式。
秒杀商品通常有两种限制:库存限制、时间限制。

(1)商品详细页显示秒杀商品信息,点击立即抢购实现秒杀下单,下单时扣减库存。当库存为0或不在活动期范围内时无法秒杀。
(2)秒杀下单成功,直接跳转到支付页面(支付宝扫码),支付成功,跳转到成功页,填写收货地址、电话、收件人等信息,完成订单。
(3)当用户秒杀下单5分钟内未支付,取消预订单,调用支付宝支付的关闭订单接口,恢复库存。

秒杀技术实现核心思想是运用缓存减少数据库瞬间的访问压力!读取商品详细信息时运用缓存,当用户点击抢购时减少缓存中的库存数量,当库存数为0时或活动期结束时,同步到数据库。产生的秒杀预订单也不会立刻写到数据库中,而是先写到缓存,当用户付款成功后再写入数据库。

实现

秒杀商品的查询

  • 判断redis是否为空,如果不为空,就从redis中查询
  • 如果为空就从数据库中查询并将查询结果存储到redis中
public List<TbSeckillGoods> findList() {
    List<TbSeckillGoods> secKillGoods = redisTemplate.boundHashOps("secKillGoods").values();
    if (secKillGoods==null || secKillGoods.size()==0){
        TbSeckillGoodsExample example = new TbSeckillGoodsExample();
        TbSeckillGoodsExample.Criteria criteria = example.createCriteria();
        criteria.andStatusEqualTo("1");
        //当前时间大于开始时间,小于结束时间
        criteria.andStartTimeLessThan(new Date());
        criteria.andEndTimeGreaterThan(new Date());
        //库存限制
        criteria.andStockCountGreaterThan(0);

        secKillGoods = seckillMapper.selectByExample(example);

        //存入redis中
        for (TbSeckillGoods secKillGood : secKillGoods) {
            redisTemplate.boundHashOps("secKillGoods").put(secKillGood.getId(),secKillGood);
        }
    }
    return secKillGoods;
}

秒杀订单的创建

  • 订单在redis中存储的大键名任意,每个小键为用户id,值为订单的list集合
  • 同一个商品一个用户只能秒杀一件
  • 判断秒杀商品已经从秒杀完从redis中删除和秒杀商品数量小于等于0的情况(在高并发情况下,库存数量是有可能为负的)
  • 扣减库存并更新到redis中
  • 当库存扣减为0的时候,从缓存中移除 该秒杀商品, 同时将数据同步到 mysql中
  • 将秒杀订单保存在redis中
public Long submitOrder(Long seckillId, String userId) {
    //从redis中查询出用户的秒杀订单
    List<TbSeckillOrder> OrderList = (List<TbSeckillOrder>) redisTemplate.boundHashOps("secKillOrders").get(userId);
    if (OrderList==null){
        OrderList = new ArrayList<>();
    }
    else {
        for (TbSeckillOrder secKillorder : OrderList) {
            if (secKillorder.getSeckillId().longValue() == seckillId.longValue()){//一个商品一个用户只能秒杀一次
                throw new RuntimeException("你已经成功秒杀到了该商品!");
            }
        }
    }
    //从redis中获取当前秒杀商品的数据,判断该商品是否还能购买
    TbSeckillGoods seckillGood = (TbSeckillGoods) redisTemplate.boundHashOps("secKillGoods").get(seckillId);
    //判断秒杀商品已经从秒杀完从redis中删除和秒杀商品数量小于等于0的情况(在高并发情况下,库存数量是有可能为负的)
    if (seckillGood==null || seckillGood.getStockCount()<=0){
        throw new RuntimeException("很遗憾,该商品已经抢完,谢谢参与");
    }
    //扣减库存
    seckillGood.setStockCount(seckillGood.getStockCount()-1);
    redisTemplate.boundHashOps("secKillGoods").put(seckillId,seckillGood);
    // 当库存扣减为0的时候,从缓存中移除 该秒杀商品, 同时将数据同步到 mysql中
    if (seckillGood.getStockCount()==0){
        redisTemplate.boundHashOps("secKillGoods").delete(seckillId);
        seckillGoodsMapper.updateByPrimaryKey(seckillGood);
    }
    //将秒杀订单保存在redis中
    TbSeckillOrder seckillOrder = new TbSeckillOrder();
    long orderId = idWorker.nextId();

    seckillOrder.setId(orderId);
    seckillOrder.setSeckillId(seckillId);
    seckillOrder.setMoney(seckillGood.getCostPrice());
    seckillOrder.setUserId(userId);
    seckillOrder.setSellerId(seckillGood.getSellerId());
    seckillOrder.setCreateTime(new Date());
    // 未付款
    seckillOrder.setStatus("0");
    //将订单加入用户的秒杀订单表
    OrderList.add(seckillOrder);
    redisTemplate.boundHashOps("secKillOrders").put(userId,OrderList);
    //返回订单id
    return orderId;
}

高并发压力测试

测试之前秒杀商品库存数量是10
在这里插入图片描述
在spring-security.xml文件中对测试url进行放行
在这里插入图片描述
使用jmeter进行测试,并发测试500
在这里插入图片描述
并发测试之后,库存清为0
在这里插入图片描述
在redis中查看创建订单的数量为17,发生了超卖现象
在这里插入图片描述redis分布式锁解决超卖
我们通过单节点Redis实现一个分布式锁。
利用redis在同一时刻操作一个键的值只能有一个进程的特性,如果能设值成功就获取到锁;解锁,就是删除指定的键;
为防止死锁可以设置锁超时时间,如果锁超时就释放锁。

秒杀订单支付及保存

支付只需要我们从redis中将订单号和支付金额查询出,然后调用支付方法即可
页面监听订单是否支付
如果已支付,将订单保存到mysql数据库以及更新redis数据库的信息
将方法写在业务层还有个好处是,业务层切入了事务,如果没有执行成功,可以进行回滚
在支付控制层的支付成功判断里调用更新方法
在这里插入图片描述

public void updateSecKillOrder(String userId, String out_trade_no) {
    
    TbSeckillOrder saveOrder = null;
    List<TbSeckillOrder> secOrderList = (List<TbSeckillOrder>) redisTemplate.boundHashOps("secKillOrders").get(userId);
    for (TbSeckillOrder seckillOrder : secOrderList) {
        if (out_trade_no.equals(seckillOrder.getId().longValue()+"")){
            seckillOrder.setPayTime(new Date());
            seckillOrder.setStatus("1");
            //同步到mysql
            saveOrder = seckillOrder;
        }
    }
    redisTemplate.boundHashOps("secKillOrders").put(userId,secOrderList);
  
    seckillOrderMapper.insert(saveOrder);
}

秒杀付款超时

在这里插入图片描述

public void backAndRemoveOrder(String name, String out_trade_no) {
    //还原库存
    //先查询出超时未付款订单
    TbSeckillOrder order = findSecKillOrderByUserIdAndOrderId(name, Long.valueOf(out_trade_no));
    TbSeckillGoods seckillGood = (TbSeckillGoods) redisTemplate.boundHashOps("secKillGoods").get(order.getSeckillId());
    if (seckillGood==null){
        TbSeckillGoods dbGoods = seckillGoodsMapper.selectByPrimaryKey(order.getSeckillId());
        // dbGoods可不可用就不一定了:可能在用户3分钟未付款期间,该商品到了秒杀截止时间,
        // 此时就不用再添加到 redis缓存了, 但是 数据库 的库存 应该 +1

        //过期
        if (dbGoods.getEndTime().getTime()<new Date().getTime()){
            dbGoods.setStockCount(dbGoods.getStockCount()+1);
            seckillGoodsMapper.updateByPrimaryKey(dbGoods);
        }else {
            dbGoods.setStockCount(dbGoods.getStockCount()+1);
            seckillGoodsMapper.updateByPrimaryKey(dbGoods);
            //更新redis中的数据
            redisTemplate.boundHashOps("secKillGoods").put(dbGoods.getId(),dbGoods);
        }
    }else {
        seckillGood.setStockCount(seckillGood.getStockCount()+1);
        //更新redis中的数据
        redisTemplate.boundHashOps("secKillGoods").put(seckillGood.getId(),seckillGood);
    }
    //移除未付款订单
    List<TbSeckillOrder> newList = new ArrayList<>();
    List<TbSeckillOrder> secOrderList = (List<TbSeckillOrder>) redisTemplate.boundHashOps("secKillOrders").get(name);
    for (TbSeckillOrder seckillOrder : secOrderList) {
        if (out_trade_no.equals(seckillOrder.getId().longValue()+"")){

        }else{
            newList.add(seckillOrder);
        }
    }
    redisTemplate.boundHashOps("secKillOrders").put(name,newList);
}

注意
list删除复杂对象使用remove没有效果,基本类型的数据可以删除

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值