php 秒杀 不超卖,秒杀业务中不超卖的实现方案汇总

本文详细介绍了防止超卖的几种数据库方案,包括使用FORUPDATE的行锁、库存大于0的判断、无符号整形库存、乐观锁以及分布式锁。强调在事务提交前不应释放分布式锁以避免超卖问题,并推荐使用Redis进行库存操作以减少数据库压力。总结中指出,技术应用应紧密结合业务需求,推荐的实现顺序为Redis > 乐观锁 > 库存大于0 > 分布式锁 > FORUPDATE。
摘要由CSDN通过智能技术生成

数据库方案

以下的方案重点在于防止超卖,库存信息不加载到缓存Redis,而是直接同DB交互,实际场景下通常不会如此,但是其中用到的细节还是值得学习的。

FOR UPDATE

该方案是在MySQL层面进行加锁,行锁Or表锁,要根据Where条件来判定。

该方案通过事务+for update进行保证,伪代码如下所示:

begin transcational

count = SELECT number FROM seckill WHERE seckill_id=? FOR UPDATE

if count > 0

UPDATE seckill SET number=number-1 WHERE seckill_id=?

INSERT order //下订单

commit

说明:

同一个事务中 如果查询最后限定了 for update 那么非本次事务中的 其他SQL指令会被阻塞。

如果where条件为主键或者索引列的时候才会锁住行,即行级锁。否则会锁表,即锁表。当前的where条件很明确是seckill_id为主键,所以是行级锁。

select操作和update操作都是在本次事务中进行的。

库存大于0判定

该方案主要通过在执行update减少库存的时候,加上对库存大于0的判定。

begin transcational

count = UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0

if count > 0

INSERT order //下订单

commit

其中最为核心的就是最后一个条件number>0

库存设置为无符号整形

核心就是将库存设置为无符号整形,就是不允许库存为负数

begin transcational

count = UPDATE seckill SET number=number-1 WHERE seckill_id=?

if count > 0

INSERT order //下订单

commit

这点同where条件有些像。

乐观锁

通过乐观锁来保证商品在每一个只会被消费一次,通过对number进行乐观锁来进行判定,伪代码如下所示:

BEGIN transcational

n = SELECT goods number

count = UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number = n

if count > 0

INSERT order //下订单

else

Loop //再次循环操作

commit

说明

请注意每次进行update前先查询,查询的目的是获取版本号。

通过对CAS的思想通过版本号对库存进行更新,如果符合预期那么更新,否则肯定是被其他线程消费过。

分布式锁

通过分布式锁来保证,同一时间只会有一个线程在处理某类商品秒杀业务:查询库存-->判定库存 -->减少库存。

dis-lock goods type //1. 通过分布式锁:锁住商品类型

begin transcational //2. 开启事务

count = SELECT number FROM seckill WHERE seckill_id=?

if count > 0

UPDATE seckill SET number=number-1 WHERE seckill_id=?

INSERT order //下订单

// dis-un-lock goods type 错误释放分布式锁

commit //3. 提交事务

dis-un-lock goods type //4. 释放分布式锁

说明

请注意3和4 的顺序,一定要先提交事务后,再释放分布式锁。

为什么了?假设此时的库存为1。第一个线程在第提交事务前释放了锁,假设提交事务需要5个单位的时间。另外一个线程在第一个线程释放锁的瞬间,抢占了锁,然后在3个时间就完成了查询和减少库存并提交事务的操作,此时库存为0。2个单位后第一个线程事务提交才完成,此时库存为-1了。这样导致了超卖。

所以事务的提交一定要释放分布式锁之前。

在Spring中事务通过是通过注解@Transcational来实现的,如果直接在@Transcational包裹的方法里面获取锁和释放锁可能会出现超卖,此时需要通过另外一个AOP进行包装,这里涉及到2个知识点。

一个方法有多个AOP注解时候,切面的执行顺序怎么确定的问题。该问题是每个切面都可以通过@order来定义顺序,越小的越先执行,而@Transcational的order是最大,所以肯定是在内部执行。

利用Redis

因为Reis是单线程的,所以可以通过其特性decr后进行判定,实际场景也是推荐这么做的。

秒杀业务前,将秒杀商品和库存信息缓存到Redis中。

对Redis中缓存的商品数量做decr减1操作,如果小于0则将商品id加入商品售卖完成缓存中。避免每次都往Redis请求。

总结

技术是为了解决业务难题而存在和发展的,切记脱离业务来学习技术。

上述方案都是解决秒杀业务的初步原形,大概思路如上所述,其中其实有很多细节,后续抽时间补上。

业务处理上需要结合自己的业务进行扩展,个人推荐:

redis > 乐观锁 > 库存大于0 > 分布式锁 > for update

原则上尽可能的避免锁表操作。

尽量避免请求直接打到数据库上。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值