原文链接:使用laravel解决库存超出的几个方案
库存超出是一个常见的幂等问题,下面介绍一下解决超卖问题常见的一些方案
Redis 存储库存
Redis 原子锁
Mysql 悲观锁
Mysql 乐观锁
准备 准备一张实验表:
1 2 3 4 5 6 7 +-------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+------------------+------+-----+---------+----------------+ | id | int(11) unsigned | NO | PRI | NULL | auto_increment | | name | varchar(10) | YES | | NULL | | | num | int(11) | YES | | NULL | | +-------+------------------+------+-----+---------+----------------+
使用 go
模拟并发:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "github.com/PeterYangs/tools/http" "sync" ) func main () { client := http.Client() wait := sync.WaitGroup{} for i := 0 ; i < 50 ; i++ { wait.Add(1 ) go func (w *sync.WaitGroup) { defer w.Done() res, _ := client.Request().GetToString("http://www.api/test1?id=1" ) fmt.Println(res) }(&wait) } wait.Wait() }
错误示范 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function test () { $id = request()->input('id' ); $product = Product::where('id' , $id)->firstOrFail(); if ($product->num <= 0 ) { return "卖光啦!!" ; } $product->decrement('num' ); return "success" ; }
查看库存:
库存超出。
Redis 存储库存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public function test () { $id = request()->input('id' ); $redis = new \Redis(); $num = $redis->rawCommand('get' , 'product_' . $id); if ($num <= 0 ) { return "卖完啦!" ; } $result = $redis->rawCommand('decrby' , 'product_' . $id, 1 ); if ($result < 0 ) { $redis->rawCommand('incrby' , 'product_' . $id, 1 ); return "卖完啦!" ; } return 'success' ; }
库存正常。
Redis 原子锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public function test () { $id = request()->input('id' ); $lock = \Cache::lock("product_" . $id, 10 ); try { $lock->block(5 ); $product = TbModel::where('id' , $id)->firstOrFail(); if ($product->num <= 0 ) { return "卖光啦!!" ; } $product->decrement('num' ); return 'success' ; }catch (LockTimeoutException $e) { return '当前人数过多' ; } finally { optional($lock)->release(); } }
库存正常。
Mysql 悲观锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public function test () { $id = request()->input('id' ); try { \DB::beginTransaction(); $product = Product::where('id' , $id)->lockForUpdate()->first(); if ($product->num <= 0 ) { return "卖光啦!!" ; } $product->decrement('num' ); \DB::commit(); return "success" ; } catch (\Exception $exception) { return "error" ; } }
库存正常。
Mysql 乐观锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public function test () { $id = request()->input('id' ); $product = TbModel::where('id' , $id)->first(); if ($product->num <= 0 ) { return "卖光啦!!" ; } $res = \DB::update('UPDATE `tb` SET num = num -1 WHERE id = ? AND num=?' , [$id, $product->num]); if (!$res) { return '当前人数过多' ; } return 'success' ; }
库存正常。
优化乐观锁,修改库存的 sql 修改为:
1 \DB::update ('UPDATE `tb` SET num = num -1 WHERE id = ? AND num-1 >= 0' , [$id ]);
总结 以上几种方案都可以有效解决库存超出的问题,应用时可以根据实际具体场景进行选择,优先考虑顺序为: Redis 存储 > Redis 原子锁 > Mysql 悲观锁/乐观锁
参考链接