防止Laravel中的并发写入

时间:2016-07-18 03:46:02

标签: php database rest laravel concurrency

我有一个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是第一个重复项的结果,正确处理(有时可能是重复,但相隔几分钟),并相应地处理

1 个答案:

答案 0 :(得分:2)

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

您可能需要查看&#34; Pushing Jobs Onto The Queue&#34;的文档。和&#34; Delayed Jobs&#34;在官方Laravel Documentation