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/>'; } }