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