在网络开发领域,速度和性能是必须具备的。无论你是在建立一个网站、软件即服务(SaaS),还是定制的网络软件,重要的是它的加载速度要快,以保持你的用户满意。为了帮助实现理想的速度,我们可以使用缓存。
缓存是一种将数据存储在 "缓存 "或高速存储层中的方法。它通常(但不总是)存储在内存中;因此,从它那里存储和获取数据比从数据库或文件存储中获取数据更快。
例如,让我们想象一下,我们有一些需要执行的数据库查询,需要花费很长的时间或使用大量的内存来运行。为了减少这种开销,我们可以执行查询,然后将其结果存储在缓存中。这意味着,当我们下次需要运行这个查询时,我们可以从缓存中抓取结果,而不是再次执行数据库查询。
同样的,我们也可以用这种方法来缓存外部API请求的响应。例如,让我们想象一下,你需要向一个汇率API发出请求,以找到两种货币在给定日期和时间的汇率。我们可以在获取一次结果后将其缓存,这样如果我们再次需要这个汇率,就不需要再做任何请求了。
Laravel提供了一个简单而强大的API来与缓存进行交互。默认情况下, Laravel支持使用Redis,Memcached,DynamoDB, 数据库, 文件, 和数组进行缓存.
使用缓存的好处
如上所述, 当有效使用时, 缓存可以减少你的文件存储系统, 数据库, 和应用服务器的工作负荷.例如,让我们想象一下,你有一个复杂的数据库查询,是CPU和内存密集型的。如果这个查询不是经常运行,可能不会有太大的问题。但是,如果这个查询经常运行,你可能会开始注意到你的应用程序的性能受到影响。资源重的查询会减慢给定的查询速度,并可能影响你系统中其他用户的其他部分。
因此,为了减少这个查询引起的基础设施开销,我们可以执行它,然后将其结果存储在缓存中。通过这样做,当我们下次需要获取这些结果时,我们就可以从缓存中获取,而不需要再次运行数据库查询。
如果我们需要运行一个昂贵的PHP脚本或定期读取一个文件,我们也可以应用这个相同的逻辑。通过缓存脚本的输出或文件的内容,我们可以减少应用服务器的开销。
同样地,如上所述,我们可以缓存来自外部API请求的响应,这样我们就不需要进行重复的查询。通过减少查询,我们也可以减少遇到API可能施加的节流限制或每月请求限制的机会。
正如我们所知,加载时间每天都在变得更加重要,尤其是现在谷歌正在使用加载时间作为排名因素。研究表明,用户倾向于离开加载时间超过三秒的页面。让我们想象一下,我们有一个数据库查询,需要几秒钟的时间来执行。 现在, 几秒钟可能一开始看起来并不坏; 但如果你把这几秒钟与你的Laravel应用程序的其他部分加载页面的时间结合起来, 你可以看到它可能开始超过三秒的阈值.
所以, 由于从缓存中获取数据的速度非常快, 我们可以存储查询的结果, 这样我们就不需要再次执行这个耗时的查询。
在缓存中存储数据
现在我们已经介绍了什么是缓存以及使用缓存的好处,让我们来探讨一下如何在我们的Laravel应用程序中使用它。在这篇文章中, 我们将使用极其简单的查询, 但他们仍然应该解释Laravel中缓存的整体概念。
假设我们有一个方法来查询数据库,获取所有符合给定条件的用户,如本例所示:
public function getUsers($foo, $bar)
{
return User::query()
->where('foo', $foo)
->where('bar', $bar)
->get();
}
如果我们想更新这个方法,让它缓存这个查询的结果,我们可以使用forever() ,这个方法会获取用户查询的结果,然后将结果存储在缓存中,没有过期。下面的例子显示了我们如何做到这一点。
public function getUsers($foo, $bar)
{
$users = User::query()
->where('foo', $foo)
->where('bar', $bar)
->get();
Cache::forever("user_query_results_foo_{$foo}_bar_{$bar}", $users);
return $user;
}
缓存通常是一个键值对存储,这意味着它有一个唯一的键,与一个值(实际的缓存数据)相对应。因此,每当你试图在缓存中存储任何东西或检索它时,你需要确保你使用的是一个唯一的键,这样你就不会覆盖任何其他缓存数据。
正如你在上面的例子中所看到的,我们把$foo 和$bar 参数附加到了缓存密钥的最后。这样做使我们能够为每个可能被使用的参数创建一个唯一的缓存密钥。
有些时候,你可能想把你的数据存储在数据库中,但有一个过期日期。如果你要缓存一个昂贵的查询结果,用于仪表盘或报告的统计,这就很有用。
因此,如果我们想把上面的用户查询缓存10分钟,我们可以使用put() 方法:
public function getUsers($foo, $bar)
{
$users = User::query()
->where('foo', $foo)
->where('bar', $bar)
->get();
Cache::put("user_query_results_foo_{$foo}_bar_{$bar}", $users, now()->addMinutes(10));
return $users;
}
现在,10分钟过后,该项目将过期,无法从缓存中获取(除非后来重新添加)。
从缓存中获取数据
假设你想更新上面例子中的用户查询,检查结果是否已经被缓存了。如果有,我们可以从缓存中获取并返回。否则,我们可以执行数据库查询,把结果缓存10分钟,然后再返回。要做到这一点,我们可以使用下面的代码。
public function getUsers($foo, $bar)
{
$cacheKey = "user_query_results_foo_{$foo}_bar_{$bar}";
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
$users = User::query()
->where('foo', $foo)
->where('bar', $bar)
->get();
Cache::put($cacheKey, $users, now()->addMinutes(10));
return $users;
}
尽管这看起来仍然是可读的, 我们可以使用Laravel的remember() 方法来进一步简化代码, 降低其复杂性.这个方法的工作方式和我们上面的例子一样。
public function getUsers($foo, $bar)
{
return Cache::remember("user_query_results_foo_{$foo}_bar_{$bar}", now()->addMinutes(10), function () use ($foo, $bar) {
return User::query()
->where('foo', $foo)
->where('bar', $bar)
->get();
});
}
在一个请求的有效期内缓存数据
有的时候, 你可能需要在同一个请求中多次运行同一个查询.作为一个非常基本的例子,让我们想象一下,你想在上面的例子中缓存用户查询,但只是在请求的持续时间内。这样,你仍然可以得到缓存的速度优势和减少资源的使用,但不必担心数据会在其他请求中持续存在。
要做到这一点,我们可以使用Laravel提供的array 缓存驱动。这个驱动将数据存储在一个PHP数组中, 这意味着一旦请求运行完毕, 缓存的数据将被删除.
为了说明我们如何在请求的有效期内缓存上面的用户查询,我们可以使用下面的方法。
public function getUsers($foo, $bar)
{
return Cache::store('array')->remember("user_query_results_foo_{$foo}_bar_{$bar}", function () use ($foo, $bar) {
return User::query()
->where('foo', $foo)
->where('bar', $bar)
->get();
});
}
正如你在例子中看到的,我们用store('array') ,选择array 缓存驱动。
使用Laravel的缓存命令
由于Laravel的运行方式,它启动框架,并在每次请求时解析路由文件。这需要读取文件, 解析它的内容, 然后以一种你的应用程序可以使用和理解的方式保持它.所以, Laravel提供了一个命令来创建一个单一的路由文件, 可以更快地解析:
php artisan route:cache
但请注意, 如果你使用这个命令并改变了你的路由, 你需要确保运行下面的命令:
php artisan route:clear
这将删除缓存的路由文件,以便你较新的路由可以被注册。
与路由缓存类似, 每次有请求时, Laravel都会被启动, 你项目中的每个配置文件都会被读取和解析.所以, 为了防止每个文件都需要被处理, 你可以运行以下命令, 这将创建一个缓存的配置文件:
php artisan config:cache
就像上面的路由缓存一样, 不过, 你需要记住每次更新你的.env文件或配置文件时都要运行下面的命令:
php artisan config:clear
同样, Laravel也提供了另外两个命令, 你可以用它们来缓存你的视图和事件, 这样它们就可以在Laravel应用被请求的时候预先编译并准备好.为了缓存事件和视图, 你可以使用以下命令:
php artisan event:cache
php artisan view:cache
就像所有其他的缓存命令一样, 不过, 你需要记住, 每当你对你的代码做任何修改时, 都要通过运行以下命令来破坏这些缓存:
php artisan event:clear
php artisan view:clear
缓存的常见误区
尽管缓存在性能方面有很大的好处,但如果实施不当,也会导致很多问题。
例如,如果缓存的数据没有在正确的时间被破译,就会导致过时的、陈旧的数据不正确。为了理解这个问题, 让我们想象一下,我们的Laravel应用程序有一些设置存储在数据库中,我们在每次页面加载时都会获取这些设置。所以, 为了提高速度, 我们决定将这些设置缓存10分钟。如果我们忘了在每次更新设置的时候破坏缓存, 可能会导致设置在10分钟内不正确, 当它被自动破坏的时候.
此外,如果在不需要缓存的地方使用它,就很容易增加代码库的复杂性。正如上面的例子所示, 缓存在Laravel中是很容易实现的.然而, 可以说, 如果你没有通过缓存一个特定的查询或方法获得太多的性能增益, 你可能不需要使用它.在错误的地方添加过多的缓存会导致难以追踪的bug和不可预测的行为。因此,最好只在有明显改善的地方添加。
此外, 如果你在你的Laravel应用程序中添加缓存, 这往往意味着你需要添加一个缓存服务器, 如Redis, 到你的应用程序的基础设施.使用这种方法可能会引入一个额外的攻击载体, 以及在服务器上打开一个额外的端口, 但如果你的应用程序的基础设施安全配置良好, 它不应该是一个问题.然而,这绝对是需要记住的事情,特别是如果你正在缓存个人身份信息(PII),因为你不想冒任何缓存数据因攻击而被泄露的风险。
总结
希望这篇文章已经清楚地展示了什么是缓存,以及你如何在自己的Laravel应用程序中实现它以提高性能。正如你所看到的,由于Laravel提供的API,缓存可以非常容易地添加到你的应用程序中。然而, 当你添加它的时候,记住最常见的陷阱是很重要的,比如记得要破除缓存,以防止陈旧或过时的数据。