laravel+Redis+Msysql商品秒杀与超卖
一,添加路由
// ======== 商品秒杀接口 ============= start ==================
Route::any('seckill/goods_order', 'App\SeckillController@goodsOrder')->name('seckill.goods_order'); // 抢购下单
Route::any('seckill/goods_stock', 'App\SeckillController@goodsStock')->name('seckill.goods_stock'); // redis添加库存
// ======== 商品秒杀接口 ============= end ==================
二,在app\Http\Controllers\App添加SeckillController.php
<?php
namespace App\Http\Controllers\App;
/**
* redis
* 1、抢购下单
* 2、redis添加库存
*/
use App\Http\Controllers\BaseController;
use App\Logics\Services\src\SeckillService;
class SeckillController extends BaseController {
public function __construct(SeckillService $service)
{
$this->service = $service;
}
/**
* 抢购下单
*/
public function goodsOrder() {
try
{
return $this->service->goodsOrder();
}
catch (\Exception $e)
{
return $this->result(['file'=>$e->getFile(), 'line'=>$e->getLine()], $e->getCode(), $e->getMessage());
}
}
/**
* redis添加库存
*/
public function goodsStock() {
try
{
return $this->service->goodsStock();
}
catch (\Exception $e)
{
return $this->result(['file'=>$e->getFile(), 'line'=>$e->getLine()], $e->getCode(), $e->getMessage());
}
}
}
三,在app\Logics\Models添加Seckill.php
<?php
namespace App\Logics\Models;
class Seckill extends Base
{
protected $table = 'goods';
//
protected $fillable = [
'name','imgs', 'price', 'num', 'status', 'desc','stock','sort'
];
public $fields = [
'id',
'name','imgs', 'price', 'num', 'status', 'desc','stock','sort'
];
}
四,在app\Logics\Models添加Order.php
<?php
namespace App\Logics\Models;
class Order extends Base
{
protected $table = 'orders';
protected $fillable = [
'user_id', 'type', 'money','out_trade_no', 'pay_time','desc', 'status'
];
public $fields = [
'id',
'user_id', 'type', 'money','out_trade_no', 'pay_time','desc', 'status'
];
}
五,app\Logics\Repositories\src添加SeckillRepository.php
<?php
namespace App\Logics\Repositories\src;
use App\Logics\Models\Seckill;
use App\Logics\Repositories\Repository;
use Illuminate\Support\Facades\Cache;
class SeckillRepository extends Repository {
public function model() {
return Seckill::class;
}
// 商品列表
public function goodsList() {
$list =$this->model
->select(['id','stock'])
->orderBy('id', 'asc')
->get();
return $this->toArr($list)
}
// 商品信息
public function goodsInfo($goods_id) {
$info =$this->model->where('id',$goods_id)
->first();
return $info;
}
}
六,app\Logics\Services\src添加SeckillService.php
<?php
/**
* 各类操作信息接口
* 1、任务中心任务列表
* 2、完成任务领取奖励
* 3、幸运币兑换
* 等等信息接口
*/
namespace App\Logics\Services\src;
use App\Logics\Models\Order;
use App\Logics\Models\Seckill;
use App\Logics\Repositories\src\SeckillRepository;
use App\Logics\Services\Service;
use App\Logics\Traits\QueueTrait;
use Illuminate\Support\Facades\DB;
class SeckillService extends Service {
use QueueTrait;
protected $seckill;
public function Repositories() {
return [
'seckill' => SeckillRepository::class,
];
}
/**
* 抢购下单
*/
public function goodsOrder() {
$user_id = 1; //用户id
$goods_id = 11; //商品id
$buy_num = 1; //购买数量
$count = $this->GetStockQueue($goods_id,$buy_num); //每次抢单
if($count !== null){
$do_order = $this->doOrder($goods_id, $count, $user_id);
if($do_order === true){
return $this->result('抢购成功',0,'ok');
}
}else{
throw new \Exception('库存不足!', 2000);
}
}
/**
* redis添加库存
*/
public function goodsStock() {
$goods = $this->seckill->goodsList(); //商品列表
if (!$goods) {
throw new \Exception('商品列表为空!', 2000);
}
foreach($goods as $k=>$v){
if($v['stock'] > 0){
$stock_add = $this->AddStockQueue($v['id'],$v['stock']);
}
}
return $this->result(true,0,'ok');
}
// 下单更新库存
public function doOrder($goods_id, $goodsNum, $user_id)
{
$goods = $this->seckill->goodsInfo($goods_id); //商品信息
$orderNo = $this->orderNo();
$number = $goods['stock'] - $goodsNum;
$money = $goodsNum * $goods['price'];
if ($number < 0) {
throw new \Exception('没有库存了!', 2000);
}
try {
DB::beginTransaction(); //开启事务
/*添加订单start*/
$order_val = [
'user_id' => $user_id,
'type' => 'balance',
'money' => $money,
'out_trade_no' => $orderNo,
'desc' => '用户购买商品生成的订单'
];
$orderadd = Order::create($order_val);
/*添加订单end*/
/*减少商品库存start*/
$stock_edit = Seckill::where('id',$goods_id)->update(['stock'=>$number]);
/*减少商品库存end*/
DB::commit();
return true;
} catch (\Exception $e) {
DB::rollBack();
$err_msg = $e->getMessage();
return $err_msg;
}
}
// 生成订单号
public function orderNo()
{
return date('Ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
}
七,app\Logics\Traits添加QueueTrait.php
<?php
namespace App\Logics\Traits;
use App\Logics\Repositories\src\SeckillRepository;
use Illuminate\Support\Facades\Redis;
trait QueueTrait
{
/**
* 添加商品库存队列
* @param int $good_id 商品ID
* @param array $num 库存数量
* @return int 队列长度(库存数量)
*/
public function AddStockQueue(int $good_id, int $num) {
list($key, $len) = $this->GetQueueNumKey($good_id);
$list_num = [];
$i = 0;
while ($i < $num) {
$list_num[] = 1;
$i++;
}
$rel = Redis::rpush($key, $list_num);
//$this->SetDutyQueueExpireAt($good_id); // 设置队列过期时间
return $this->queueTraitReturnRel($rel);
}
/**
* 获取商品库存
* @param int $goodsid 商品ID
* @param int $buynum 购买数量
* @return int 队列号
*/
public function GetStockQueue(int $goodsid,$buynum) {
list($key, $len) = $this->GetQueueNumKey($goodsid);
if ($len < $buynum) {
return $this->queueTraitReturnRel(null);
//$this->queueTraitExitErr('数量不足!', 3000);
}
// 删除库存数量
$ticket_no = Redis::lRem($key,$buynum,1);
if (!$ticket_no) { // 没有获取到票号
return $this->queueTraitReturnRel(null);
}
$rel = $ticket_no;
return $this->queueTraitReturnRel($rel);
}
/**
* 查询队列数量并返回
* @param int $goods_id
* @return [$key $len]
*/
public function GetQueueNumKey(int $goods_id) {
$this->setQueueDb('start');
$key = 'goods_stock_'. $goods_id;
$len = Redis::llen($key); // 正常返回数量;没有队列返回 0
return [$key, $len];
}
/**
* 抛出异常
* @param string $msg
* @param int $code
* @return fixed
*/
private function queueTraitExitErr($msg, $code) {
$this->setQueueDb('end');
throw new \Exception($msg, $code);
}
/**
* 正常返回信息
* @param $rel
*/
private function queueTraitReturnRel($rel) {
$this->setQueueDb('end');
return $rel;
}
/**
* 设置 队列 DB
* @param $db
* @return bool
*/
protected function setQueueDb($db = 'start')
{
$db = $db == 'start' ? config('database.redis.seckill.database') : config('database.redis.default.database');
return Redis::select($db);
}
private function getDutyInQueueTrait() {
if (!isset($this->seckillRep) || !$this->seckillRep) {
$this->seckillRep = new SeckillRepository();
}
return $this->seckillRep;
}
}
八,postman并发测试
打开postman软件
左侧栏点击+号键,创建一个并发测试文件夹
2、主面板点击+号键,输入一个测试地址,点击save按钮保存到并发测试文件夹
3、点击三角箭头,再点击Run,弹出Collection Runner插件
4、设置并发数和延时时间,点击Run
5、运行结果会依次排序,显示结果状态和耗时
从图中可以看到百度的响应速度还是非常快的。
点击Run Summary可以看到运行结果概述: