首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么Cache::lock()在Laravel 7中返回false?

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

Stack Overflow用户
提问于 2020-04-25 18:46:13
回答 3查看 5.6K关注 0票数 8

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

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

代码语言:javascript
运行
复制
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);
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-05-01 06:26:30

所以首先是一些背景。

正如您正确提到的那样,互斥(互斥)锁的目的是通过确保只有一个线程或进程进入临界截面来防止争用条件。

但是首先,什么是关键部分?

请考虑以下代码:

代码语言:javascript
运行
复制
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检查,并且都成功地退出,但是这可能导致用户有负余额,或者在没有更新余额的情况下双取款(取决于进程的不同步程度)。

问题是操作需要采取多个步骤,并且可以在任何给定步骤被中断。换句话说,操作不是原子

这是互斥锁解决的关键部分问题。您可以修改上面的内容以使其更安全:

代码语言:javascript
运行
复制
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);
    }

}

值得指出的有趣之处是:

  1. 原子 (或线程安全)操作不需要这样的保护。
  2. 我们放置在锁获取和释放之间的代码可以被视为已“转换”为原子操作。
  3. 获取锁本身需要线程安全或原子操作。

在操作系统级别,互斥锁通常使用为此特定目的构建的原子处理器指令来实现,例如原子测试集操作。这将检查是否设置了一个值,如果没有设置,则将其设置。如果您只是说锁本身就是值的存在,那么它作为互斥体工作。如果存在锁,则采取锁,如果锁不存在,则通过设置值来获取锁。

Laravel以类似的方式实现了这些锁。它利用了某些缓存驱动程序提供的"set (如果不是已经设置)“操作的原子性质,这就是为什么锁只有在那些特定的缓存驱动程序存在时才能工作。

然而,最重要的是:

在测试和设置锁中,锁本身是正在测试是否存在的缓存密钥.如果设置了密钥,则锁定将被接受,并且通常无法重新获取.通常,锁是用“旁路”实现的,在这种情况下,如果同一个进程多次尝试获得相同的锁,则锁会成功。这称为重入互斥,允许在关键部分使用相同的锁对象,而不必担心将自己锁在外部。当关键部分变得复杂并且跨越多个函数时,这是非常有用的。

现在,您的逻辑有两个缺陷:

  1. 对锁和值都使用相同的键才是破解锁的原因。在锁类比中,您试图将您的贵重物品存储在一个保险箱中,这个保险箱本身就是您的贵重物品的一部分。那怎么可能。
  2. 您在关键部分之外有if (Cache::store('memcached')->has('post_' . $post_id)) {,但是它本身应该是关键部分的一部分。

要解决这个问题,您需要对锁使用与缓存条目不同的键,并在关键部分中移动has检查:

代码语言:javascript
运行
复制
$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耗尽内存时),进程会突然终止,因此无法运行任何清理代码。锁的持续时间可以确保即使在这种情况下也能释放锁,并且应该将锁的持续时间设置为锁合理持有的最大绝对时间。

票数 23
EN

Stack Overflow用户

发布于 2020-04-28 15:46:28

Cache::lock('post_' . $post_id, 10)->get()返回false,因为'post_' . $post_id被锁定了,所以锁还没有释放。

所以你需要先释放锁:

代码语言:javascript
运行
复制
Cache::lock('post_' . $post_id)->release()
// or release a lock without respecting its current owner
Cache::lock('post_' . $post_id)->forceRelease(); 

然后再试一次,它将返回true

并建议使用try catchblock设置指定的时限,Laravel将等待此时限。将抛出一个Illuminate\Contracts\Cache\LockTimeoutException,可以释放锁。

代码语言:javascript
运行
复制
use Illuminate\Contracts\Cache\LockTimeoutException;

$lock = Cache::lock('post_' . $post_id, 10);

try {
    $lock->block(5);
    ...
    Cache::put('post_' . $post_id, $post_data, 5 * 60);
    $lock->release();
    // Lock acquired after waiting maximum of 5 seconds...
} catch (LockTimeoutException $e) {
    // Unable to acquire lock...
} finally {
    optional($lock)->release();
}
代码语言:javascript
运行
复制
Cache::lock('post_' . $post_id, 10)->block(5, function () use ($post_id, $post_data) {
    // Lock acquired after waiting maximum of 5 seconds...
    ...
    Cache::put('post_' . $post_id, $post_data, 5 * 60);
    $lock->release();
});
票数 3
EN

Stack Overflow用户

发布于 2022-05-12 16:39:45

在我的例子中,我的Redis配置导致了使Cache:lock总是返回false的问题。这是因为我在配置文件上重命名命令DELFLUSHDB,这是Laravel用来释放锁的。

我认为重命名命令会提高安全性,但它会在应用程序级别造成问题。因此,如果有人使用Redis作为驱动程序,那么不要重命名DELFLUSHDB。我需要一个小时才能弄清楚,希望它能帮助到其他人。

Debian中类似于/etc/redis/redis.conf的配置文件

代码语言:javascript
运行
复制
rename-command FLUSHDB ""
rename-command DEL ""
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61430799

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档