电商库存超卖常见方案总结

卡二条的技术圈

共 2578字,需浏览 6分钟

 ·

2021-07-04 23:29

字段设计

f02993658afc8bcef5a904855d1aea82.webpSnipaste_2021-07-03_15-07-00

错误示例

function test1()
{

    //商品id
    $id = request()->input('id');

    $product = Product::where('id', $id)->firstOrFail();

    if ($product->num <= 0) {

        return "卖光啦!!";
    }

    //库存减1
    $product->decrement('num');

    return "success";

}

使用go模拟并发

package main

import (
    "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()

}

在数据库中查看库存411f3d1d4fb984a08e657af64d766539.webp

redis原子锁

function test2()
{
    //商品id
    $id = request()->input('id');

    $lock = \Cache::lock("product_" . $id, 10);

    try {

        //最多等待5秒,5秒后未获取到锁,则抛出异常
        $lock->block(5);

        $product = Product::where('id', $id)->firstOrFail();

        if ($product->num <= 0) {

            return "卖光啦!!";
        }
        //库存减1
        $product->decrement('num');

        return 'success';

    }catch (LockTimeoutException $e) {

        return '当前人数过多';

    } finally {

        optional($lock)->release();
    }
}
275c6231633eb38a4160f3578d203187.webpSnipaste_2021-07-03_15-07-26

MySQL悲观锁

function test3()
{

    //商品id
    $id = request()->input('id');

    try {
        \DB::beginTransaction();
        $product = Product::where('id', $id)->lockForUpdate()->first();

        if ($product->num <= 0) {

            return "卖光啦!!";
        }

        //库存减1
        $product->decrement('num');

        \DB::commit();

        return "success";

    } catch (\Exception $exception) {

    }

}
ec63fa3f4150fb3c1d2480b84a1d79c3.webpSnipaste_2021-07-03_15-07-36

MySQL乐观锁

function test4()
{

  //商品id
  $id = request()->input('id');

  $product = Product::where('id', $id)->first();

  if ($product->num <= 0) {

      return "卖光啦!!";
  }

  //修改前检查库存和之前是否一致,不一致说明已经有变动,则放弃更改
  $res = \DB::update('UPDATE `product` SET num = num -1 WHERE id = ? AND num=?', [$id, $product->num]);

  if (!$res) {

      return '当前人数过多';

  }

  return 'success';


}

优化

\DB::update('UPDATE `product` SET num = num -1 WHERE id = ? AND num-1 >= 0', [$id]);

适用Redis存储库存

function test5()
{

    //商品id
    $id = request()->input('id');

    $num = Redis::command('get', ['product_' . $id]);

    if ($num <= 0) {

        return "卖完啦!";
    }

    //减库存
    $re = Redis::command('decrby', ['product_' . $id, 1]);

    //减多了回滚
    if ($re < 0) {

        Redis::command('incrby', ['product_' . $id, 1]);

        return "卖完啦!";
    }

    return 'success';
}


浏览 38
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报