一般由于秒杀抢购要求的性能以及实时性,实现方法有:① 一个redis队列数组,存放的是N商品的信息,用户进来拿取并出队 ②一个redis队列数组,存放的是N个用户的信息,达到N时无法新增,再给队列里面所有用户N个商品。

本文实现的时第一种,第二种也是道理相通。

—–

本文处理逻辑是:用户进行秒杀,加入支付缓存Hash中,触发laravel队列启动(监听用户支付Hash表,10分钟后用户未支付,归还库存和抢兑次数),触发启动的同时,用户从Redis中拿到商品信息,减少库存,减少抢兑次数。

命名规则

商品队列  project:robList:activity_id

支付哈希 project:payList:activity_id

废话少说,上代码。

1.首先,初始化缓存数据结构,抽出一个初始化缓存方法。

 // 抢兑名额队列,建议表名写在config下或者类静态遍变量,以下变量就引用该静态变量。
    const REDIS_ROB_TABLE = 'oneyuan:robList:';

// 初始化商品信息库存,$stock是库存容量,$data是商品信息,这里传进来是json字符串,$right_id是商品id
public function addOrder($right_id = null, $stock = 0, $data = 0)
    {
        if ($right_id) {
            Redis::del(self::REDIS_ROB_TABLE . $right_id);
            $list = array_fill(0, $stock, $data);
            Redis::lPush(self::REDIS_ROB_TABLE . $right_id, $list);
        }
    }

2. 抢兑过程

// 业务逻辑..省略

// 判断队列是否为空,抢兑成功并完成业务逻辑后。加入支付Hash,传入用户id和商品信息还有订单开始时间  
$orderSer->addHash(json_encode([
                                        'created_at' => strtotime($orderRes['created_at']),
                                        'stock'      => $data
                                    ]), $user->id, $request->rights_id);
                                    // 开启轮询,传入活动结束时间
                                    $orderSer->openListen($activity);

// 业务逻辑..省略

其中addHash方法,加入支付Hash

 // 加入监听支付hash
    public function addHash($data, $user_id, $right_id = null)
    {
        if ($right_id) {
            Redis::hSet(self::REDIS_PAY_TABLE . $right_id, $user_id, $data);
        }
    }

其中openListen方法则是开启监听队列,下面会讲。

3. 新建一个队列

php artisan make:job LockStockWatcher

在app/jobs下看到我们刚新建的LockStockWatcher.php文件

打开看到handle方法,这里就是我们要写业务逻辑的地方

启动一个队列有多种方式,普遍方式是Queue::dispatch([param]);

讲回上面的openListen方法,当dispatch方法启动后,redis中会有队列的相关信息,like this

 

然后php artisan queue:work –queue=name,name换成onQueue中指定的队列名称

redis中queue的状态变为queues:project:reserved,所以加个判断条件,若queue已经启动,则无需再次启动。

在job文件中添加构造方法,传入监听队列所需的数据

/**
     * LockStockWatcher constructor.
     * @param $activity 活动查询语句
     */
    public function __construct($activity)
    {
        // 保存轮询结束时间
        $this->end_time = strtotime($activity->end_time);
        // 8秒轮询
        $this->interval_time = 8;
        // 保存需要轮询的权益id
        $this->rights_list = [];
        foreach ($activity->rights()->get() as $item) {
            array_push($this->rights_list, $item->id);
        }
        $this->activity_id=$activity->id;
    }

handle方法改为自己的业务逻辑

  /**
     * @param RightOrderService $orderService
     */
    public function handle(RightOrderService $orderService)
    {
        set_time_limit(0);
        while (time() < $this->end_time) {
            // 伪代码,检查支付队列有没有超过10分钟,有的话回收库存,
            // 伪代码,检查所有商品缓存有没有都被抢完了,抢完退出轮询
            sleep($this->interval_time);
        }
        // 活动已结束
        Log::debug('活动已结束');
    }

完结。

升级:因为队列有时候受nginx影响会杀掉队列进程,所以这里推荐使用supervisor守护队列进程,当进程挂掉的时候,supervisor会重启队列进度。

 

有待改善的地方:每个用户有10分钟支付时间,这里要开启轮询每个支付列表,这个轮询最差的情况是整个活动的开始与结束时间。

您或许感兴趣

[2018-10-14]Simple Rtmp Server (srs) 与 Flv.js 实现直播功能

发表评论

电子邮件地址不会被公开。