多个请求同时绕过检查

时间:2018-05-27 22:14:53

标签: php laravel

基本上我有一个用PHP(Laravel)构建的在线游戏。它的功能非常简单。当玩家装备物品时,它会检查是否已配备相同类型的物品。如果没有配备相同类型的物品,它将继续装备物品。

这可以通过简单地将Inventory模型上的装备标志更改为1来完成。像这样

        if($alreadyequippeditem == false){
        //equip current item
        $inventory = \App\Inventory::find($item->pivot->id);
        $inventory->equipped = 1;
        $inventory->save();
        }

然而,如果一个人足够快,一个人可以同时装备两个(或甚至更多)同一时间的物品。基本上绕过支票。

在我看来,这两个请求正在由服务器在同一时间处理,并且它们在完全相同的时间装备这些项目并且它基本上绕过了检查。这可以解释为什么可以配备两个项目,因为在请求运行时,他们不会相互认识。我可能错了,但这是我能想到的唯一解释。

我该如何解决这个问题?有没有办法限制它,所以只有1个请求同时发送?当我构建具有核心PHP且没有框架的系统时,我无法回想起曾经遇到过这个问题,因此我不知道它是否由laravel配置引起。我知道我可以使用队列绕过它(尽管我还没有深入研究过它),但我更愿意找到一个在整个站点范围内工作的解决方案,因为这个特定的问题影响了系统的许多方面。

1 个答案:

答案 0 :(得分:3)

问题(竞争条件)

您所体验的是race condition的经典教科书示例:

  

竞争条件是设备发生的不良情况   或系统尝试同时执行两个或多个操作,   但由于设备或系统的性质,操作必须   按正确的顺序完成,以便正确完成。

包含所有必需品的示例修复

幸运的是,这不是一个新问题,也不是一个很难解决的问题。特别是使用Laravel时:

<?php
namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Inventory;
use App\Player;
use Request;


class BustRaceConditionsController extends Controller
{
    public function EquipItem(Request $request)
    {
        \DB::beginTransaction();

        $player = $player->where("id", $request->user()->player_id)->lockForUpdate()->first();

        $success = true;

        try {

            /* some logic here to check if item is equiped or not */

            if (itemIsEquiped()) {

                $success = false;

            } else {

                /* lets equip the item, then save it, and then we need to commit it to the DB */

                $player->itemEquip = $item;
                $player->save();

                \DB::commit();

            }


        } catch (\Exception $e) {

            $success = false;
            \DB::rollback();

        }
        return ['success' => $success];
    }
}
  1. 这是一个非常粗略的例子,说明如何在Laravel中完成,但实质上发生的是我们开始DB transaction,它允许我们基本上确保一切正常,或什么都没有。这在这种情况下特别有用。
  2. 接下来,我们从模型中获取所需的所有数据,但请注意我在方法链中使用lockForUpdate()。这是为了确保在我的更改提交或失败之前,其他任何内容都无法读取或写入,从而释放锁定。
  3. 最后,我们需要将更改提交到数据库,或者最终使更改失败并将所有内容回滚。
  4. 我希望这有帮助!