redis 隊列簡單實現高併發搶購/秒殺

前提爲每人限購1件

開搶前

把秒殺商品庫存存進 Redis 隊列中

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

//庫存
$num = 10;

//往隊列 goods_store 插入商品, 隊列的長度爲庫存
for($i=0;$i<$num;$i++)
    $redis->lpush('goods_store', 1);

開搶中

方法1
前端: 用戶點擊購買按鈕進行form表單提交
後端: 執行下面代碼

//用戶等待隊列
$wait_key = "user_wait";

//庫存隊列
$store_key = "goods_store";

//根據Redis hash特性, 設置成功返回1, 舊值被覆蓋則返回0, 用來控制同一用戶多買現象
$result = $redis->hset($wait_key, $user_id, $user_id);
if($result){
    $count = $redis->lpop($store_key);
    if(!$count)
        return '已經搶光了';

    //下單流程, 數據庫入庫等操作
    //下單失敗或報錯則執行 $redis->hdel($wait_key, $user_id); 和加庫存 $redis->lpush('goods_store', 1); 並跳轉回上一頁提示下單失敗
    ......
    
    //下單成功則跳轉到相應頁面
    return '搶購成功';
}

問題: 高併發下可能造成服務器壓力瞬間過大, 導致數據入庫失敗, 可將下單入庫等流程用crontab定時器異步執行

方法2
前端: 用戶點擊購買按鈕, 按鈕變灰防止用戶重複點擊, 並且彈出小窗口提示排隊中
ajax異步調用搶購接口

  • 成功: js輪詢請求是否下單成功接口
    • 成功: 跳轉相應頁面進行支付流程
    • 失敗: 提示用戶重新進行購買流程
  • 失敗: 提示用戶重新進行購買流程

搶購接口代碼

//用戶等待隊列
$wait_key = "user_wait";

//搶購成功的用戶隊列
$user_key = "user";

//庫存隊列
$store_key = "goods_store";

//根據Redis hash特性, 設置成功返回1, 舊值被覆蓋則返回0, 用來控制同一用戶多買現象
$result = $redis->hset($wait_key, $user_id, $user_id);
if($result){
    $count = $redis->lpop($store_key);
    if(!$count)
        return '已經搶光了';

    $redis->lpush($user_key, $user_id);
    return '搶購成功';
}

crontab定時器異步執行下單入庫代碼

//用戶等待隊列
$wait_key = "user_wait";

//搶購成功的用戶隊列
$user_key = "user";

$count = $redis->rpop($user_key);
if(!$count)
    return;

//下單流程, 數據庫入庫等操作
//下單失敗或報錯則執行 $redis->hdel($wait_key, $user_id); 和加庫存 $redis->lpush('goods_store', 1);
......

是否下單成功接口代碼

//數據庫查詢order訂單表返回是否存在未支付訂單數據

待解決:
假設用戶A流程進行到以下邏輯時
流程1.下單流程, 數據庫入庫等操作
流程2.下單失敗或報錯則執行 $redis->hdel($wait_key, $user_id); 和加庫存 $redis->lpush('goods_store', 1);


假設1由於未知錯誤導致程序崩潰, 沒有執行2就退出了, 用戶A不能重新搶購
假設1由於未知錯誤導致入庫失敗, 2在執行 $redis->hdel 失敗了, 用戶A不能重新搶購

優化:
前端靜態資源上CDN
設置nginx的最大連接數
假設秒殺商品庫存有10個, 當用戶等待隊列 user_wait 長度大於 30~100 後的請求全部過濾
添加一個延時隊列, 把下單規定時間內沒有付款的訂單取消掉, 並加庫存


附送 Redis 鎖簡易代碼

// 加鎖  $random:隨機數  $expire_time:有效時間
$lock_status = $redis->set($lock_key, $random, array('nx', 'ex' => $expire_time));

if($lock_status){

    // do something
    ......

    if($redis->get($lock_key) == $random){
        // 解鎖
        $redis->del($lock_key);
    }
}





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