Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/fortran/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php Laravel如何防止并发处理同一用户发送的请求_Php_Database_Laravel_Concurrency - Fatal编程技术网

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

Php Laravel如何防止并发处理同一用户发送的请求,php,database,laravel,concurrency,Php,Database,Laravel,Concurrency,我有以下控制器方法,当且仅当没有打开的订单时(打开的订单状态为=0,关闭的订单状态为=1),创建新订单 此方法绑定到特定的路由 Route::post('/create', 'OrderController@create'); 客户端向该路由发出ajax请求。 这背后的逻辑非常简单:我希望用户一次只有一个活动订单,所以用户必须在创建新订单之前执行一些操作来关闭以前的订单。 下面的代码在普通用户的情况下工作得很好,但在用户想要破坏我的应用程序的情况下就不行了。 这就是问题所在。当用户每秒发送大量

我有以下控制器方法,当且仅当没有打开的订单时(打开的订单状态为=0,关闭的订单状态为=1),创建新订单

此方法绑定到特定的路由

Route::post('/create', 'OrderController@create');
客户端向该路由发出ajax请求。 这背后的逻辑非常简单:我希望用户一次只有一个活动订单,所以用户必须在创建新订单之前执行一些操作来关闭以前的订单。 下面的代码在普通用户的情况下工作得很好,但在用户想要破坏我的应用程序的情况下就不行了。 这就是问题所在。当用户每秒发送大量这样的请求时(我只在GoogleChromeDev控制台中使用以下脚本)

使用事务可以显著减少插入的行数(通常甚至可以按预期工作-只允许插入一行,但并不总是如此)

  • 我创建了一个中间件,它跟踪最后一个用户请求是何时发出的,并将此信息存储在会话中
  • 它根本没有帮助,因为它只是将问题转移到中间件级别

  • 我还尝试了一些奇怪的东西:
  • 这种方法在许多情况下都有效,尤其是与事务结合使用时,但有时它仍然允许最多插入2行(30种情况中有1-2种情况)。而且对我来说它看起来很奇怪。 我考虑过队列,但正如laravel doc所说,它们是用于耗时的任务的。我还考虑了表锁定,但对于普通用户来说,它似乎很奇怪,性能也会受到影响。 我相信对于这个问题存在着简洁明了的解决方案,但我在谷歌上找不到任何合理的东西,也许我遗漏了一些非常明显的东西?你能帮忙吗?
    另外,在我的应用程序中有很多类似的情况,我真的很想找到一些通用的解决方案,以解决并发执行不仅对数据库,而且对会话、缓存、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()$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();