Php Laravel如何防止并发处理同一用户发送的请求
我有以下控制器方法,当且仅当没有打开的订单时(打开的订单状态为=0,关闭的订单状态为=1),创建新订单 此方法绑定到特定的路由Php Laravel如何防止并发处理同一用户发送的请求,php,database,laravel,concurrency,Php,Database,Laravel,Concurrency,我有以下控制器方法,当且仅当没有打开的订单时(打开的订单状态为=0,关闭的订单状态为=1),创建新订单 此方法绑定到特定的路由 Route::post('/create', 'OrderController@create'); 客户端向该路由发出ajax请求。 这背后的逻辑非常简单:我希望用户一次只有一个活动订单,所以用户必须在创建新订单之前执行一些操作来关闭以前的订单。 下面的代码在普通用户的情况下工作得很好,但在用户想要破坏我的应用程序的情况下就不行了。 这就是问题所在。当用户每秒发送大量
Route::post('/create', 'OrderController@create');
客户端向该路由发出ajax请求。
这背后的逻辑非常简单:我希望用户一次只有一个活动订单,所以用户必须在创建新订单之前执行一些操作来关闭以前的订单。
下面的代码在普通用户的情况下工作得很好,但在用户想要破坏我的应用程序的情况下就不行了。
这就是问题所在。当用户每秒发送大量这样的请求时(我只在GoogleChromeDev控制台中使用以下脚本)
使用事务可以显著减少插入的行数(通常甚至可以按预期工作-只允许插入一行,但并不总是如此)
另外,在我的应用程序中有很多类似的情况,我真的很想找到一些通用的解决方案,以解决并发执行不仅对数据库,而且对会话、缓存、redis等造成此类错误的情况。您应该能够在
用户
模型上使用lockForUpdate(),要防止同一用户插入并发订单,请执行以下操作:
DB::beginTransaction();
User::where('id', $this->user->id)->lockForUpdate()->first();
// Create order if not exists etc...
DB::commit();
我不认为这个$last\u active=Orders::where('user\u id',$this->user->id)->where('status',0)->orderBy('id',desc')->first()
将准确返回最后一个用户的订单。我怎么看它会返回最后一个状态为零的。这不一定是以前的订单。如果要检查上一个订单的状态,它应该是$order=order::where(['user\u id'=>$this->user->id])->latest()->first();如果($order&&(1===$order->status)){/**Exception*/}
是,我更想检查是否存在一些未关闭的订单并获取它,而不仅仅是检查上一个订单是否已关闭。我尝试了你的代码,但它也不起作用,因为在检查if($order&&(1===$order->status))
的时候,多个进程同时通过了这个检查,并且所有进程都能够插入一个新的记录,因为你强调了“先前的顺序”是单数。在本例中,您现在讲述的是反向搜索逻辑:$order=order::where(['user'=>$this->user->id,'status'=>1])->first();if($order){throw new\Exception(“状态为1的现有订单的情况”)}/**代码的其余部分*/
。我并没有试图解决这个问题,但更可能的是,我试图连接代码、描述、错误和意图。你试过@levi的建议吗?谢谢你的指点,改变了声明@李维斯完全拯救了我的一天,它只需要一点零钱就可以完美地工作。这是个好主意,谢谢。它不能只使用$this->user->lockForUpdate()因为它似乎不生成任何sql查询,而是$u=User::where('id',$this->User->id)->lockForUpdate()->first()代码>确实很有魅力。据我所知,lockForUpdate阻止在整个事务提交之前选择用户行,因此其他事务将不得不等待。我不是真的喜欢数据库锁定,您能解释一下在什么情况下这个技巧会导致死锁吗?如果我只锁定单个用户,而不锁定数据库中的任何其他内容,这是否可能?因为只有一个与此事务相关联的资源锁,它不应该导致死锁。看这里-
for (var i = 0; i < 20; i++)
setTimeout(function(){
$.ajax({
url : '/create',
type : 'post',
success: function(d){
console.log(d)
}
})
}, 1);
public function createOrder(Request $req){
// some validation stuff
DB::beginTransaction();
try{
$last_active = Orders::where('user_id', $this->user->id)->where('status', 0)->orderBy('id', 'desc')->first();
if ($last_active){
DB::rollBack(); // i dont think i even need this
return ['status' => 'error'];
}
$order= Orders::create([
'status' => 0
// some details
]);
DB::commit();
}
catch (\Exception $e){
DB::rollBack();
return ['status' => 'error'];
}
return ['status' => 'success'];
}
public function handle($request, Closure $next)
{
if ((session()->has('last_request_time') && (microtime(true) - session()->get('last_request_time')) > 1)
|| !session()->has('last_request_time')){
session()->put('last_request_time', microtime(true));
return $next($request);
}
return abort(429);
}
public function createOrder(Request $req){
if (Cache::has('action.' . $this->user->id)) return ['status' => 'error'];
Cache::put('action.' . $this->user->id, '', 0.5);
// some validation stuff
$last_active = Orders::where('user_id', $this->user->id)->where('status', 0)->orderBy('id', 'desc')->first();
if ($last_active){
Cache::forget('action.' . $this->user->id);
return ['status' => 'error'];
}
$order= Orders::create([
'status' => 0
// some details
]);
Cache::forget('action.' . $this->user->id);
return ['status' => 'success'];
}
DB::beginTransaction();
User::where('id', $this->user->id)->lockForUpdate()->first();
// Create order if not exists etc...
DB::commit();