【问题标题】:Prevent Concurrent Writes in Laravel在 Laravel 中防止并发写入
【发布时间】:2016-07-18 03:46:02
【问题描述】:

我有一个 laravel 5.2 应用程序,当客户到达特定位置时,它会收到来自客户的 POST。有时,客户端会同时触发两个相同的请求,但使用相同的记录(最后一个事件,不属于这些重复请求的一部分)来决定如何处理。

对于每个请求,我试图通过检查该用户的最后一个事件来确定客户端是否已经登录或退出同一位置,从而防止重复签入/签出。

例如:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Checkin;
use App\Models\Population;
use App\Http\Requests;

class CheckinApiController extends Controller {

    public function post(Request $request) {

        $uuid = $request->uuid;
        $isCheckin = $request->isCheckin;
        $locationId = $request->locationId;

        // get the last checkin for user matching this UUID
        $lastCheckin = Checkin::where("uuid", $uuid)
                               ->orderBy('updated_at', 'desc')
                               ->first();

        if ($isCheckin == $lastCheckin->isCheckin 
            && $locationId == $lastCheckin->locationId) {
/* Scenario 1:
 * if this post event is the same as the last record's checkin type
 * (checkin/checkout), and the locationIds are the same, this is a 
 * duplicate. Return an error code and message
 * In this case, both the duplicate requests see the same record,
 * and both are handled as duplicates
 */
            abort(403, 'duplicate');
        } else if ($isCheckin == true 
                   && $isCheckin == $lastCheckin->isCheckin 
                   && $locationId != $lastCheckin->locationId) {

/* Scenario 2:
 * this is a checkin event, but the last event for this user was
 * a checkin at a different location
 * we create a mock 'checkout' for this request, and 
 * set the updated_at field as current time minus a few seconds
 * BUT... in the case of duplicates, both duplicates see the 
 * last event, so both duplicates handle this the same way, and i
 * get TWO $missedCheckin records.
 */

            $missedCheckin = Checkin::create([
                'locationId' => $lastCheckin->locationId,
                'isCheckin' => false,
                'uuid' => $uuid,
                'updated_at' => time() - 10,
            ]);
        }

        // write this checkin event
        $checkin = Checkin::create([
            'locationId' => $locationId,
            'isCheckin' => $isCheckin,
            'uuid' => $uuid
        ]);

        // adjust population on another model
        $population = Population::firstOrCreate([
            'location_id' => $locationId
        ]);
        // increment or decrement population based on 
        // if this is a checkin or checkout, 
        // omitted here but mentioned as it is another 
        // database transaction on a different model

        // $responseDataSet is a dictionary with info to tell the client to present to the user
        return response()->json($responseDataSet); 
    }
}

有没有办法可以暂停可能的重复记录,允许第一条记录通过此过程,然后才允许第二条记录通过(这将被视为重复)?

我尝试在处理之前放入以下等待随机数毫秒,但似乎随机数生成器在相同的请求期间返回相同的随机数:

$msToSleep = 1 * random_int(500, 100000);
usleep($msToSleep);

客户端不一定需要知道结果,所以我不需要返回除了 200 状态代码之外的任何内容,尽管最终我可能想要返回创建的有效签入对象。

但是我需要一组重复请求的第二个请求才能看到 $lastCheckin 是第一个重复的结果,正确处理(有时它可能是重复的,但相隔几分钟),并进行相应处理.

【问题讨论】:

  • 用户如何触发两个事件?在客户端阻止这种情况不是更容易吗?
  • 我不确定,正在尝试追查;他们在异步线程上响应位置更新并在后台发布签入事件。
  • 我会弄清楚并修复根本问题,而不是尝试创建修复程序来处理它。

标签: php database rest laravel concurrency


【解决方案1】:

Laravel 5.2 有一个不错的 API for Queues,这可能是您想要查看的内容。从客户端触发的异步事件应该排队以防止重复。

您可能想查看官方Laravel Documentation 中“Pushing Jobs Onto The Queue”和“Delayed Jobs”的文档。

【讨论】:

  • 这可能会起作用,据我了解,如果我将它们发送到队列中,它们将以同步方式执行。因此,据我了解,对 POST 签入的每个请求都会排队,并向客户端提供任意响应,确认已收到请求;同时,在队列中,执行确定是否确实是有效签入所需的逻辑,或者是否可以执行重复,结果不提供给客户端(或作为推送通知提供给客户端) ?
  • 这就是我最终要做的;将 Laravel 配置为使用数据库驱动的队列,抓取和操作输入数据,将其传递给工作人员,并立即返回指示签入已排队等待处理的响应。在队列作业中,我执行了所有验证逻辑,据我所知,我没有得到任何重复项——它们似乎相隔几秒钟。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-06
  • 1970-01-01
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 2022-05-03
  • 1970-01-01
相关资源
最近更新 更多