九、扩展 Laravel

用任何编程语言构建的框架的一个特点是使用各种组件。正如我们在前面几章中看到的,框架为软件开发人员提供了许多不同的预构建工具来完成诸如身份验证、数据库交互和 RESTful API 创建等任务。

然而,关于框架,可伸缩性问题始终是信息技术领域任何管理者最担心的问题。与任何使用现有代码的库一样,总会有一些开销,一些膨胀,一些超出实际需要的东西。

可扩展性问题

框架不容易扩展的原因有很多。让我们看一个简短的问题列表:

  • 一个问题是不必要的代码和包与正在构建的实际应用没有直接关系。不是每个项目都需要身份验证,例如,每个 MySQL 都需要一个数据库驱动程序。作为框架核心的一部分的包必须被监控兼容性问题。
  • 设计模式、观点和学习曲线通常会阻止新团队成员快速熟悉。随着项目的扩展,日常开发需要增长,软件开发团队必须不断招募已经对框架有些熟悉或者至少理解其基本概念的成员。
  • 框架安全问题需要持续监控框架社区的网站或存储库,以收集所需的紧急安全更新信息。甚至底层的 web 服务器和操作系统本身都需要被监控。在撰写本文时,Laravel 5.1 即将发布,它将需要 PHP 5.5,因为 PHP 5.4 将在 2015 年晚些时候被宣布寿终正寝。
  • 像雄辩这样的表单总是会增加少量的开销,因为代码需要首先从雄辩转换成流畅的查询生成器,然后转换成 PDO 代码。显然,使用面向对象的方法来查询数据库是一个明智的选择,但这是有代价的。

走向企业

尽管可能存在障碍,Laravel 仍将是企业未来的强有力选择。PHP 7 将会非常快,Zend Framework 3 等框架已经在其路线图中宣布了 PHP 7 优化。此外,通过使用 FastCGI 流程管理器 ( FPM )、NGINX 网络服务器,并允许 PHP 的缓存机制正常工作,随着其复兴的继续和新开发人员对其核心的贡献,应用的可扩展性将继续在企业领域得到更多的接受。

在本章中,您将学习如何让 Laravel 在可伸缩性问题最为重要的企业环境中表现得更好。首先,将讨论路由缓存。然后,您将了解许多工具、技术,甚至新的微框架,这些都是以可伸缩性为首要目标开发的。具体来说,我们将讨论从 Laravel、 Lumen 导出的官方微框架。最后,你将通过一种叫做读取写入的技术来学习如何有效地使用数据库。

就代码库的大小而言,与 Zend 或 Symfony 相比,Laravel 拥有最小的代码库之一,尽管它确实使用了一些 Symfony 组件。如前几章所述,从 Symfony 基于组件的思想中得到启示,不同的包被移除以减轻占用空间。例如,默认情况下不再包括 HTML、SSH 和注释包。

路由缓存

路线缓存有助于加快速度。在 Laravel 5 中,引入了缓存机制来加快执行速度。

示例routes.php如下所示:

Route::post('reserve-room', 'ReservationController@store');

Route::controllers([
  'auth' => 'Auth\AuthController',
  'password' => 'Auth\PasswordController',
]);
Route::post('/bookRoom','ReservationsController@reserve', ['middleware' => 'auth', 'domain'=>'booking.hotelwebsite.com']);

Route::resource('rooms', 'RoomsController');

Route::group(['middleware' => ['auth','whitelist']], function()
{

  Route::resource('accommodations', 'AccommodationsController');
  Route::resource('accommodations.amenities', 'AccommodationsAmenitiesController');
  Route::resource('accommodations.rooms', 'AccommodationsRoomsController');
  Route::resource('accommodations.locations', 'AccommodationsLocationsController');
  Route::resource('amenities', 'AmenitiesController');
  Route::resource('locations', 'LocationsController');
});

通过运行以下命令,Laravel 将缓存路线:

$ php artisan route:cache

然后,将它们放入以下目录:

/vendor/routes.php

以下是结果文件的一小部分:

<?php

/*

| 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.
|
*/

app('router')->setRoutes(
  unserialize(base64_decode('TzozNDoiSWxsdW1pbmF0ZVxSb3V0aW5nXF JvdXRlQ29sbGVjdGlvbiI6NDp7czo5OiIAKgByb3V0ZXMiO2E6Njp7czozOiJH RVQiO2E6NTA6e3M6MToiLyI7TzoyNDoiSWxsdW1pbmF0ZVxSb3V0aW5nXFJvdX RlIjo3OntzOjY6IgAqAHVyaSI7czoxOiIvIjtzOjEwOiIAKgBtZXRob2RzIjth OjI6e2k6MDtzOjM6IkdFVCI7aToxO3M6NDoiSEVBRCI7fX
...
Db250cm9sbGVyc1xBbWVuaXRpZXNDb250cm9sbGVyQHVwZGF0ZSI7cjoxNDQx O3M6NTQ6Ik15Q29tcGFueVxIyb2xsZXJzXEhvdGVsQ29udHJvbGxlckBkZXN0c m95IjtyOjE2MzI7fX0='))
);

当文档块状态时,路由以 base64 编码,然后序列化:

unserialize(base64_decode( … ));

这将执行一些预编译。如果我们对文件内容进行 base64 解码,我们将获得序列化数据。以下代码是文件的摘录:

O:34:"Illuminate\Routing\RouteCollection":4:{s:9:"*routes"; a:6:{s:3:"GET";a:50:{s:1:"/";O:24:"Illuminate\Routing\Route": 7:{s:6:"*uri";s:1:"/";s:10:"*methods";a:2:{i:0;s:3:"GET";i:1; s:4:"HEAD";}s:9:"*action";a:5:{s:4:"uses";s:50:"MyCompany \Http\Controllers\WelcomeController@index";s:10:"controller"; s:50:"MyCompany\Http\Controllers\WelcomeController@index"; s:9:"namespace";s:26:"MyCompany\Http\Controllers";s:6:"prefix"; N;s:5:"where";a:0:{}}s:11:"*defaults";a:0:{}s:9:"*wheres"; a:0:{}s:13:"*parameters";N;s:17:"*parameterNames";N; }s:4:"home";O:24:"Illumin…

"MyCompany\Http\Controllers\HotelController@destroy";r:1632;}}

如果存在/vendor/routes.php文件,则使用该文件代替位于/app/Http/routes.phproutes.php文件。如果不再需要使用路由缓存文件,请使用以下artisan命令:

$ php artisan route:clear

该命令将删除缓存的routes文件,并且 Laravel 将再次开始使用/app/Http/routes.php文件。

类型

需要注意的是,如果routes.php文件中使用了任何闭包,那么缓存将会失败。下面是一个路由中的闭包示例:

Route::get('room/{$id}', function(){
  return Room::find($id);
});

无论什么原因都不建议在routes.php文件中使用闭包。为了能够使用路由缓存,请将闭包中使用的代码重新定位到控制器中。

照亮路线

所有这些工作加速了请求生命周期的一个重要部分,即路由。在 Laravel 中,路由类位于illuminate/routing命名空间内:

<?php namespace Illuminate\Routing;
use Closure;
use LogicException;
use ReflectionFunction;
use Illuminate\Http\Request;
use Illuminate\Container\Container;
use Illuminate\Routing\Matching\UriValidator;
use Illuminate\Routing\Matching\HostValidator;
use Illuminate\Routing\Matching\MethodValidator;
use Illuminate\Routing\Matching\SchemeValidator;
use Symfony\Component\Routing\Route as SymfonyRoute;
use Illuminate\Http\Exception\HttpResponseException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 

检查use操作符,很明显路由机制由相当多的类组成。最重要的一行如下:

use Symfony\Component\Routing\Route as SymfonyRoute;

Laravel 使用 Symfony 的路由类。然而,尼基塔·波波夫编写的新路由包已经出现。FastRoute是一个比其他路由包更快的快速请求路由,解决了现有路由包的一些问题。该组件是 Lumen 微框架的主要优势之一。

流明

在苏打水营销术语中,卢蒙可以被认为是拉威尔或拉威尔。除了使用FastRoute布线封装,许多封装已经从 Lumen 中移除,使其最小化并减少其占地面积。

【Laravel 与 Lumen 的比较

Laravel 和 Lumen 中的包装对比如下表所示。运行以下命令时会安装这些软件包:

$ composer update –-no-dev

当开发完成并且应用准备好在服务器上部署时,使用前面的命令。PHPUnit 和 PHPSpec 等工具在这个阶段显然被排除在外。

包装名称对齐,以说明包装在拉弗尔和流明中的位置:

|

Laravel 软件包

|

流明包装

| | --- | --- | | - | nikic/fast-route | | illuminate/cache | - | | illuminate/config | illuminate/config | | illuminate/console | illuminate/console | | illuminate/container | illuminate/container | | illuminate/contracts | illuminate/contracts | | illuminate/cookie | illuminate/cookie | | illuminate/database | illuminate/database | | illuminate/encryption | illuminate/encryption | | illuminate/events | illuminate/events | | illuminate/exception | - | | illuminate/filesystem | illuminate/filesystem | | illuminate/foundation | - | | illuminate/hashing | illuminate/hashing | | illuminate/http | illuminate/http | | illuminate/log | - | | illuminate/mail | - | | illuminate/pagination | illuminate/pagination | | illuminate/pipeline | - | | illuminate/queue | illuminate/queue | | illuminate/redis | - | | illuminate/routing | - | | illuminate/session | illuminate/session | | illuminate/support | illuminate/support | | illuminate/translation | illuminate/translation | | illuminate/validation | illuminate/validation | | illuminate/view | illuminate/view | | jeremeamia/superclosure | - | | league/flysystem | - | | monolog/monolog | monolog/monolog | | mtdowling/cron-expression | mtdowling/cron-expression | | nesbot/carbon | - | | psy/psysh | - | | swiftmailer/swiftmailer | - | | symfony/console | - | | symfony/css-selector | - | | symfony/debug | - | | symfony/dom-crawler | - | | symfony/finder | - | | symfony/http-foundation | symfony/http-foundation | | symfony/http-kernel | symfony/http-kernel | | symfony/process | - | | symfony/routing | - | | symfony/security-core | symfony/security-core | | symfony/var-dumper | symfony/var-dumper | | vlucas/phpdotenv | - | | classpreloader/classpreloader | - | | danielstjules/stringy | - | | doctrine/inflector | - | | ext-mbstring | - | | ext-mcrypt | - |

在编写时,有 51 个包(显示在左栏)使用非开发配置安装在 Laravel 5.0 中。将此软件包数量与 Lumen 中安装的软件包数量进行比较(显示在右栏中)—只有 24 个。

前面提到的nikic/fast-route包是 Lumen 唯一拥有而 Laravel 没有的包。symfony/routing套餐是在 Laravel 找到的免费套餐。

精益应用开发

我们将以为例,一个简单的面向公众的 RESTful API。此 RESTful API 通过GET以 JSON 格式向任何用户显示住宿列表的名称和地址:

  • 如果不使用密码,则不需要ext/mcrypt
  • 如果不进行日期计算,则不需要nesbot/carbon。由于没有 HTML 接口,因此不需要测试应用的 HTML 所涉及的以下库:symfony/css-selectorsymfony/dom-crawler
  • 如果不向用户发送电子邮件,则既不需要illuminate/mail也不需要swiftmailer/swiftmailer
  • 如果不需要与文件系统进行特殊交互,那么就不需要league/flysystem
  • 如果不是要从命令行运行的命令,则不需要symfony/console
  • 如果不需要 Redis,那么illuminate/redis可以省略。
  • 如果不同的环境不需要特定的配置值,那么就不需要vlucas/phpdotenv

类型

vlucas/phpdotenv包是composer.json文件中的建议包。

很明显移除某些包装的决定是经过仔细考虑的,以便在考虑最简单应用的情况下根据需要点亮 Lumen。

读/写

Laravel 还有另一个帮助其在企业中表现出色的机制:读写。这与数据库性能有关,但是功能设置非常容易,任何应用都可以利用它的有用性。

关于 MySQL,最初的 MyISAM 数据库引擎需要在插入、更新和删除期间锁定整个表。这在修改数据和选择查询等待访问这些表的大型操作中造成了巨大的瓶颈。随着 InnoDB 的引入,UPDATEINSERTDELETE SQL 语句只需要行级的锁。这极大地影响了性能,因为 selects 可以从正在进行其他操作的表的不同部分读取。

MySQL 分叉的 MariaDB 声称比传统的 MySQL 性能更快。用 TokuDB 替换数据库引擎会带来更高的性能,尤其是在大数据环境中。

另一种提高数据库性能的机制是使用主/从配置。在下图中,所有操作都在一个表上执行。插入和更新将锁定单行,select 语句将按分配的方式执行。

Read/write

传统数据库表操作

主表

主/从配置使用允许SELECTUPDATEDELETE语句的主表。这些语句修改表或写入表。也可能有多个主表。每个主表都保持持续同步:对任何表所做的更改都需要传递给主表。

从表

从表是主表的从表。它的变化取决于主表。SQL 客户端只能从中执行读取操作(SELECT)。也可能有多个从属表依赖于一个或多个主表。主表将其所有的变化传递给所有的从表。下图显示了主/从设置的基本架构:

Slave table

主机和从机(读/写设置)

这种持续的同步给数据库结构增加了轻微的开销;但是,它具有重要的优势:

由于在从表上只能执行语句,而在主表上可以执行INSERTUPDATEDELETE语句,所以从表可以自由接受许多SELECT语句,而不必“等待”任何涉及相同行的操作完成。

这方面的一个例子是货币汇率或股票价格表。该表将不断地用最新值实时更新,甚至可能每秒更新多次。显然,一个允许许多用户访问这些信息的网站可能会有成千上万的访问者。此外,用于显示这些数据的网页可能会向每个用户发出连续的多次请求。

当存在需要同时访问相同数据的UPDATE语句时,执行许多SELECT语句会稍微慢一些。

通过使用主/从配置,SELECT语句将仅在从表上执行。该表仅接收以极其优化的方式更改的数据。

在使用库(如mysqli)的普通 PHP 中,可能配置了两个数据库连接:

$master=mysqli_connect('127.0.0.1:3306','dbuser','dbpassword','mydatabase');
$slave=mysqli_connect('127.0.0.1:3307','dbuser','dbpassword','mydatabase');

在这个简化的例子中,从机设置在同一台服务器上。在实际应用中,它很可能被设置在另一台服务器上,以利用单独的硬件。

然后,所有涉及语句的 SQL 语句将在从机上执行,而将在主机上执行。

这将给编程工作增加一些开销,因为需要将不同的连接传递到每个 SQL 语句中:

$result= mysqli_real_query($master,"UPDATE exchanges set rate='1.345' where exchange_id=2");
$result= mysqli_query($slave,"SELECT rate from exchanges where exchange_id=2");

在前面的代码示例中,谨慎的做法是记住哪些 SQL 语句应该用于主服务器,哪些 SQL 语句应该用于从服务器。

配置读/写

正如之前所说的,用 English 编写的代码被转换成流畅的查询构建器代码。然后,这些代码被转换成 PDO,这是各种数据库驱动程序的标准包装。

Laravel 通过其读/写配置提供管理主/从配置的能力。这允许程序员编写流畅的查询构建器代码,而不必担心查询是在主表上执行还是在从表上执行。此外,以非主/从配置开始,随后需要扩展到主/从设置的软件项目只需要更改数据库配置的一个方面。数据库配置文件位于config/database.php

作为connections数组的一个元素,带有键mysql的条目将以下列配置创建:

'connections' =>
'mysql' => [
    'read' => [
        'host' => '192.168.1.1',
     'password'  => 'slave-Passw0rd', 
    ],
    'write' => [
        'host' => '196.168.1.2',
    'username'  => 'dbhostusername'    
    ],
    'driver'    => 'mysql',
    'database'  => 'database',
    'username'  => 'dbusername',
    'password'  => 's0methingSecure',
    'charset'   => 'utf8',
    'collation' => 'utf8_unicode_ci',
    'prefix'    => '',
],

读和写分别代表从机和主机。由于参数是级联的,如果用户名、密码和数据库名称相同,那么只需要列出主机名的 IP 地址。但是,任何值都可以被覆盖。在本例中,读操作的密码不同于主操作,写操作的用户名不同于从操作。

创建主/从数据库配置

要设置主/从数据库,请从命令行执行以下步骤。

  1. The first step is to determine which address the MySQL server is bound to. To do this, locate the line of the MySQL configuration file that contains the bind-address parameter:

    ```php bind-address = 127.0.0.1

    ```

    该 IP 地址将被设置为主服务器使用的任何 IP 地址。

  2. 接下来,取消包含server-id的 MySQL 配置文件行的注释,该行最有可能位于/etc/my.cn/etc/mysql/mysql.conf.d/mysqld.cnf

  3. The Unix sed command can perform this easily:

    ```php $ sed -i s/#server-id/server-id/g /etc/mysql/my.cnf

    ```

    类型

    /etc/mysql/my.cnf字符串需要用正确的文件名替换。

  4. Uncomment the line of the MySQL configuration file that contains the server-id:

    ```php $ sed -i s/#log_bin/log_bin/g /etc/mysql/my.cnf

    ```

    类型

    同样,/etc/mysql/my.cnf字符串需要用正确的文件名替换。

  5. 现在,MySQL 需要重启。您可以使用以下命令进行操作:

    ```php $ sudo service mysql restart

    ```

  6. 以下占位符应替换为实际值:

    ```php MYSQLUSER MYSQLPASSWORD MASTERDATABASE MASTERDATABASEUSER MASTERDATABASEPASSWORD SLAVEDATABASE SLAVEDATABASEUSER SLAVEDATABASEPASSWORD

    ```

主服务器设置

以下是设置主服务器的步骤:

  1. 授予从属数据库用户权限:

    ```php $ echo "GRANT REPLICATION SLAVE ON . TO 'DATABASEUSER'@'%' IDENTIFIED BY 'DATABASESLAVEPASSWORD';" | mysql -u MYSQLUSER -p"MYSQLPASSWORD"

    ```

  2. 接下来,必须使用以下命令刷新权限:

    ```php $ echo "FLUSH PRIVILEGES;" | mysql -u MYSQLUSER -p"MYSQLPASSWORD"

    ```

  3. 接下来,使用以下命令切换到主数据库:

    ```php $ echo "USE MASTERDATABASE;" | mysql -u MYSQLUSER -p"DATABASEPASSWORD"

    ```

  4. 接下来,使用以下命令刷新表格:

    ```php $ echo "FLUSH TABLES WITH READ LOCK;" | mysql -u MYSQLUSER -p"MYSQLPASSWORD"

    ```

  5. Display the master database status using the following command:

    ```php $ echo "SHOW MASTER STATUS;" | mysql -u MYSQLUSER -p"MYSQLPASSWORD"

    ```

    记下输出的位置和文件名:

    php POSITION FILENAME

  6. 使用以下命令转储主数据库:

    ```php $ mysqldump -u root -p"MYSQLPASSWORD" --opt "MASTERDATABASE" > dumpfile.sql

    ```

  7. 使用以下命令解锁表格:

    ```php $ echo "UNLOCK TABLES;" | mysql -u MYSQLUSER -p"MYSQLPASSWORD"

    ```

从属服务器设置

以下是设置从服务器的步骤:

  1. 在从属服务器上,使用以下命令创建从属数据库:

    ```php $ echo "CREATE DATABASE SLAVEDATABASE;" | mysql -u MYSQLUSER -p"MYSQLPASSWORD"

    ```

  2. 使用以下命令导入从主数据库创建的转储文件:

    ```php $ mysql -u MYSQLUSER -p"MYSQLPASSWORD" "MASTERDATABASE" < dumpfile.sql

    ```

  3. 现在,MySQL 配置文件使用服务器 id 2:

    php server-id = 2

  4. 在 MySQL 配置文件中,有两行需要取消注释,如下所示:

    ```php

    log_bin = /var/log/mysql/mysql-bin.log

    expire_logs_days = 10 max_binlog_size = 100M

    binlog_do_db = include_database_name

    ```

  5. 你会得到如下结果:

    php log_bin = /var/log/mysql/mysql-bin.log expire_logs_days = 10 max_binlog_size = 100M binlog_do_db = include_database_name

  6. 另外,需要在binglog_do_db下方增加以下一行:

    php relay-log = /var/log/mysql/mysql-relay-bin.log

  7. 现在,MySQL 需要使用以下命令重新启动:

    ```php $ sudo service mysql restart

    ```

  8. 最后,设置主密码。主日志文件和位置将被设置为步骤 5 中记录的文件名和位置。运行以下命令:

    php MASTER_PASSWORD='password', MASTER_LOG_FILE='FILENAME', MASTER_LOG_POS= POSITION;

总结

在本章中,您学习了如何通过路由缓存加快路由速度。您还学习了如何用 Lumen 完全替换 Laravel,Lumen 是完全从 Laravel 派生的微框架。最后,我们讨论了 Laravel 如何使用读写配置来充分利用主从配置。

Symfony 2.7 于 2015 年 5 月发布。它是一个长期支持版本。此版本将支持 36 个月。此后不久,泰勒·奥特威尔决定创作第一部 LTS 版的《拉弗尔》。这是 Laravel 在企业空间稳固定位的第一个标志。拉腊维尔背后还没有正式的公司,就像赛姆弗尼和曾德的情况一样。然而,有一个由社区包和服务组成的大型生态系统,如由杰弗里·韦(Jeffrey Way)运营的 Laracasts,他与泰勒密切合作,提供官方培训视频。

此外,泰勒·奥特韦尔还运行了一项名为 Envoyer 的服务,该服务消除了 Laravel 部署的所有初始障碍,并为 Laravel 以及其他类型的现代 PHP 项目提供了零停机部署。

随着 Laravel 5.1 LTS 的到来,许多新的和令人兴奋的事情将发生在 Laravel 身上。使用许多社区包的决定允许泰勒和他的社区专注于框架最重要的方面,而不必重新发明轮子和维护许多冗余包。此外,Laravel Collective 维护了已被弃用的包——即使最终从 Laravel 中删除的包也将在未来许多年内继续得到支持。

除了方便的服务,如 Envoyer,下一章将介绍一个最近出现的伟大的自动化工具:Elixirs。