Laravel結合Redis實現的一個很簡單的搶購、秒殺功能

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/>';
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章