f0x*_*der 2 php database concurrency laravel
我有以下控制器方法,当且仅当没有打开的订单(打开的订单状态= 0,关闭的订单状态= 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'];
}
Run Code Online (Sandbox Code Playgroud)
此方法绑定到特定路由
Route::post('/create', 'OrderController@create');
Run Code Online (Sandbox Code Playgroud)
客户端向此路由发出 ajax 请求。这背后的逻辑非常简单:我希望用户一次只有一个活动订单,因此用户必须在创建新订单之前执行一些操作来关闭先前的订单。以下代码在普通用户的情况下可以完美运行,但在想要损害我的应用程序的用户的情况下则不然。所以问题来了。当用户每秒发送大量此类请求时(我只是在 Google Chrome 开发控制台中使用以下脚本执行此操作)
for (var i = 0; i < 20; i++)
setTimeout(function(){
$.ajax({
url : '/create',
type : 'post',
success: function(d){
console.log(d)
}
})
}, 1);
Run Code Online (Sandbox Code Playgroud)
它会导致多个状态=0 的记录插入到数据库中,而预期只插入一个,而其他的则不应该。IMO,会发生什么:
我试图解决这个问题:
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'];
}
Run Code Online (Sandbox Code Playgroud)
使用事务显着减少了插入的行数(通常甚至可以按预期工作 - 只允许插入 1 行,但并非总是如此)。
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);
}
Run Code Online (Sandbox Code Playgroud)
它根本没有帮助,因为它只是在中间件级别移动问题
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'];
}
Run Code Online (Sandbox Code Playgroud)
这种在很多情况下都有效,尤其是与事务结合使用,但有时它仍然允许最多插入 2 行(在 30 中的 1-2 种情况下)。而且它对我来说看起来很奇怪。我想到了队列,但正如 laravel 文档所说,它们是用于耗时的任务。我也考虑过表锁定,但对普通用户来说似乎也很奇怪并且影响性能。我相信这个问题存在干净简单的解决方案,但我在谷歌中找不到任何合理的东西,也许我错过了一些非常明显的东西?你能帮忙吗?此外,在我的应用程序中有很多类似的情况,我真的很想为并发执行导致此类错误的情况找到一些通用的解决方案,不仅与数据库有关,还与会话、缓存、redis 等有关。
您应该能够lockForUpdate()在user模型上使用,以防止同一用户插入并发订单:
DB::beginTransaction();
User::where('id', $this->user->id)->lockForUpdate()->first();
// Create order if not exists etc...
DB::commit();
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1265 次 |
| 最近记录: |