如何为 Laravel API 构建缓存层

假设您正在构建一个 api 来提供一些数据,您发现 get 响应非常慢。您已尝试优化查询,通过频繁查询的列对数据库表建立索引,但仍然没有获得所需的响应时间。下一步是为您的 api 编写一个缓存层。这里的“缓存层”只是中间件的一个奇特术语,它将成功的响应存储在快速检索存储中。例如redis、memcached 等,然后对 api 的任何进一步请求都会检查数据是否在存储中可用并提供响应。

先决条件

拉拉维尔redis

在我们开始之前

我假设如果您已经到达这里,您就知道如何创建 laravel 应用程序。您还应该有一个本地或云 redis 实例可供连接。如果你本地有 docker,你可以在这里复制我的 compose 文件。另外,有关如何连接到 redis 缓存驱动程序的指南,请阅读此处。

创建我们的虚拟数据

帮助我们查看缓存层是否按预期工作。当然,我们需要一些数据,假设我们有一个名为 post 的模型。所以我将创建一些帖子,我还将添加一些复杂的过滤,这些过滤可能是数据库密集型的,然后我们可以通过缓存进行优化。

现在让我们开始编写我们的中间件:

我们通过运行创建我们的中间件骨架

php artisan make:middleware cachelayer

然后将其注册到 api 中间件组下的 app/http/kernel.php 中,如下所示:

    protected $middlewaregroups = [        'api' => [            cachelayer::class,        ],    ];

但是如果你运行的是 laravel 11。请在 bootstrap/app.php 中注册它

->withmiddleware(function (middleware $middleware) {        $middleware->api(append: [            apphttpmiddlewarecachelayer::class,        ]);    })

缓存术语

缓存命中:当在缓存中找到请求的数据时发生。cache miss:当请求的数据在缓存中找不到时发生。 缓存刷新:清除缓存中存储的数据,以便可以用新数据重新填充。缓存标签:这是redis独有的功能。缓存标签是一种用于对缓存中的相关项目进行分组的功能,可以更轻松地同时管理和使相关数据失效。生存时间(ttl):这是指缓存对象在过期之前保持有效的时间。一种常见的误解是认为每次从缓存访问对象(缓存命中)时,其过期时间都会重置。然而,事实并非如此。例如,如果 ttl 设置为 5 分钟,则缓存对象将在 5 分钟后过期,无论在该时间内被访问多少次。 5 分钟结束后,对该对象的下一个请求将导致在缓存中创建一个新条目。

计算唯一的缓存键

所以缓存驱动程序是一个键值存储。所以你有一个键,那么值就是你的json。因此,您需要一个唯一的缓存键来标识资源,唯一的缓存键还有助于缓存失效,即在创建/更新新资源时删除缓存项。我的缓存键生成方法是将请求 url、查询参数和正文转换为对象。然后将其序列化为字符串。将其添加到您的缓存中间件中:

class cachelayer {    public function handle(request $request, closure $next): response    {    }    private function getcachekey(request $request): string    {        $routeparameters = ! empty($request->route()->parameters) ? $request->route()->parameters : [auth()->user()->id];        $allparameters = array_merge($request->all(), $routeparameters);        $this->recursivesort($allparameters);        return $request->url() . json_encode($allparameters);    }    private function recursivesort(&$array): void    {        foreach ($array as &$value) {            if (is_array($value)) {                $this->recursivesort($value);            }        }        ksort($array);    }}

让我们逐行查看代码。

首先我们检查是否有匹配的请求参数。我们不想为 /users/1/posts 和 /users/2/posts 计算相同的缓存键。如果没有匹配的参数,我们传入用户的 id。这部分是可选的。如果您有类似 /user 的路由,它返回当前经过身份验证的用户的详细信息。在缓存键中传入用户 id 是合适的。如果不是,你可以将其设置为空数组([])。 然后我们获取所有查询参数并将其与请求参数合并然后我们对参数进行排序,为什么这个排序步骤非常重要,因为这样我们就可以返回相同的数据,例如 /posts?page=1&limit=20 和 /posts?limit=20&page=1。因此,无论参数的顺序如何,我们仍然返回相同的缓存键。

排除航线

所以取决于您正在构建的应用程序的性质。会有一些您不想缓存的 get 路由,因此我们使用正则表达式创建一个常量来匹配这些路由。这看起来像:

 private const excluded_urls = [    '~^api/v1/posts/[0-9a-za-z]+/comments(?.*)?$~i''];

在这种情况下,这个正则表达式将匹配所有帖子的评论。

配置ttl

为此,只需将此条目添加到您的 config/cache.php

  'ttl' => now()->addminutes(5),

编写我们的中间件

现在我们已经设置了所有初步步骤,我们可以编写中间件代码:

public function handle(request $request, closure $next): response    {        if ('get' !== $method) {           return $next($request);        }        foreach (self::excluded_urls as $pattern) {            if (preg_match($pattern, $request->getrequesturi())) {                return $next($request);            }        }        $cachekey = $this->getcachekey($request);        $exception = null;        $response = cache()            ->tags([$request->url()])            ->remember(                key: $cachekey,                ttl: config('cache.ttl'),                callback: function () use ($next, $request, &$exception) {                    $res = $next($request);                    if (property_exists($res, 'exception') && null !== $res->exception) {                        $exception = $res;                        return null;                    }                    return $res;                }            );        return $exception ?? $response;    }

首先,我们跳过非 get 请求和排除网址的缓存。然后我们使用缓存助手,通过请求 url 标记该缓存条目。我们使用 remember 方法来存储该缓存条目。然后我们通过 $next($request) 调用堆栈中的其他处理程序。我们检查是否有异常。然后返回异常或响应。

缓存失效

当新资源创建/更新时,我们必须清除缓存,以便用户可以看到新数据。为此,我们将稍微调整我们的中间件代码。所以在我们检查请求方法的部分我们添加这个:

if ('get' !== $method) {    $response = $next($request);    if ($response->issuccessful()) {        $tag = $request->url();        if ('patch' === $method || 'delete' === $method) {            $tag = mb_substr($tag, 0, mb_strrpos($tag, '/'));        }        cache()->tags([$tag])->flush();    }    return $response;}

所以这段代码所做的是刷新非 get 请求的缓存。然后,对于 patch 和删除请求,我们将剥离 {id}。例如,如果请求 url 是 patch /users/1/posts/2 。我们正在删除最后一个 id,留下 /users/1/posts。这样,当我们更新帖子时,我们会清除所有用户帖子的缓存。这样用户就可以看到最新的数据。

现在我们已经完成了 cachelayer 的实现。来测试一下吧

测试我们的缓存

假设我们想要检索所有包含链接、媒体的用户帖子,并按喜欢和最近创建的内容对其进行排序。根据 json:api 规范,此类请求的 url 如下所示:/posts?filter[links]=1&filter[media]=1&sort=-created_at,-likes。在包含 120 万条记录的帖子表上,响应时间为:~800ms

如何为 Laravel API 构建缓存层
添加缓存中间件后,我们的响应时间为 41 毫秒

如何为 Laravel API 构建缓存层

非常成功!

优化

另一个可选步骤是压缩我们存储在 redis 上的 json 负载。 json 不是最节省内存的格式,所以我们可以做的是在存储之前使用 zlib 压缩来压缩 json,并在发送到客户端之前解压。
其代码如下所示:

$response = cache()            ->tags([$request->url()])            ->remember(                key: $cachekey,                ttl: config('cache.ttl'),                callback: function () use ($next, $request, &$exception) {                    $res = $next($request);                    if (property_exists($res, 'exception') && null !== $res->exception) {                        $exception = $res;                        return null;                    }                    return gzcompress($res->getcontent());                }            );        return $exception ?? response(gzuncompress($response));

完整代码如下所示:

getMethod();        if ('GET' !== $method) {            $response = $next($request);            if ($response->isSuccessful()) {                $tag = $request->url();                if ('PATCH' === $method || 'DELETE' === $method) {                    $tag = mb_substr($tag, 0, mb_strrpos($tag, '/'));                }                cache()->tags([$tag])->flush();            }            return $response;        }        foreach (self::EXCLUDED_URLS as $pattern) {            if (preg_match($pattern, $request->getRequestUri())) {                return $next($request);            }        }        $cacheKey = $this->getCacheKey($request);        $exception = null;        $response = cache()            ->tags([$request->url()])            ->remember(                key: $cacheKey,                ttl: config('cache.ttl'),                callback: function () use ($next, $request, &$exception) {                    $res = $next($request);                    if (property_exists($res, 'exception') && null !== $res->exception) {                        $exception = $res;                        return null;                    }                    return gzcompress($res->getContent());                }            );        return $exception ?? response(gzuncompress($response));    }    private function getCacheKey(Request $request): string    {        $routeParameters = ! empty($request->route()->parameters) ? $request->route()->parameters : [auth()->user()->id];        $allParameters = array_merge($request->all(), $routeParameters);        $this->recursiveSort($allParameters);        return $request->url() . json_encode($allParameters);    }    private function recursiveSort(&$array): void    {        foreach ($array as &$value) {            if (is_array($value)) {                $this->recursiveSort($value);            }        }        ksort($array);    }}

概括

这就是我今天为您提供的有关缓存的全部内容,祝您构建愉快,并在评论中提出任何问题、意见和改进!

以上就是如何为 Laravel API 构建缓存层的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1318217.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
PHP框架扩展机制在微服务架构中的应用
上一篇 2025年12月12日 03:00:24
PHP 框架性能优化中负载均衡策略
下一篇 2025年12月12日 03:00:37

相关推荐

  • composer require-dev和require有什么不同_Composer Require与Require-Dev区别解析

    require用于声明项目运行必需的依赖,如框架、数据库组件和第三方SDK,这些包会随项目部署到生产环境;2. require-dev用于声明仅在开发和测试阶段需要的工具,如PHPUnit、PHPStan、Faker等,不会默认部署到生产环境;3. 安装时composer install根据环境决定…

    2026年5月10日
    1000
  • 开源免费PHP工具 PHP开发效率提升利器

    推荐开源免费PHP开发工具以提升效率:VS Code、Sublime Text轻量高效,PhpStorm专业强大;调试用Xdebug、Kint、Ray;依赖管理选Composer;代码质量工具包括PHPStan、Psalm、PHP_CodeSniffer;数据库管理可用%ignore_a_1%MyA…

    2026年5月10日
    000
  • 一台服务器上如何同时运行多个UWSGI服务避免冲突?

    多UWSGI服务部署方案:利用Docker实现服务器资源隔离 本文探讨如何在单台服务器上安全运行多个UWSGI服务,避免服务冲突。 问题在于,即使端口不同,两个UWSGI服务(例如:san和san_test)也可能发生冲突,后启动的服务覆盖之前的服务。 理想情况下,san_test应该持续运行,而s…

    2026年5月10日
    000
  • 我有时使用 awk 而不是 Python 的四个原因

    Python 是一门强大的编程语言,但在某些特定场景下,Awk 的优势更为显著,尤其体现在可移植性、生命周期、代码简洁性和与其他工具的互操作性方面。 Python 脚本通常具有良好的可移植性,但并非总能在所有环境中完美运行,例如流行的 Docker 基础镜像 (如 Debian 和 Alpine)。…

    2026年5月10日
    000
  • Bootstrap和MDB固定导航栏遮挡内容:如何优雅地解决页面跳转后内容被遮挡的问题?

    解决bootstrap和mdb固定导航栏遮挡内容的问题 使用Bootstrap和MDB框架构建网站时,固定导航栏遮挡内容是一个常见问题。尤其在页面跳转后,目标内容区域会被导航栏遮挡。本文提供一种优雅的解决方案,无需修改HTML结构,即可在页面跳转后自动调整滚动位置,避免内容被遮挡。 问题:点击导航链…

    2026年5月10日
    000
  • 深入理解 Laravel Session::put:避免常见陷阱与实现表单限流

    本文旨在深入探讨 laravel 框架中 `session::put` 方法的正确用法及其常见误区。针对用户在实现表单提交限流时遇到的问题,详细阐述了 `session::put` 必须提供键值对的原理,并提供了如何在控制器中利用会话机制有效防止重复提交的实战代码示例。通过本文,读者将掌握 lara…

    2026年5月10日
    000
  • Voyager 中关联关系的翻译问题解决方案

    本文档旨在解决在使用 TCGVoyager 管理后台时,关联模型无法正确翻译的问题。主要针对 Laravel 项目中,使用 Voyager 1.4 版本以及 Laravel 8.0 版本,并且已经配置多语言支持的情况下,如何确保关联关系中的可翻译字段能够根据当前应用语言环境进行正确翻译。通过修改 B…

    2026年5月10日
    000
  • 如何用Golang构建无状态微服务 分享Session管理最佳实践

    如何用Golang构建无状态微服务 分享Session管理最佳实践如何用Golang构建无状态微服务 分享Session管理最佳实践如何用Golang构建无状态微服务 分享Session管理最佳实践如何用Golang构建无状态微服务 分享Session管理最佳实践

    构建无状态微服务时,session管理可通过jwt、redis和统一认证中心实现。①使用jwt作为token,客户端存储,服务端无状态;②结合redis记录session元数据,支持主动失效;③设立统一认证中心,中间件校验token;④确保https传输安全并设计token刷新机制。 用 Golan…

    2026年5月10日 用户投稿
    000
  • 怎样用Golang实现一个简单的键值存储 基于文件持久化方案

    怎样用Golang实现一个简单的键值存储 基于文件持久化方案怎样用Golang实现一个简单的键值存储 基于文件持久化方案怎样用Golang实现一个简单的键值存储 基于文件持久化方案怎样用Golang实现一个简单的键值存储 基于文件持久化方案

    要实现一个简单的键值存储系统,需结合golang与文件持久化方案。1. 使用map[string]string作为内存数据结构,选择json或gob进行序列化;2. 围绕map实现crud操作,写入后立即或定时刷新到磁盘,并在启动时加载数据;3. 文件策略可选每次写入刷盘、定时异步刷盘或日志记录变更…

    2026年5月10日 用户投稿
    000
  • C++云计算最佳实践:部署、管理和伸缩性考虑因素

    为了实现 c++++ 云应用程序的有效部署,最佳实践包括:容器化部署,使用 docker 等容器。采用 ci/cd,自动化发布过程。使用版本控制,管理代码更改。实施日志记录和监控,跟踪应用程序运行状况。使用自动扩展,优化资源利用率。利用云管理服务,管理应用程序基础设施。采用水平伸缩和垂直伸缩,根据需…

    2026年5月10日
    000
  • 优化 Laravel Eloquent 查询:高效构建用户排行榜数据

    本教程详细讲解如何优化 Laravel Eloquent 查询以高效生成基于关联记录计数的排行榜。通过识别并消除冗余的 whereHas 子句,并巧妙利用 withCount 的条件闭包,我们能显著提升查询性能,大幅缩短数据获取时间,从而改善用户体验并降低数据库负载。 在 laravel 应用开发中…

    2026年5月10日
    300
  • 使用PHP FirestoreClient发送自定义头部认证令牌的最佳实践

    本文旨在解决php firestoreclient在启用安全规则后遇到的“权限不足”错误。核心内容是,对于服务器端应用,应通过服务账户进行身份验证,并推荐在`firestoreclient`构造函数中使用`keyfilepath`参数明确指定服务账户密钥文件路径,以确保请求能够正确通过firesto…

    2026年5月10日
    000
  • C#的System.IO.Pipelines是什么?如何实现高性能的流处理?

    System.IO.Pipelines通过PipeReader和PipeWriter减少内存分配与拷贝,高效处理流数据,适用于高吞吐、低延迟场景如网络通信和协议解析。 System.IO.Pipelines 是 C# 中用于高效处理流数据的一个库,特别适合高吞吐、低延迟的场景,比如网络通信、文件解析…

    2026年5月10日
    300
  • C++ 框架的未来趋势是什么?

    c++++框架的未来趋势包括:元编程(支持更灵活的代码)、反射和内省(改进调试和可测试性)、并发和并行(提升性能)、分布式计算(适用于分布式系统)。示例代码展示了使用boost.asio(c++网络库)创建http服务器的方法。 C++ 框架的未来趋势 C++ 作为一门强大的系统编程语言,拥有庞大的…

    2026年5月10日
    000
  • WebSocket消息队列处理性能优化

    优化WebSocket性能需解耦通信与业务逻辑,通过消息队列异步处理、二进制序列化、数据压缩、批量发送及动态心跳机制,提升吞吐量并降低延迟。 处理WebSocket消息时,性能瓶颈常出现在消息的接收、处理和分发环节。优化核心在于解耦通信与业务逻辑,并高效管理消息流。 引入消息队列进行异步解耦 直接在…

    2026年5月10日
    000
  • PHP中基于用户角色的页面访问控制实践

    本教程详细讲解如何在PHP应用程序中利用会话(Session)机制实现基于用户角色的页面访问控制。通过正确的session_start()调用、用户登录时的角色信息存储,以及在受保护页面进行严格的会话和角色类型检查,确保只有特定用户(如“manager”)才能访问指定页面,从而有效防止未经授权的访问…

    2026年5月10日
    200
  • 告别重复:使用Laravel Precognition统一前后端API验证

    本文旨在解决在Laravel后端与前端API交互中,如何高效复用后端验证规则的挑战。传统方案常限于表单元素,难以覆盖所有API请求。通过引入Laravel Precognition,开发者能够实现后端验证逻辑在前端的无缝应用,避免规则重复编写,从而提升开发效率与代码一致性,确保所有API请求的数据完…

    2026年5月10日
    200
  • Laravel Session::put 正确用法详解与常见误区规避

    本文详细探讨了 laravel 中 `session::put` 方法的正确用法,特别指出在仅提供键名而未指定值时可能导致会话数据未被正确设置的问题。通过示例代码,阐述了如何为会话数据赋予明确的值,并演示了如何正确地检查和获取会话数据,以确保会话管理功能按预期工作,有效避免常见的会话操作错误。 La…

    2026年5月10日
    000
  • PHP大型配置管理:结构化数据与外部文件实践

    本文旨在探讨在php项目中高效管理大量配置项的最佳实践。我们将摒弃直接创建数百个独立php变量或在配置文件中混合代码的低效方式,转而介绍如何利用php数组以及ini、json、yaml等外部结构化数据格式来组织和加载配置,并结合业务逻辑进行处理,从而提升代码的可维护性、可扩展性和专业性。 在PHP应…

    2026年5月10日
    100
  • PHP中批量为嵌套数组元素添加公共属性的教程

    本教程将详细介绍在php中如何高效地为包含多个关联数组的集合中的每个子数组添加一个或多个新的公共键值对。我们将探讨使用循环和数组合并函数实现这一目标的方法,并提供清晰的代码示例,帮助开发者处理此类数据结构转换。 在PHP开发中,我们经常会遇到处理复杂数据结构的需求,其中一种常见场景是拥有一个由多个关…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信