为什么Cache :: lock()在Laravel 7中返回false? [英] Why does Cache::lock() return false in Laravel 7?

查看:345
本文介绍了为什么Cache :: lock()在Laravel 7中返回false?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

My framework is Laravel 7 and the Cache driver is Memcached. I want to perform atomic cache get/edit/put. For that I use Cache::lock() but it doesn't seem to work. The $lock->get() returns false (see below). How can I resolve this?

堡垒测试,我重新加载Homestead,并仅运行下面的代码.而且锁定永远不会发生. Cache :: has()是否有可能打破锁定机制?

Fort testing, I reload Homestead, and run only the code below. And locking never happens. Is it possible Cache::has() break the lock mechanism?

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);
}

推荐答案

所以首先要有一些背景知识.

So first of all a bit of background.

您正确提及的互斥锁(mutex)旨在通过防止种族竞争确保只有一个线程或进程曾经输入关键部分.

A mutual exclusion (mutex) lock as you correctly mentioned is meant to prevent race conditions by ensuring only one thread or process ever enters a critical section.

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

But first of all what is a 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 检查,并且都成功提取,但是这可能导致用户的余额为负数或在不更新余额的情况下重复提取资金(取决于流程的异同程度).

The problem here is if two processes run this function concurrently, they will both enter the if check at around the same time, and both succeed in withdrawing, however this might lead the user having negative balance or money being double-withdrawn without the balance being updated (depending on how out of phase the processes are).

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

The problem is the operation takes multiple steps and can be interrupted at any given step. In other words the operation is NOT atomic.

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

This is the sort of critical section problem that a mutual exclusion lock solves. You can modify the above to make it safer:

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);
    }

}

要指出的有趣的事情是:

The interesting things to point out are:

  1. 原子(或线程安全)操作不需要这种保护
  2. 我们在锁获取和释放之间放置的代码可以被视为已转换"为原子操作.
  3. 获取锁本身必须是线程安全的或原子操作.
  1. Atomic (or thread-safe) operations don't require such protection
  2. The code we put between the lock acquire and release, can be considered to have been "converted" to an atomic operation.
  3. Acquiring the lock itself needs to be a thread-safe or atomic operation.

在操作系统级别,通常使用为此目的而构建的原子处理器指令来实现互斥锁,例如原子测试并设置操作.这将检查是否设置了值,如果未设置,则设置它.如果您只是说锁本身就是值的存在,那么它就可以用作互斥锁.如果存在,则采用该锁;如果不存在,则通过设置该值来获取该锁.

At the operating system level, mutex locks are typically implemented using atomic processor instructions built for this specific purpose such as an atomic test-and-set operation. This would check if a value if set, and if it is not set, set it. This works as a mutex if you just say the lock itself is the existence of the value. If it exists, the lock is taken and if it's not then you acquire the lock by setting the value.

Laravel以类似的方式实现锁.它利用某些缓存驱动程序提供的如果尚未设置"操作的原子性质,这就是为什么只有在那些特定的缓存驱动程序在那里时锁才起作用的原因.

Laravel implements the locks in a similar manner. It takes advantage of the atomic nature of the "set if not already set" operations that certain cache drivers provide which is why locks only work when those specific cache drivers are there.

但这是最重要的事情:

在测试并设置"锁中,锁本身是要测试存在性的缓存键.如果设置了密钥,则将锁定并且无法通常重新获得该锁定.通常,锁是通过旁路"实现的,如果同一过程多次尝试获取相同的锁,则成功.这称为重入互斥体,它允许在整个关键部分使用相同的锁定对象,而不必担心锁定自己.当关键部分变得复杂且跨越多个功能时,这很有用.

In the test-and-set lock, the lock itself is the cache key being tested for existence. If the key is set, then the lock is taken and cannot generally be re-acquired. Typically locks are implemented with a "bypass" in which if the same process tries to acquire the same lock multiple times it succeeds. This is called a reentrant mutex and allows to use the same lock object throughout your critical section without worrying about locking yourself out. This is useful when the critical section becomes complicated and spans multiple functions.

现在这是您在逻辑上有两个缺陷的地方:

Now here's where you have two flaws with your logic:

  1. 为锁和值使用相同的键将破坏您的锁.在锁类比中,您试图将贵重物品存储在保险箱中,保险箱本身就是贵重物品的一部分.那是不可能的.
  2. 您在关键部分之外有 if(Cache :: store('memcached')->> has('post_'.$ post_id)){部分.

要解决此问题,您需要为锁使用与缓存条目不同的密钥,并在关键部分移动 has 检查:

To fix this issue you need to use a different key for the lock than you use for the cached entries and move your has check in the critical section:


$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();
}

finally 部分中具有 $ lock-> release()的原因是,在万一发生异常的情况下,您仍然希望释放锁而不是保持卡住".

The reason for having the $lock->release() in the finally part is because in case there's an exception you still want the lock being released rather than staying "stuck".

这篇关于为什么Cache :: lock()在Laravel 7中返回false?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆