php 如何在更新会话值时防止竞争条件?

pgvzfuti  于 2023-06-28  发布在  PHP
关注(0)|答案(2)|浏览(157)

在我的Controller中,我需要检查会话是否已过期,如果是,更新值:

public function some_method(Request $request)
{
    // get previous last activity
    $session_last_activity = session('session_last_activity');
    // update last activity to now
    session(['session_last_activity'] => now());

    if ($session_expiry_date < now()->subMinutes(10)) {
        $some_value = Str::random(30);
        session(['some_value' => $some_value]);
    } else {
        $some_value = session('some_value');
    }

    // proceed to update or create some record on the DB
}

当会话过期,并且对同一页有多个快速请求时,会出现问题。然后,对于那些立即请求,每个请求创建一个新的随机字符串并更新会话。
因此,最后一个请求将具有最后一个会话值,然而,到那时,数据库创建了多个记录,因为对于这些请求中的每个请求,会话都过期了一会儿。
例如,用户在会话过期时快速单击example.com/page1两次。现在有2个请求读取会话过期,设置一个新键,并使用2个不同的键创建2个记录,即使应该只有一个。
如何在不使用缓存的情况下防止这种情况发生,或者不在DB端(即使用DB锁),而是在代码端?

gmxoilav

gmxoilav1#

  • 根据您的问题更新答案更新 *:

一种方法是使用存储在会话本身中的标志来指示请求是否已经在更新会话的过程中。

use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;

public function some_method(Request $request)
{
    $session_expiry_date = session('session_expiry_date');

    if ($session_expiry_date < now()) {
        $lockKey = 'session_update_lock';
        
        // Check if the session update lock flag is set
        if (!Session::has($lockKey)) {
            // Set the lock flag to prevent other requests from updating the session
            Session::put($lockKey, true);
            
            $some_value = Str::random(30);
            
            // Update the session with the new value
            session(['some_value' => $some_value]);
            
            // Proceed to update or create the record on the DB
            // ...
            
            // Clear the lock flag to allow other requests to update the session
            Session::forget($lockKey);
        } else {
            // Another request is already updating the session, so retrieve the value
            $some_value = session('some_value');
        }
    } else {
        $some_value = session('some_value');
    }

    // Proceed with the rest of the code
}
vybvopom

vybvopom2#

要锁定会话,您可以在路由上使用block。每当有许多同时的请求传入时,将为此请求获取会话锁定。
所以,如果这个请求是针对example.com/page1的,那么代码片段将是:

Route::get('/page1', function () {
    // your code
})->block(5, 5);

但是,要使其生效,您必须使用支持原子锁的缓存驱动程序。引用文档:
若要利用会话阻塞,应用程序必须使用支持原子锁的缓存驱动程序。目前,这些缓存驱动程序包括memcached、dynamodb、redis和数据库驱动程序。此外,您不得使用Cookie会话驱动程序。

相关问题