6. Laravel 路由缓存

说明

Laravel 提供一个路由缓存功能,只需要调用一个命令即可生成。文档里有这样的介绍:

基于闭包的路由无法被缓存。要使用路由缓存。你需要将任何闭包路由转换成控制器路由。
如果你的应用只使用了基于控制器的路由,那么你应该利用路由缓存。使用路由缓存将极大地减少注册所有应用路由所需的时间。某些情况下,路由注册的速度甚至会快 100 倍。 —— 来自: 控制器《Laravel 6.x 中文文档》

一个使用感受是,路由缓存一般在有大量路由的情况,例如几百上千个路由的时候,优化效果非常明显。

路由初始化逻辑

处理路由相关代码见 config/app.php 文件中的 providers 数组里加载了 App\Providers\RouteServiceProvider 继承于 Illuminate\Foundation\Support\Providers\RouteServiceProvider 类,在此类的 boot() 方法里,有如下逻辑:

vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php

<?php
.
.
.
class RouteServiceProvider extends ServiceProvider
{
    .
    .
    .
    public function boot()
    {
        // 先设置好控制器命名空间
        $this->setRootControllerNamespace();

        // 1. 如有缓存直接加载
        if ($this->routesAreCached()) {
            $this->loadCachedRoutes();

        // 2. 否则解析路由
        } else {
            $this->loadRoutes();

            $this->app->booted(function () {
                $this->app['router']->getRoutes()->refreshNameLookups();
                $this->app['router']->getRoutes()->refreshActionLookups();
            });
        }
    }
    .
    .
    .
}

代码解析请见注释,接下来我们大致分解下缓存与否的两种情况。

没有缓存的情况

在没有缓存的情况,会调用 loadRoutes() ,此方法里调用了 app/Providers/RouteServiceProvider.php 文件里的 mapApiRoutes()mapWebRoutes() 方法,将 routes 文件夹中的 api.phpweb.php 中设置的路由进行解析。

每一个 routes/web.phproutes/api.php 中设置的路由,都会被解析成为 Illuminate/Routing/Route 对象,解析的过程需要处理路由命名、中间件、路由组、指定控制器动作、确定正确的 HTTP 方法和请求参数等,将所有的路由入口处理完成后,再重新合并到成为一个 Illuminate/Routing/RouteCollection 对象。

最后,调用 $this->app->booted() 设置闭包,这个闭包会在应用启动完成后被调用。闭包函数里调用 refreshNameLookups()refreshActionLookups() ,刷新生成的路由对象,以防止在程序启动过程中第三方注册的提供器里动态注册了路由,或者重写了控制器动作。

存在缓存的情况

路由缓存是将处理好的路由集合 Illuminate/Routing/RouteCollection 集合对象,序列化后缓存到 bootstrap/cache/routes.php 文件中。因为无法对闭包(匿名函数)进行序列化,所以如上指出的,只支持控制器路由。

上面我们提到过的,此对象是所有解析后的 Illuminate/Routing/Route对象的集合,在缓存文件的最顶部有这样的注释:

/*
|--------------------------------------------------------------------------
| Load The Cached Routes
|--------------------------------------------------------------------------
|
| Here we will decode and unserialize the RouteCollection instance that
| holds all of the route information for an application. This allows
| us to instantaneously load the entire route map into the router.
|
*/

译为:我们可以解析和反序列化来得到一个 RouteCollection 对象,此对象里包含应用中的所有路由。此举允许我们极速地加载所有路由映射。

也就是说,有了缓存以后,无需在每次应用启动时解析每一个路由映射,因此来提高应用的性能。

优先加载缓存,会有两个问题需要注意,第一个是在 routes/api.php 或者 routes/web.php 等路由文件中新增路由后,需要手动重新缓存。第二个问题是如果你安装了第三方扩展包,他们可能会在 Provider 中注册路由,也是需要手动重刷缓存才能起作用。

重新缓存同样执行:

$ php artisan route:cache

Larabbs 第一次运行以上命令,应该会遇到以下报错:

路由缓存

以上报错的重点是:Unable to prepare route [{$this->uri}] for serialization. Uses Closure. ,提示在准备缓存的时候,碰到使用闭包的情况。这是因为我们的 routes/api.php 中有一个闭包路由,注释掉即可:

routes/api.php

<?php

// use Illuminate\Http\Request;

// Route::middleware('auth:api')->get('/user', function (Request $request) {
//     return $request->user();
// });

重新运行命令:

路由缓存

在开发环境下,设置路由缓存会带来诸多不便,可以使用以下命令清除缓存:

$ php artisan route:clear

还有另外一种更加直接的方式,就是直接删除 bootstrap/cache/routes.php 缓存文件。

测验优化效果

开始之前,我们先做下版本标记,方便等会的代码回滚:

$ git add . && git commit -m "commented api route closure"

路由统计

在项目根目录下,执行:

$ php artisan route:list -c | wc -l | awk '{print $1 - 4}'

上面命令会统计在你的应用里注册了多少个路由,Larabbs 项目会输出 80 多个路由。

接下来我们创建 500 个路由作为测试。500 个路由是什么概念呢?

Larabbs 作为一个论坛程序的最基本版,就已经 80 多个路由了。Laravel 程序很容易到达 500 个路由,作者所在的技术服务公司,就接触过不少超过这个值的项目,有的项目甚至一千多个路由。所以 500 个路由是一个比较合理的测试的值。(扩展阅读:https://learnku.com/articles/33457)

根目录下执行以下命令:

$ for i in $(seq -f "%03g" 1 500); do echo "Route::get('test$i/{id}/{query}','Auth\LoginController@testtest$i')->name('test$i')->middleware('auth');" >> routes/web.php; done

以上命令会在 routes/web.php 中插入 500 个路由,打开此文件即可看到:

路由缓存

再次执行我们的路由计算命令:

$ php artisan route:list -c | wc -l | awk '{print $1 - 4}'

可以看到输出了新增的路由:

路由缓存

注意:没有看见新增 500 个路由的同学,可以使用 route:clear 命令清空缓存后再查看。

测试缓存效果

我们可以开始测试缓存的效果了,命令行执行以下命令确保没有缓存:

$ php artisan route:clear

没有缓存的情况下:

路由缓存

$ php artisan route:cache

开启路由缓存的情况下:

路由缓存

总结

经过以上的试验,我们得知路由数量超过一定数量后,优化效果还是很明显的。优化需要的代价也很低,只需要执行一个命令即可。

如果你在项目里遇到不支持闭包的情况,解决起来也很简单,你无须因此而放弃路由缓存。只需重写闭包逻辑到控制器即可。

使用路由缓存时,需时刻留意路由是否被缓存,建议养成习惯:「开发环境不使用路由缓存,生产环境中使用自动部署工具,在每一次提交代码后自动运行 route:cache 命令」。以此来避免不必要的混乱。

最后我们清理下项目,为下一个文章做准备:

$ git checkout .

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
发起讨论 查看所有版本


暂无话题~