基于laravel队列+Redis的秒杀锁库存实现
一般由于秒杀抢购要求的性能以及实时性,实现方法有:① 一个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分钟支付时间,这里要开启轮询每个支付列表,这个轮询最差的情况是整个活动的开始与结束时间。