1. 秒殺功能主要有兩個問題要解決: ①高併發對數據庫產生的壓力 ②競爭狀態下如何解決庫存的正確減少(“超賣” 問題)。 第一個問題,對於 PHP 來說很簡單,用緩存技術就可以緩解數據庫壓力,比如 memcache,redis 等緩存技術,這裏我使用了 redis。 第二個問題,我使用 redis 隊列,因爲 pop 操作是原子的,即使有很多用戶同時到達,也是依次執行。
2. 對於第二個問題,我覺得可以使用 Laravel 的任務調度功能,每分鐘運行一次腳本,把超時的訂單從數據庫中刪除掉,同時將庫存加 1
Route::get('/home', 'HomeController@index')->name('home'); Route::get('/goods-store', 'HomeController@goodsNumberQueue')->name('goods-store'); Route::get('/order', 'HomeController@secKill')->name('order');
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Redis; class HomeController extends Controller { /** * Show the application dashboard. * * @return \Illuminate\Contracts\Support\Renderable */ public $msg = ''; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('auth'); $goodsId = 1; //商品ID號,這裏我寫死爲1 $this->user_queue_key = "goods:{$goodsId}:user"; //搶購成功隊列的key,使用redis的hashes方式存儲 $this->goods_number_key = "goods:{$goodsId}"; //商品庫存key,使用redis的lists方式存儲 } /** * Show the application dashboard. * * @return \Illuminate\Http\Response */ public function index() { $orders = Redis::hgetall($this->user_queue_key); foreach ($orders as $key => $order) { $orders[$key] = unserialize($order); } return view('home', compact('orders')); } /** * 開始秒殺 * */ public function secKill() { $isHaveQueue = Redis::hexists($this->user_queue_key, auth()->id()); if ($isHaveQueue) { return back()->with('msg', '您已搶購!'); } $count = Redis::rpop($this->goods_number_key); if (!$count) { return back()->with('msg', '已經搶光了哦!'); } $this->succUserNumberQueue(auth()->id()); return back()->with('msg', '搶購成功!'); } /** * 搶購結果隊列,將搶購成功的客戶加入隊列 * * @return bool */ private function succUserNumberQueue($uid) { $orderNo = $this->buildOrderNo(); $userQueue = [ 'user_id' => auth()->id(), 'user_name' => \Auth::user()->name, 'order_no' => $orderNo, 'created_at' => date('Y-m-d H:i:s'), ]; $userQueue = serialize($userQueue); Redis::hset($this->user_queue_key, $uid, $userQueue); return true; } //生成唯一訂單號 private function buildOrderNo() { return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8); } /** * 將商品庫存加入redis,生成庫存 * */ public function goodsNumberQueue() { $store = 100; $goodsLen = Redis::llen($this->goods_number_key); $count = $store - $goodsLen <= 0 ? 0 : $store - $goodsLen; for($i = 0; $i < $count; $i++) { Redis::lpush($this->goods_number_key, 1); } echo '目前庫存:' . Redis::llen($this->goods_number_key) . '<br/>'; } }