介绍
秒杀多用在电商中,秒杀、抢购、抢票等实现特定需求场景,都可以归为一种资源争用模式,要保证交易的安全性、可靠性,实现方法较多。先看下秒杀的特点、逻辑。
秒杀特点:
- 抢购人数远多于库存,读写并发巨大
- 库存少,有效写少
- 写需强一致性,商品不能超卖
- 读一致性要求并不高
秒杀逻辑:
1.
获取秒杀抢购数据信息
⒉
.
校验抢购商品的信息,主要商品类型,库存,上下架,时间进行校验
3.是否已经购买
4.
更新地址使用时间
5.
扣
sku
的库存
6.
新建一个订单
,包含地址,邮编,备注,订单金额,订单类型等信息
7.
关联用户写入数据
8.
因为是秒杀商品所以会限制购买数量,只需要一个订单详情即可。
9.将订单加入到延迟队列中
10.
返回订单信息
下面谈谈laravel组件实现秒杀的方法。
秒杀实现方案
框架环境准备
我们采用laravels(laravel+swoole)框架,使用rabbitmq消息组件,相关可以参见文章(《laravel之rabbitmq组件使用》https://blog.youkuaiyun.com/yan_dk/article/details/117914312)
代码集成
秒杀商品模型类:定义秒杀商品开始时间、结束时间等属性。秒杀商品应该是从商品库取得,通过主键id关联商品表,与商品库应该是1对1关系。后面会通过模型及其关联,逻辑判断商品是否上架,商品秒杀时间等验证规则。
class SeckillProduct extends Model {
protected $fillable = ['start_at','end_at'];
protected $dates = ['start_at','end_at'];
public $timestamps = false;
public function product() {
//关联商品表
return $this->belongsTo(Product::class);
}
public function getIsBeforeStartAttribute() {
//判断当前时间是否到了秒杀开始时间之内
// dd("Carbon::now()==",Carbon::now());
return Carbon::now()->lt($this->start_at);
}
public function getIsAfterEndAttribute() {
//判断秒杀时间是否结束
return Carbon::now()->gt($this->end_at);
}
}
秒杀请求验证类:创建秒杀请求验证类,用于校验秒杀请求或用户输入正确性。
# php artisan make:request Api/V1/SeckillOrderRequest
class SeckillOrderRequest extends FormRequest{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules() {
return [
'address_id' => [
'required',
{用户地址是否存在规则}
],
'sku_id' => [
'required',
function ($attribute,$value,$fail){
验证('该商品不存在');
验证('该商品不支持秒杀');
验证('该商品不支持秒杀');
验证('该商品未上架');
验证('秒杀还未开始');
验证('秒杀已结束');
验证('该商品已售完');
验证('你已经抢购了');
验证('你已经下单了,请支付');
}
]
]
}
秒杀控制器类:定义秒杀请求的处理
class OrdersController extends Controller {
public function SeckillOrder(SeckillOrderRequest $request)
{
$user = (new UserLoginController())->userinfo()->original[0]->id;
$address = UserAddr::find($request->input('address_id'));
$sku = ProductSku::find($request->input('sku_id'));
$order = \DB::transaction(function () use ($user,$address,$sku){
$address->update(["last_time" => Carbon::now()]);
if ($sku->decreaseStock(1) <= 0){
throw new \RuntimeException("该商品库存不足");
}
// 创建一个订单
$order = new Order([
'address' => [ // 将地址信息放入订单中
'address' => $address->full_address,
'zip' => $address->zip,
],
'remark' => '',
'total_amount' => $sku->price,
'type' => Order::TYPE_SECKILL,
]);
//订单与用户相关联
$order->user()->associate($user);
$order->save();
// 订单关联到当前用户
$order->user()->associate($user);
// 写入数据库
$order->save();
// 创建一个新的订单项并与 SKU 关联
$item = $order->items()->make([
'amount' => 1, // 秒杀商品只能一份
'price' => $sku->price,
]);
$item->product()->associate($sku->product_id);
$item->productSku()->associate($sku);
$item->save();
return $order;
});
// 秒杀订单的自动关闭时间与普通订单不同
dispatch(new CloseOrder($order, config('app.seckill_order_ttl')));
return $order;
}
}
工作处理类 :工作处理,进入消息队列管理,具体工作是超时后自动关闭订单的相关处理。
生成脚本命令如下
# php artisan make:job CloseOrder
自动生成文件app/Jobs/CloseOrder.php
class CloseOrder implements ShouldQueue{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $order;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Order $order,$time) {
$this->order = $order;
//延迟时间
$this->delay = $time;
}
/**
* Execute the job.
* @return void
*/
public function handle() {
if ($this->order->paid_at){
return;
}
\DB::transaction(function (){
$this->order->update(["closed" => true]);
foreach ($this->order->items as $item){
$item->productSku->addStock($item->amount);
}
});
}
}
路由配置
routes/api.php
$api = app('Dingo\Api\Routing\Router');
$api->version('v1',[
'middleware' => ['bindings'],
'namespace' => "App\Http\Controllers\Api\V1"
],function ($api){
$api->get("test","TestController@test")->name("test.test"); //测试连通性
rabbitmq,更新商品详情
$api->post('SeckillOrder',"OrdersController@SeckillOrder")->name("seckill.order"); //用于测试秒杀
});
相关配置参数
指定秒杀活动开始、结束时间
配置文件修改相关时间参数:config/app.php
'timezone' => 'Asia/Shanghai',
//注意,这里默认 UTC ,要改为Asia/Shanghai
//增加秒杀模块参数 order_ttl(普通订单延时时间)、seckill_order_ttl(秒杀订单延时时间)
'order_ttl' => 144000,
'seckill_order_ttl' => 20,
测试
模拟发起秒杀请求
后台监听消息队列
# php artisan queue:work rabbitmq

调用消息队列中,自动对任务进行处理。