python 如何解决高并发下的库存问题

本文介绍了一种常见的并发问题——库存超卖,并通过乐观锁和悲观锁两种技术手段来解决这一问题。详细解释了这两种锁的工作原理及其适用场景。

        一个简单的使用场景:一件商品的库存只有5件,同时A用户买了5个,B用户买了5个,都提交数据,照成库存不足的问题。

        逻辑:根据一般电商商品的模型类,生成订单一般包括订单类(Order)和订单详情类(DetailOrder),这两张表根据外键order_id 进行关联,所以是同生共死的关系,所以我们在这里用事务来控制。那么python如何解决库存问题呢?

        python 提供了2种方法解决该问题的问题:1,悲观锁;2,乐观锁

        悲观锁:在查询商品储存的时候加锁 select_for_update()  在发生事务的commit或者是事务的rollback时,自动释放该锁,这样其他用户就可以接着查询该商品。

        乐观锁:乐观锁不是真正的锁,在创建订单之前查询商品的库存,在创建订单详情表前,update更新查询数据,如果两次查询的库存量一样就创建详情表,并减去库存,否则,循环三次,如果都不一样,就发生rollback。

        使用场景:并发量高的时候使用悲观锁,缺点:加锁消耗资源

                       并发量低的时候使用乐观锁,缺点:乐观锁循环耗费时间。

代码:

悲观锁:

 @transaction.atomic
    def post(self, request):
        """接受数据  修改购物车数据 添加订单 和订单商品 加悲观锁 解决并发问题"""
        sku_ids = request.POST.get('sku_ids')
        addr_id =request.POST.get('addr_id')
        pay_method = request.POST.get('pay_method')
        transit_price = request.POST.get('transit_price')
        user = request.user
        if not user.is_authenticated():  # 判断是否登陆
            return JsonResponse({'res': 0, 'errmsg': '用户未登录,请先登陆'})
        try:  # 判断地址是否存在
            address = Customer.objects.get(id=addr_id)
        except Customer.DoesNotExist:
            return JsonResponse({'res': 1, 'errmsg': '地址不存在'})

        #  判断支付方式是否存在
        if pay_method not in OrderInfo.PAY_METHODS.keys():
            return JsonResponse({'res': 2, 'errmsg': '支付方式不存在'})
        total_price = 0
        total_count = 0
        order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id)
        # 设置事务保存点
        tip = transaction.savepoint()
        try:
            new_order =OrderInfo.objects.create(order_id=order_id,
                                     user=user,
                                     addr=address,
                                     pay_method=pay_method,
                                     total_count=total_count,
                                     total_price=total_price,
                                     transit_price=transit_price)
        except Exception as e:
            # 回滚到tip
            transaction.savepoint_rollback(tip)
            return JsonResponse({'res': 3, 'errmsg': '生成订单失败'})
        cart_key = 'cart_%d' % user.id
        conn = get_redis_connection('default')
        skus = conn.hgetall(cart_key)
        for sku_id in eval(sku_ids):  # 循环获得商品的信息  每条数据生成一个订单商品数据
            # 获得购物车中每个商品的数量
            sku_count = conn.hget(cart_key, sku_id)
            if not sku_count:  # 判断购物车里有没有该商品
                # 回滚到tip
                transaction.savepoint_rollback(tip)
                return JsonResponse({'res': 4, 'errmsg': '该商品不在购物车中'})
            try:  # 在商品表中 找该商品sel
                sku = GoodsSKU.objects.select_for_update().get(id=sku_id, is_show=1)
            except GoodsSKU.DoesNotExist:
                # 回滚到tip
                transaction.savepoint_rollback(tip)
                return JsonResponse({'res': 5, 'errmsg': '该商品已下架'})
            price = sku.price
            sku_count = int(sku_count)
            if sku_count > int(sku.count):  # 判断库存
                # 回滚到tip
                transaction.savepoint_rollback(tip)
                return JsonResponse({'res': 6, 'errmsg': '库存不足'})
            price = int(price)
            sku_price = sku_count * price
            total_price += sku_price
            total_count += sku_count
            # 添加订单商品表
            try:
                OrderGoods.objects.create(order=new_order,
                                          sku=sku,
                                          count=sku_count,
                                          price=sku_price,
                                          )
            except Exception as e:
                # 回滚到tip
                transaction.savepoint_rollback(tip)
                return JsonResponse({'res': 7, 'errmsg': '订单商品创建失败'})
            # 减少商品库存 增加商品销售额
            sku.count -= sku_count
            sku.sale_count += sku_count
            sku.save()
            # 删除修改购物车数据
            conn.hdel(cart_key, sku_id)
        total_price += int(transit_price)
        # 修改order 订单数量 总价  邮费
        new_order.total_count = total_count
        new_order.total_price = total_price
        new_order.transit_price = transit_price
        new_order.save()
        #释放保存点

        transaction.savepoint_commit(tip)

乐观锁:

          @transaction.atomic
    def post(self, request):
        '''订单创建'''
        # 判断用户是否登录
        user = request.user
        if not user.is_authenticated():
            return JsonResponse({'res':0, 'errmsg':'用户未登录'})
        # 接收参数
        addr_id = request.POST.get('addr_id')
        pay_method = request.POST.get('pay_method')
        sku_ids = request.POST.get('sku_ids') # 1,4
        # 校验参数
        if not all([addr_id, pay_method, sku_ids]):
            return JsonResponse({'res':1, 'errmsg':'数据不完整'})
        # 校验地址
        try:
            addr = Address.objects.get(id=addr_id)
        except Address.DoesNotExist:
            # 地址不存在
            return JsonResponse({'res':2, 'errmsg':'地址信息错误'})
        # 校验支付方式
        if pay_method not in OrderInfo.PAY_METHODS.keys():
            # 支付方式无效
            return JsonResponse({'res':3, 'errmsg':'支付方式无效'})
        # 业务处理:订单创建
        # 组织参数
        # 订单id(order_id): 20171211123130+用户的id
        order_id = datetime.now().strftime('%Y%m%d%H%M%S')+str(user.id)
        # 运费
        transit_price = 10
        # 总数目和总金额
        total_count = 0
        total_price = 0
        # 设置保存点
        sid = transaction.savepoint()
        try:
            # todo: 向df_order_info表中添加一条记录
            order = OrderInfo.objects.create(order_id=order_id,
                                             user=user,
                                             addr=addr,
                                             pay_method=pay_method,
                                             total_count=total_count,
                                             total_price=total_price,
                                             transit_price=transit_price)
            conn = get_redis_connection('default')
            cart_key = 'cart_%d'%user.id
            # todo: 用户的订单中包含几个商品,就应该向df_order_goods中添加几条记录
            sku_ids = sku_ids.split(',') # [1,4]
            for sku_id in sku_ids:
                for i in range(3):
                    # 根据sku_id获取商品的信息
                    try:
                        # select * from df_goods_sku where id=sku_id;
                        sku = GoodsSKU.objects.get(id=sku_id)
                    except GoodsSKU.DoesNotExist:
                        transaction.savepoint_rollback(sid)
                        return JsonResponse({'res':4, 'errmsg':'商品信息错误'})
                    # 获取用户要购买的商品的数目
                    count = conn.hget(cart_key, sku_id)
                    # todo: 判断商品库存
                    if int(count) > sku.stock:
                        transaction.savepoint_rollback(sid)
                        return JsonResponse({'res':6, 'errmsg':'商品库存不足'})
                    # todo: 减少商品的库存,增加销量
                    origin_stock = sku.stock
                    new_stock = origin_stock - int(count)
                    new_sales = sku.sales + int(count)
                    # print('user:%d i:%d stock:%d'%(user.id, i, origin_stock))
                    # import time
                    # time.sleep(10)
                    # 返回更新的行数
                    # update df_goods_sku set stock=new_stock, sales=new_sales
                    # where id=sku_id and stock=origin_stock
                    res = GoodsSKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
                    if res == 0:
                        # 更新失败
                        if i == 2:
                            # 已经尝试了3次,下单失败
                            transaction.savepoint_rollback(sid)
                            return JsonResponse({'res':7, 'errmsg':'下单失败2'})
                        continue
                    # todo: 向df_order_goods中添加一条记录
                    OrderGoods.objects.create(order=order,
                                              sku=sku,
                                              count=count,
                                              price=sku.price)
                    # todo: 累加计算商品的总件数和总金额
                    total_count += int(count)
                    total_price += sku.price*int(count)
                    # 更新成功之后跳出循环
                    break
            # todo: 更新order的total_count和total_price
            order.total_count = total_count
            order.total_price = total_price
            order.save()
        except Exception as e:
            transaction.savepoint_rollback(sid)
            return JsonResponse({'res':7, 'errmsg':'下单失败'})
        # 释放保存点
        transaction.savepoint_commit(sid)
        # todo: 删除购物车对应记录信息
        conn.hdel(cart_key, *sku_ids) # 1,4
        # 返回应答
        return JsonResponse({'res':5, 'message':'订单创建成功'})

 tip:使用乐观锁时,记得修改mysql的隔离级别:vi /etc/mysql/mysql.conf.d/mysqld.cnf    在mysqld最下面一行添加:transaction-isolation = READ_COMMITTED 。

<think>好的,我现在需要解决用户关于高并发秒杀场景中超卖问题的疑问。用户提到了最佳实践,所以我得整理出有效的解决方案。首先,我需要回忆之前学过的相关知识,并结合提供的引用资料来构建回答。 用户提到的超卖问题,主要是因为在高并发下多个请求同时扣减库存,导致库存变为负数。引用3里提到超卖的原因是没有有效控制并发访问库存。所以,解决方案的核心应该是如何控制并发,确保库存扣减的原子性和一致性。 首先想到的是使用数据库的乐观,比如版本号控制。引用4中提到乐观,可能是指通过版本号或者CAS(Compare and Set)来更新库存,这样只有版本匹配时才更新,避免覆盖其他事务的结果。例如,在更新时检查库存是否大于0,并且版本号正确。这样可以防止多个请求同时扣减同一个库存。 然后是悲观,比如数据库的行级(SELECT FOR UPDATE)。过引用4提到悲观会导致线程阻塞和排队,影响性能,特别是在高并发下可能是最佳选择。过在某些情况下,如果使用得当,可能还是有效,但需要权衡性能。 接下来是Redis的原子操作。因为Redis是单线程的,可以处理命令的原子性。比如使用INCR或者DECR命令来扣减库存,或者使用Lua脚本保证多个操作的原子性。引用4提到Redis的乐观,可能是指用WATCH命令配合事务,但更高效的方式可能是直接使用原子操作,比如DECR判断结果是否大于等于0。这样可以避免超卖,因为Redis的操作是原子性的,会出现并发问题。 然后,可以考虑预扣库存。将库存加载到Redis中,提前预热,每次请求先扣减Redis中的库存,如果成功再异步更新数据库。这样可以减少数据库的压力,提高响应速度。但需要保证Redis和数据库的数据一致性,可能需要消息队列来处理后续的持久化操作。 另外,限流和排队机制也很重要。比如使用令牌桶或者漏桶算法来控制请求的速率,只放行部分请求到后端系统,避免瞬间压垮系统。引用2提到瞬时高并发问题,限流可以有效缓解压力,结合队列来缓冲请求,按顺序处理。 还有分布式,比如基于Redis的RedLock或者ZooKeeper的分布式,确保同一时间只有一个服务实例能处理库存扣减。过分布式的实现复杂,需要考虑的超时和续期问题,可能会影响性能,需要谨慎使用。 最后是异步处理和队列,将请求放入消息队列,消费者逐个处理,保证顺序性。这样虽然会增加延迟,但能保证库存的正确性。同时,前端可以通过分阶段提交,比如先预占库存,再引导用户支付,支付成功后再真正扣减,否则释放预占库存。这需要设计状态机来管理订单状态。 另外,需要考虑库存回补,比如用户超时未支付,库存需要自动回滚,避免占用库存导致其他用户无法购买。这可以通过定时任务或者延迟队列来实现。 总结下来,最佳实践应该是结合多种技术:Redis原子操作预扣库存、数据库乐观保证最终一致性、限流和队列控制并发、异步处理订单,以及库存回补机制。同时,需要压测和监控系统现,及时调整参数。 现在要确保回答的结构清晰,分点说明每个方案,并引用相关资料。例如,在提到Redis时引用4,提到超卖原因引用3,提到高并发场景引用1和2。需要自然地将引用标注在相关段落末尾,避免干扰阅读。</think>### 高并发秒杀场景下避免超卖问题的最佳实践 #### 1. **库存预加载与原子操作** 将商品库存提前加载到Redis中,利用Redis的原子操作(如`DECR`或`Lua脚本`)实现库存扣减。 - **Redis原子操作示例**: ```lua -- 使用Lua脚本保证原子性 if redis.call("GET", stock_key) >= 1 then redis.call("DECR", stock_key) return 1 else return 0 end ``` 此方法通过单线程特性确保扣减操作的原子性,避免并发冲突[^4]。 #### 2. **数据库乐观** 在最终数据库层通过版本号或条件判断(如`WHERE stock > 0`)兜底,防止Redis与数据库数据一致时的超卖。 - **SQL示例**: ```sql UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 123 AND stock > 0 AND version = #{current_version}; ``` 乐观通过版本号控制,仅允许符合条件的请求更新库存[^4]。 #### 3. **限流与削峰** - **令牌桶/漏桶算法**:限制每秒请求量,防止系统过载。 - **队列缓冲**:使用消息队列(如Kafka、RocketMQ)将请求异步化,按系统处理能力消费订单,例如: ```python # 伪代码:将请求推入队列 def handle_request(user_id, item_id): if check_redis_stock(item_id): mq.push({"user_id": user_id, "item_id": item_id}) ``` 队列机制可平滑流量峰值,避免瞬时压力[^2][^3]。 #### 4. **分布式控制** 对关键操作(如库存扣减)使用分布式(如Redis的RedLock),确保同一时间仅一个线程处理。 - **示例**: ```java // 伪代码:RedLock实现 RedissonClient redisson = Redisson.create(); RLock lock = redisson.getLock("stock_lock"); try { if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { // 扣减库存操作 } } finally { lock.unlock(); } ``` 需注意的超时和续期问题,避免死。 #### 5. **异步订单与状态机** - **分阶段提交**:用户提交请求后生成“预占订单”,支付成功后再转为正式订单,超时未支付则释放库存。 - **库存回补**:通过定时任务或延迟队列(如Redis的ZSET)检查未支付订单,自动回滚库存。 #### 6. **熔断与降级** - **服务熔断**:当库存足或系统压力过大时,直接返回“已售罄”或降级页面,减少无效请求。 - **静态化页面**:将秒杀商品页静态化,减少后端动态请求压力[^1]。 #### 7. **压测与监控** - **全链路压测**:模拟高并发场景,验证系统瓶颈。 - **实时监控**:通过Prometheus+Grafana监控库存、QPS、延迟等指标,及时扩容或调整策略。 ### 总结方案组合 **最佳实践流程**: 1. 前端限流(如Nginx层限制IP频率)。 2. 请求进入后,Redis原子操作扣减预加载库存。 3. 扣减成功则生成预占订单,进入消息队列。 4. 异步消费队列,通过数据库乐观完成最终库存扣减和订单持久化。 5. 支付超时或失败时触发库存回补[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值