解决超卖问题

  • 一. 在SQL加上判断库存防止库存为负数
  • 二. 数据库加唯一索引防止用户重复购买
  • 三. Redis预减库存减少数据库访问,内存标记减少Redis访问
  • 三. 悲观锁 加同步代码块 效率低
  • 四. 乐观锁 Version版本 效率高


一. 在SQL加上判断库存防止库存为负数

可以简单的解决超卖的情况,但不能完全避免

redis秒杀如何解决超卖 redis如何解决秒杀超卖java_悲观锁

public interface MiaoshaGoodsMapper extends Mapper<MiaoshaGoods> {
    //更新数据库中减库存1  并且stock_count>0
    @Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
    public int reduceStockById(@Param("goodsId") Long goodsId);
}

二. 数据库加唯一索引防止用户重复购买

redis秒杀如何解决超卖 redis如何解决秒杀超卖java_Redis_02

redis秒杀如何解决超卖 redis如何解决秒杀超卖java_乐观锁_03

三. Redis预减库存减少数据库访问,内存标记减少Redis访问

  • 利用redis的单线程预减库存。比如商品有100件。那么我在Redis存储一个K,V。例如 <gs1001, 100>,每一个用户线程进来,key值就减1,等减到0的时候,全部拒绝剩下的请求。那么也就是只有100个线程会进入到后续操作。所以一定不会出现超卖的现象

redis秒杀如何解决超卖 redis如何解决秒杀超卖java_Redis_04

// 商品结束 秒杀标记 map
    private Map<Long, Boolean> localOverMap = new HashMap<>();
        // ④.预减库存 redis库存减一,返回剩余库存
        Long stock = redisService.decr(GoodsPrefix.goodsMiaoshaStock, "" + goodsId);
        if (stock < 0) {
            //剩余库存秒杀光了 将标记设置为true
            localOverMap.put(goodsId, true);
            //秒杀结束
            return ResultUtil.error(ResultEnum.MIAOSHA_OVER);
        }

三. 悲观锁 加同步代码块 效率低

四. 乐观锁 Version版本 效率高

  • 这种方式采用了版本号的方式,其实也就是CAS的原理。

redis秒杀如何解决超卖 redis如何解决秒杀超卖java_悲观锁_05

  • 悲观锁虽然可以解决超卖问题,但是加锁的时间可能会很长,会长时间的限制其他用户的访问,导致很多请求等待锁,卡死在这里,如果这种请求很多就会耗尽连接,系统出现异常。乐观锁默认不加锁,更失败就直接返回抢购失败,可以承受较高并发

redis秒杀如何解决超卖 redis如何解决秒杀超卖java_悲观锁_06

@Override
public int createOptimisticOrder(int sid) throws Exception {
    // 校验库存
    Stock stock = checkStock(sid);
    // 乐观锁更新
    saleStockOptimstic(stock);
    // 创建订单
    int id = createOrder(stock);
    return id;
}
// 乐观锁 Mapper 文件
@Update("UPDATE stock SET count = count - 1, sale = sale + 1, version = version + 1 WHERE " +
        "id = #{id, jdbcType = INTEGER} AND version = #{version, jdbcType = INTEGER}")