laravel+Redis簡單實現隊列通過壓力測試的高並發處理


秒殺活動

在一般的網絡商城中我們會經常接觸到一些高並發的業務狀況,例如我們常見的秒殺搶購等活動,

在這些業務中我們經常需要處理一些關於請求信息過濾以及商品庫存的問題。

在請求中比較常見的狀況是同一用戶發出多次請求或者包含惡意的攻擊,以及一些訂單的復購等情況。

而在庫存方面則需要考慮超賣這種狀況。

下面我們來模擬一個簡單可用的並發處理。

 

直接上代碼

代碼的流程

1.模擬用戶請求,將用戶寫入redis隊列中

2.從用戶中取出一個請求信息進行處理(可以在這個步驟做更多的處理,請求過濾,訂單復購等)

3.用戶下單(支付等),減少庫存。下面使用了兩種方式進行了處理,一種使用了Redis中單線程原子操作的特性使程序一直線性操作從而保持了數據的一致

另外一種是用了事務進行操作,可以根據業務進行調整,這里就不一一描述了。

 

實際的業務狀況更為復雜,但更多的是出於對基礎思路的拓展。

<?php

namespace App\Http\Controllers\SecKill;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class SecKillControllers extends Controller {

    public function SecKillTest() {
     ///在此之前我們已經將一千過用戶寫入了redis中了
$num = Redis::lpop('user_list');
     ///取出一個用戶
     ///
     ///一些對請求的處理
     ///
if (!is_null($num)) {
       ///將需要秒殺的商品放入隊列中
$this->AddGoodToRedis(1);
       ///需要注意的是我們如果寫的是秒殺活動的話,需要做進一步的處理,例如設置商品隊列的緩存等方式,這里就實現了

       ///下訂單減庫存
$this->GetGood(1,$num); } } public function DoLog($log) { file_put_contents("test.txt", $log . '\r\n', FILE_APPEND); } /** * 重點在於Redis中存儲數據的單線程的原子性,!!!無論多少請求同時執行這個方法,依然是依次執行的!!!!! * 這種方式性能較高,並且確保了對數據庫的單一操作,但容錯率極低,一旦出現未可預知的錯誤會導致數據混亂; */ public function GetGood($id,$user_id) { $good = \App\Goods::find($id); if (is_null($good)) { $this->DoLog("商品不存在"); return 'error'; } ///去除一個庫存 $num = Redis::lpop('good_list'); ///判斷取出庫存是否成功 if (!$num) { $this->DoLog("取出庫存失敗"); return 'error'; } else { ///創建訂單 $order = new \App\Order(); $order->good_id = $good->good_id; $order->user_id = $user_id; $order->save(); $ok = DB::table('Goods') ->where('good_id', $good->good_id) ->decrement('good_left', $num); if (!$ok) { $this->DoLog("庫存減少失敗"); return; } echo '下單成功'; } } public function AddUserToRedis() { $user_count = 1000; for ($i = 0; $i < $user_count; $i++) { try { Redis::lpush('user_list', rand(1, 10000)); } catch (Exception $e) { echo $e->getMessage(); } } $user_num = Redis::llen('user_list'); dd($user_num); } public function AddGoodToRedis($id) { $good = \App\Goods::find($id); if ($good == null) { $this->DoLog("商品不存在"); return; } ///獲取當前redis中的庫存。 $left = Redis::llen('good_list'); ///獲取到當前實際存在的庫存,庫存減去Redis中剩余的數量。 $count = $good->good_left - $left; // dd($good->good_left); ///將實際庫存添加到Redis中 for ($i = 0; $i < $count; $i++) { Redis::lpush('good_list', 1); } echo Redis::llen('good_list'); } public function getGood4Mysql($id) { DB::beginTransaction(); ///開啟事務對庫存以及下單進行處理 try { ///創建訂單 $order = new \App\Order(); $order->good_id = $good->good_id; $order->user_id = rand(1, 1000); $order->save(); $good = DB::table("goods")->where(['goods_id' => $id])->sharedLock()->first(); //對商品表進行加鎖(悲觀鎖) if ($good->good_left) { $ok = DB::table('Goods') ->where('good_id', $good->good_id) ->decrement('good_left', $num); if ($ok) { // 提交事務 DB::commit(); echo'下單成功'; } else { $this->DoLog("庫存減少失敗"); } } else { $this->DoLog("庫存剩余為空"); } DB::rollBack(); return 'error'; } catch (Exception $e) { // 出錯回滾數據 DB::rollBack(); return 'error'; //執行其他操作 } } }

AB測試

這里我使用了apache bench對代碼進行測試

不了解的可以參閱這篇文章,有非常詳細的講解

https://www.jianshu.com/p/43d04d8baaf7

調用 代碼中的 

AddUserToRedis()
方法將一堆請求用戶放進redis隊列中
先看庫存

這里設置了一千個庫存
開始壓力測試

向我們的程序發起1200個請求,並發量為200

這里我們完成了1200個請求,其中標記失敗的有1199個。這個是因為apache bench會以第一個請求響應的內容作為基准,

如果后續請求響應內容不一致會標記為失敗,如果看到length中標記的數量不要方,基本可以忽略,我們的請求實際是完成了的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM