gpt4 book ai didi

laravel - 为什么 Cache::lock() 在 Laravel 7 中返回 false?

转载 作者:行者123 更新时间:2023-12-03 15:18:47 26 4
gpt4 key购买 nike

我的框架是 Laravel 7,缓存驱动程序是 Memcached。我想执行原子缓存获取/编辑/放置。为此,我使用 Cache::lock()但它似乎不起作用。 $lock->get()返回 false(见下文)。我该如何解决这个问题?

Fort 测试,我重新加载 Homestead,并且只运行下面的代码。并且锁定永远不会发生。可以吗Cache::has()打破锁定机制?

if (Cache::store('memcached')->has('post_' . $post_id)) {
$lock = Cache::lock('post_' . $post_id, 10);
Log::info('checkpoint 1'); // comes here

if ($lock->get()) {
Log::info('checkpoint 2'); // but not here.
$post_data = Cache::store('memcached')->get('post_' . $post_id);
... // updating $post_data..
Cache::put('post_' . $post_id, $post_data, 5 * 60);
$lock->release();
}
} else {
Cache::store('memcached')->put('post_' . $post_id, $initial, 5 * 60);
}

最佳答案

首先是一些背景知识。
mutual exclusion (mutex)正如您正确提到的那样,锁定旨在通过确保只有一个线程或进程进入 critical section 来防止竞争条件。 .
但首先什么是临界区?
考虑这个代码:

public function withdrawMoney(User $user, $amount) {
if ($user->bankAccount->money >= $amount) {
$user->bankAccount->money = $user->bankAccount->money - $amount;
$user->bankAccount->save();
return true;
}
return false;

}
这里的问题是如果两个进程同时运行这个函数,它们都会进入 if大约在同一时间检查,并且都成功提款,但是这可能会导致用户余额为负数或资金被双重提款而不更新余额(取决于流程的异相程度)。
问题是操作需要多个步骤,并且可以在任何给定步骤中断。换句话说 该操作不是原子操作 .
这就是互斥锁解决的临界区问题。您可以修改上述内容以使其更安全:
public function withdrawMoney(User $user, $amount) {
try {
if (acquireLockForUser($user)) {
if ($user->bankAccount->money >= $amount) {
$user->bankAccount->money = $user->bankAccount->money - $amount;
$user->bankAccount->save();
return true;
}
return false;
}
} finally {
releaseLockForUser($user);
}

}
需要指出的有趣的事情是:
  • Atomic (或线程安全)操作不需要这样的保护
  • 我们放在获取锁和释放锁之间的代码,可以认为已经“转换”为原子操作。
  • 获取锁本身需要是线程安全的或原子操作。

  • 在操作系统级别,互斥锁通常使用为此特定目的构建的原子处理器指令来实现,例如原子 test-and-set手术。这将检查是否设置了值,如果未设置,则设置它。如果您只是说锁本身就是值的存在,那么这可以作为互斥锁使用。如果存在,则获取锁,如果不存在,则通过设置值获取锁。
    Laravel 以类似的方式实现锁。它利用了某些缓存驱动程序提供的“如果尚未设置则设置”操作的原子性质,这就是为什么锁仅在那些特定的缓存驱动程序存在时才起作用的原因。
    然而,最重要的是:
    在 test-and-set 锁中,锁本身就是被测试是否存在的缓存键。如果设置了 key ,则锁定被获取并且通常不能重新获得。通常,锁是通过“绕过”实现的,如果同一个进程多次尝试获取同一个锁,它就会成功。这称为 reentrant mutex并允许在整个关键部分使用相同的锁定对象,而不必担心将自己锁定。当临界区变得复杂并跨越多个功能时,这很有用。
    现在你的逻辑有两个缺陷:
  • 对锁和值使用相同的 key 是破坏你的锁的原因。在锁类比中,您试图将贵重元素存放在保险箱中,而保险箱本身就是贵重元素的一部分。这不可能。
  • 您有 if (Cache::store('memcached')->has('post_' . $post_id)) {在临界区之外,但它本身应该是临界区的一部分。

  • 要解决此问题,您需要为锁使用与用于缓存条目不同的 key ,然后移动您的 has检查关键部分:

    $lock = Cache::lock('post_' . $post_id. '_lock', 10);
    try {
    if ($lock->get()) {
    //Critical section starts
    Log::info('checkpoint 1'); // if it comes here

    if (Cache::store('memcached')->has('post_' . $post_id)) {
    Log::info('checkpoint 2'); // it should also come here.
    $post_data = Cache::store('memcached')->get('post_' . $post_id);
    ... // updating $post_data..
    Cache::put('post_' . $post_id, $post_data, 5 * 60);

    } else {
    Cache::store('memcached')->put('post_' . $post_id, $initial, 5 * 60);
    }
    }
    // Critical section ends
    } finally {
    $lock->release();
    }
    拥有 $lock->release() 的原因在 finally部分是因为如果出现异常,您仍然希望锁定被释放而不是保持“卡住”。
    另一件要注意的事情是,由于 PHP 的性质,您还需要设置锁在自动释放之前将被持有的持续时间。这是因为在某些情况下(例如 PHP 内存不足时),进程会突然终止,因此无法运行任何清理代码。锁的持续时间确保即使在这些情况下也能释放锁,并且持续时间应设置为合理持有锁的绝对最长时间。

    关于laravel - 为什么 Cache::lock() 在 Laravel 7 中返回 false?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61430799/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com