Laravel结合Redis实现的一个很简单的抢购、秒杀功能

本文详细介绍了秒杀系统的两大核心问题:高并发下数据库压力缓解及库存正确减少的解决方案。利用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/>';
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值