PHP 通過redis和mysql實現秒殺業務

第一種方式

$db = MySQLDB::getInstance();
$info = $db->fetchRow('select * from goods where goods_id=1');
//判斷是否還有庫存
if ($info['stock'] <= 0) {
    exit('賣完了');
}

//減少庫存,num 只是一個記錄修改數據的次數,可以判斷是否存在超賣現象
$result = $db->update("update goods set stock=stock-1,num=num+1 where goods_id=1 and stock>0");
if (!$result) {
    exit('搶購失敗');
}
file_put_contents('./order.txt', $result . PHP_EOL, FILE_APPEND);
exit('搶購成功');

通過apache壓測工具ab來試一試會不會超賣

數據庫情況

ab -c 100 -n 1000 http://test.localhost.com/database.php

結果

數據庫情況

日誌文件

沒有超賣

這種方案適合用戶量少的情況,mysql壓力不會太大

第二種方案

監聽key + 事務 實現

具體代碼

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379, 5);

    //監聽已搶購的數量,如果在事務執行之前這個 key 被其他命令所改動,那麼事務將被打斷。
    $redis->watch('rush:goods_stock:1');
    //獲取庫存
    $stock = $redis->get('rush:goods_stock:1');
    if ($stock == 0) {
        die('搶購失敗,已經銷售完畢');
    }
    //事務開始
    $redis->multi();
    //庫存 -1
    $redis->decr('rush:goods_stock:1');
    //執行提交
    $res = $redis->exec();
    //事務提交成功
    if ($res) {
        $data = [
            'goods_id'=>1,
            'user_id'=>rand(1,9999),
            'time'=>microtime(true)
        ];
        $redis->lPush('rush:goods:1',json_encode($data));
        file_put_contents('./order.txt', 1 . PHP_EOL, FILE_APPEND);
        die('搶購成功');
    }
    die('搶購失敗');

設置redis 的值

通過ab壓測

ab -c 100 -n 1000 http://192.168.1.13:82/redis.php

查看redis數據情況

這種方案不一定是先提交的先下單,可能會涉及到還有庫存搶購失敗的情況。

第三種方案

通過把庫存設置爲redis 隊列 來實現

先設置商品庫存的隊列

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379, 5);
    //庫存
    $stock = 10;
    //先清空
    $redis->del('rush:goods_stock:queue:2');
    //放入隊列中
    for ($i = 0; $i < $stock; $i++) {
        $redis->lPush('rush:goods_stock:queue:2', 1);
    }
    die('放入隊列完成');

執行完成看一下redis數據

實現搶購功能

    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379, 5);
    //讀取隊列,redis操作都是原子性的,不用擔心會重複讀取
    if ($redis->rPop('rush:goods_stock:queue:2')) {
        $data = [
            'goods_id'=>2,
            'user_id'=>rand(1,9999),
            'time'=>microtime(true)
        ];
        $redis->lPush('rush:goods:2',json_encode($data));
        die('搶購成功');
    }
    die('搶購失敗,已經銷售完畢');

壓測

ab -c 100 -n 1000 http://192.168.1.13:82/redis.php

結果數據正確

該方案用戶基本可以先下手爲強。注意設置庫存的地方

 

庫存解決好了後面就是把訂單寫入數據庫,其實跑一個腳本去讀搶購成功的隊列就行了

 

有的文章是直接判斷搶購成功的隊列是否小於庫存,並且寫入隊列的時候沒有減少庫存。這樣的方式必須等待搶購完畢之後才能寫入數據庫,因爲一邊進一邊出,會導致超賣,這種方式不太靈活。也看具體場景吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章