gpt4 book ai didi

php - Laravel 如何防止并发处理同一用户发送的请求

转载 作者:行者123 更新时间:2023-12-02 00:03:49 25 4
gpt4 key购买 nike

我有以下 Controller 方法,当且仅当没有打开的订单时创建一个新订单(打开的订单有status = 0,关闭的订单有status = 1).

public function createOrder(Request $req){
// some validation stuff
$last_active = Orders::where('user_id', $this->user->id)->where('status', 0)->orderBy('id', 'desc')->first();
if ($last_active){
return ['status' => 'error'];
}
$order= Orders::create([
'status' => 0
// some details
]);
return ['status' => 'success'];
}

此方法绑定(bind)到特定路由

Route::post('/create', 'OrderController@create');

客户端向此路由发出 ajax 请求。这背后的逻辑非常简单:我希望用户一次只有一个事件订单,因此用户必须在创建新订单之前执行一些操作来关闭之前的订单。以下代码在普通用户的情况下完美运行,但在用户想要损害我的应用程序的情况下则不然。所以这就是问题所在。当用户每秒发送大量此类请求时(我只是在 Google Chrome 开发控制台中使用以下脚本执行此操作)

for (var i = 0; i < 20; i++) 
setTimeout(function(){
$.ajax({
url : '/create',
type : 'post',
success: function(d){
console.log(d)
}
})
}, 1);

它会导致多个status=0 的记录被插入到数据库中,而预期只有一个被插入,而其他的不应该被插入。IMO,发生的事情是:

  1. 许多请求涉及网络服务器(在我的例子中是 nginx)
  2. 网络服务器创建了许多 PHP 进程(在我的例子中是通过 php-fpm)
  3. 多个 PHP 进程同时运行方法,通过 if ($last_active){...} 在另一个进程中插入某些记录之前同时检查,从而导致插入多个记录。

我试图解决这个问题:

  1. 在 nginx 端,我限制请求速率 (10 r/s)。这并没有多大帮助,因为它仍然允许在拒绝它们之前非常快速地发送 10 个请求,它们之间的延迟非常小。我不能将速率限制值设置为低于 10 r/s,因为它会伤害普通用户
  2. 在 laravel 方面,我尝试进行交易
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'];
}

使用事务显着减少了插入的行数(通常甚至按预期工作 - 只允许插入 1 行,但并非总是如此)。

  1. 我创建了一个中间件,用于跟踪上次用户请求的时间并将此信息存储在 session 中
   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);
}

它根本没有帮助,因为它只是将问题转移到中间件级别

  1. 我还尝试了一些奇怪的事情:
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'];
}

这种方法在很多情况下都有效,尤其是与事务结合使用时,但有时它仍然允许最多插入 2 行(30 种情况中有 1-2 种情况)。而且它对我来说确实看起来很奇怪。我考虑过队列,但正如 laravel 文档所述,它们专用于耗时的任务。我也考虑过表锁定,但对于普通用户来说似乎也很奇怪并且影响性能。我相信这个问题存在简单明了的解决方案,但我在谷歌中找不到任何合理的东西,也许我遗漏了一些非常明显的东西?你能帮忙吗?此外,在我的应用程序中有很多类似的情况,我真的很想找到一些通用的解决方案来解决并发执行不仅会导致数据库,还会导致 session 、缓存、redis 等错误的情况。

最佳答案

您应该能够在 user 模型上使用 lockForUpdate(),以防止同一用户插入并发订单:

DB::beginTransaction();
User::where('id', $this->user->id)->lockForUpdate()->first();
// Create order if not exists etc...
DB::commit();

关于php - Laravel 如何防止并发处理同一用户发送的请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61289374/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com