共计 9479 个字符,预计需要花费 24 分钟才能阅读完成。
转载自 Laravel 论坛:https://learnku.com/laravel/t…
Laravel 有很多货色。然而快不是其中之一。让咱们学习一些优化技巧,以放慢运行速度!
自从 Laravel 诞生以来,没有一个 PHP 开发人员不受她的影响。他们是喜爱 Laravel 提供的疾速开发的高级或中级开发人员,或者是因为市场压力而被迫学习 Laravel 的高级开发人员。
不管怎样,不可否认的是,Laravel 曾经振兴了 PHP 生态系统(我确定,如果没有 Laravel,早就来到了 PHP 世界了)。
对 Laravel 的评估节选
然而,因为 Laravel 全力以赴让您的事件变得简略,这意味着它在底层做了大量工作,以确保您作为开发人员能有一个舒服的编程体验。Laravel 所有看似「神奇」的性能都有一层又一层的代码,每当运行一个性能时都须要启动这些代码层。甚至是一个简略的异样都会深究到底层(从谬误那里开始,始终到内核):
对于一个视图中仿佛是编译谬误的状况,有 18 个函数调用要跟踪。我集体遇到过 40 个的,如果您应用其余库和插件,则可能会更多。
重点是,默认状况下,这样层层嵌套的代码,使得 Laravel 速度很慢。
Laravel 有多慢?
说实话,这个问题根本无法答复,起因有几个。
首先,目前还没有公认的、主观的、正当的规范来掂量网络应用的速度。与什么相比更快或更慢?在什么条件下?
第二,一个 Web 利用取决于很多货色(数据库、文件系统、网络、缓存等),所以议论速度是很愚昧的。一个十分快的 Web 利用,如果有一个十分慢的数据库,那么它就是一个十分慢的 Web 利用。
但这种不确定性正是基准测试受欢迎的起因。只管它们毫无意义(参见 这里 和 这里),但它们提供了 一些 参考框架,帮忙咱们防止怄气。因而,最好有所保留,让咱们对 PHP 框架之间的速度有一个谬误的、粗略的意识。
依据这个相当值得尊敬的 GitHub 源码,以下是 PHP 框架的比照状况。
你可能基本不会留神到 Laravel 在这里(即便你真的很致力地眯着眼睛),除非你把你的眼光投到最尾部。是的,敬爱的敌人们,Laravel 排在最初!当初,天经地义的,这些「框架」中的大多数都不是很实用,甚至没有什么用途,但它的确通知咱们,与其余更风行的框架相比,Laravel 是如许的慢。
通常状况下,这种「慢」在利用中不会呈现,因为咱们日常的 Web 利用很少达到很高的数据量。然而一旦达到了(比方高达 200-500 以上的并发量),服务器就会开始阻塞而死。这时候即便扔再多的硬件也解决不了问题,基础架构费用迅速攀升,你对云计算的崇高理想轰然倒塌。
不过,嘿嘿,振作起来吧!这篇文章并不是讲什么不能做,而是讲什么能够做。
好消息是,你能够做很多事件来让你的 Laravel 利用更快。几倍的速度。是的,不是开玩笑。你能够让同样的代码库变得疾速,每个月节俭几百美元的基础设施 / 托管费用。怎么做?让咱们开始吧。
四种类型的优化
在我看来,优化能够在四个不同的层面上进行(当波及到 PHP 利用时,就是):
- 语言层面:这意味着你应用更快的语言版本,并防止语言中特定的性能 / 编码格调,使你的代码速度变慢。
- 框架层面:这些是咱们在本文中要波及的内容。
- 基础设施层面:调整你的 PHP 过程管理器、Web 服务器、数据库等。
- 硬件层面:转向更好、更快、更弱小的硬件主机提供商。
所有这些类型的优化都有其存在的意义(例如,php-fpm 的优化是十分要害和弱小的)。但本文的重点是纯正的第 2 类优化:那些与框架相干的优化。
顺便说一下,这些编号背地没有任何理由,也不是一个公认的规范。我只是编了这些。请千万不要援用我的话说:「咱们的服务器须要 type-3 优化」,否则你的团队负责人会杀了你,找到我,而后把我也杀了。
当初,咱们终于到了应许之地。
要留神 n+1 数据库查问
n+1 查问问题是应用 ORM 时常见的问题。Laravel 有其弱小的 ORM,叫 Eloquent,它是如此的丑陋,如此的不便,以至于咱们经常遗记了看是怎么回事。
思考一个十分常见的场景:显示指定客户列表下的所有订单。这在电子商务系统和任何须要显示与某些实体相干的所有实体的列表中十分常见,
咱们能够设想有这样一个控制器:
class OrdersController extends Controller
{
// ...
public function getAllByCustomers(Request $request, array $ids) {$customers = Customer::findMany($ids);
$orders = collect(); // new collection
foreach ($customers as $customer) {$orders = $orders->merge($customer->orders);
}
return view('admin.reports.orders', ['orders' => $orders]);
}
}
太好了!更重要的是,优雅,漂亮。????????
可怜的是,用 Laravel 编写这样的代码是一种灾难性的办法。
起因如下。
当咱们应用 ORM 查找给定的客户实体时,会生成这样一个 SQL 查问语句:
SELECT * FROM customers WHERE id IN (22, 45, 34, . . .);
这与预期的完全一致。后果,所有返回的行都被存储在控制器函数中的汇合 $customers
中。
当初咱们逐个循环解决每个客户,并获取他们的订单。这将执行上面的查问……
SELECT * FROM orders WHERE customer_id = 22;
……有多少客户就有多少次。
换句话说,如果咱们须要获取 1000 个客户的订单数据,那么执行的数据库查问总数将是 1(用于获取所有客户的数据)+1000(用于获取每个客户的订单数据)=1001。这就是 n+1 这个名字的由来。
咱们能够做得更好吗? 当然能够!通过应用预加载,咱们能够强制 ORM 执行 JOIN,并在一次查问中返回所有须要的数据!就像这样:
$orders = Customer::findMany($ids)->with('orders')->get();
由此产生的数据结构是一个嵌套构造,当然,但订单数据能够很容易地提取进去。在这种状况下,产生的单个查问是这样的。
SELECT * FROM customers INNER JOIN orders ON customers.id = orders.customer_id WHERE customers.id IN (22, 45, ...);
ps: 我感觉原作者了解有误,预查问应用的 where in, 产生的语句应该是这样:
SELECT * FROM customers WHERE id IN (22, 45, ...);
SELECT * FROM orders WHERE customer_id IN(22, 45, ...);
而后在循环插入到对应的对象中。
当然,一次查问比多查问一千次要好。设想一下,如果有一万个客户要解决,会产生什么状况!或者说,如果咱们还想显示每个订单中蕴含的我的项目,那几乎就是天方夜谭!记住,这个技术的名字叫预加载,它简直在任何时候都能派上用场。
缓存配置!
Laravel 的灵活性的起因之一是它有大量的配置文件,这些文件是框架的一部分。想要扭转图片的存储形式 / 地位?
好吧,只有批改 config/filesystems.php
文件就能够了(至多写到这里)。想要应用多个队列驱动?能够在 config/queue.php
中随便形容。我刚刚统计了一下,发现针对框架的不同方面有 13 个配置文件,保障你无论想改什么都不会悲观。
鉴于 PHP 的个性,每当一个新的 Web 申请进来,Laravel 就会被唤醒,启动所有的货色,并解析 所有 的配置文件来找出这次该如何做不同的事件。如果这几天什么都没变,那就太傻了!每次申请都要重建配置文件是一种节约,这是能够 (实际上,必须) 防止的,解决的方法是 Laravel 提供的一个简略的命令:
php artisan config:cache
这样做的目标是把所有可用的配置文件合并成一个文件,并缓存在某个中央以便疾速检索。下一次有 Web 申请的时候,Laravel 会简略地读取这个繁多的文件并开始工作。
也就是说,配置缓存是一个极其奥妙的操作,可能会在你的背后炸开。最大的陷阱是一旦你收回这个命令,除了配置文件之外,其余中央的 env()
函数调用都会返回 null
!
认真想想的确有情理。如果你应用配置缓存,你就是在通知框架:「你晓得吗,我感觉我曾经把货色设置得很好了,我 100% 确定我不心愿它们扭转。」换句话说,你心愿环境放弃动态,这就是 .env
文件的作用。
说到这里,这里有一些铁定的、神圣的、不可违反的配置缓存规定:
- 只在生产零碎上做。
- 只有在你十分十分确定要解冻配置的状况下才做。
- 万一出了问题,用
php artisan cache:clear
撤销设置。 - 祷告对企业造成的损失不是很大!
缩小主动加载的服务
为了帮忙大家, Laravel 在唤醒时加载了大量的服务, 这些服务在 config/app.php
文件中作为 'providers'
数组键的一部分。让咱们来看看我的状况:
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
我再一次数了数,一共列出了 27 项服务!当初,你可能须要所有的服务,但不太可能。
例如,我当初正好在构建一个 REST API,这意味着我不须要 Session Service Provider、View Service Provider 等。而且因为我是依照本人的形式来做一些事件,而不是依照框架的默认值来做,所以我也能够禁用 Auth Service Provider、Pagination Service Provider、Translation Service Provider 等。总而言之,对于我的用例来说,这些简直有一半是不必要的。
认真扫视一下你的利用吧。它是否须要所有这些服务提供者?然而看在上帝的份上,请不要自觉地正文掉这些服务,而后推送到生产中去!运行所有的测试,在开发机和暂存机上手动查看,并且在扣动扳机之前要十分十分偏执。
明智地应用中间件堆栈。
当你须要对传入的 Web 申请进行一些自定义解决时,创立一个新的中间件就是答案。当初,关上 app/Http/Kernel.php
并将中间件粘在 web
或 api
堆栈中是很有诱惑力的;这样一来,它就会在整个应用程序中变得可用,而且如果它没有做一些侵入性的事件(例如,像日志或告诉)。
然而,随着应用程序的增长,如果所有(或大多数)这些全局中间件都存在于每个申请中,那么这个全局中间件的汇合可能会成为应用程序的一个无声累赘,即便没有业务起因。
换句话说,要小心你在哪里增加 / 利用新的中间件。在全局范畴内增加一些货色可能会更不便,但从久远来看,性能惩办是十分高的。我晓得如果每次有新的变动都要有选择地利用中间件,你要接受的苦楚,但这是我迫不得已接受的苦楚,也是我所举荐的!
防止应用 ORM (有时)
尽管 Eloquent 让 DB 交互的很多方面变得愉悦,但它是以速度为代价的。作为一个映射器,ORM 不仅要从数据库中获取记录,还要实例化模型对象,并用列数据对其进行填充。
所以,如果你做一个简略的 $users = User::all()
,比方有 10000 个用户,框架会从数据库中获取 10000 行记录,并在外部做 10000 个 new User()
,并用相干数据填充他们的属性。这是大量的工作在幕后进行,如果数据库是你的利用成为瓶颈的中央,绕过 ORM 有时是个好主见。
这对于简单的 SQL 查问来说尤其如此,在这种状况下,你必须跳很多的圈子,写一个又一个的闭包,但最终还是能失去一个高效的查问。在这种状况下,最好做一个 DB::raw()
,而后手工写查问。
依据 这个 的性能钻研, 即便是简略的插入,Eloquent 也会随着记录数量的减少而变慢:
尽量应用缓存
Web 利用优化中最激进的机密之一就是缓存。
对于老手来说,缓存的意思是事后计算和存储低廉的后果(低廉的 CPU 和内存使用量),并在反复雷同的查问时简略地返回。
例如,在一个电商商店里,可能会遇到,在 200 万种产品中,大多数时候人们都会对那些新鲜出炉的、在肯定价格范畴内的、针对特定年龄段的产品感兴趣。在数据库中查问这些信息是很节约的——因为查问的内容不会常常变动,所以最好把这些后果存储在咱们能够快速访问的中央。
Laravel 内置反对多种类型的缓存。除了应用缓存驱动和从底层构建缓存零碎外,你可能还想应用一些 Laravel 包,不便模型缓存、查问缓存等。
然而请留神, 在肯定的简化用例之外, 预制的缓存包可能会带来更多的问题, 而不是解决这些问题.
优先选择内存缓存
当你在 Laravel 中缓存一些货色时,你有几个选项能够抉择将须要缓存的计算结果存储在哪里。这些选项也被称为 缓存驱动。所以,尽管应用文件系统来存储缓存后果是可能的,也是齐全正当的,但这并不是缓存的真正目标。
现实状况下,你心愿应用内存中(齐全活在 RAM 中)的缓存,比方 Redis、Memcached、MongoDB 等,这样在较高的负载下,缓存就能起到至关重要的作用,而不是本人成为瓶颈。
当初,你可能会认为领有 SSD 磁盘和应用 RAM 棒简直是一样的,但还差得远。即便是非正式的 基准测试也显示,在速度方面,RAM 优于 SSD 的 10-20 倍。
在缓存方面,我最喜爱的零碎是 Redis。它的速度 快得离谱(每秒 10 万次读取操作是很常见的),对于十分大的缓存零碎,能够很容易地演变成一个 集群。
缓存路由
就像应用程序的配置一样,路由不会随着工夫的推移而扭转,是缓存的现实抉择。如果你像我一样无法忍受大文件,并且最终把你的 web.php
和 api.php
宰割成几个文件的话,这一点尤其实用。一个简略的 Laravel 命令就能够把所有可用的路由打包并保存起来,不便当前的拜访:
php artisan route:cache
而当你最终要减少或扭转路由时,只需这样做即可。
php artisan route:clear
图像优化和 CDN
图片是大多数网络应用的外围和灵魂。偶合的是,它们也是最大的带宽耗费者,也是导致应用程序 / 网站速度慢的最大起因之一。如果你只是简略地将上传的图片天真地存储在服务器上,而后以 HTTP 响应的形式发送回来,你就会让一个微小的优化机会溜走。
我的第一个倡议是不要在本地存储图片——有数据失落的问题要解决,而且取决于你的客户在哪个天文区域,数据传输可能会十分迟缓。
相同,抉择像 Cloudinary 这样的解决方案,它能够主动动静调整和优化图像的大小。
如果这不可能,应用相似 Cloudflare 的货色来缓存和服务图像,同时它们存储在你的服务器上。
如果连这一点都做不到,调整一下你的网络服务器软件,压缩资产并疏导访问者的浏览器去缓存货色,就会有很大的不同。上面是一个 Nginx 配置的片段。
server {
# file truncated
# gzip compression settings
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
# browser cache control
location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|eot)$ {
expires 1d;
access_log off;
add_header Pragma public;
add_header Cache-Control "public, max-age=86400";
}
}
我晓得图片优化与 Laravel 无关,但这是一个如此简略而弱小的技巧 (而且常常被忽视),所以我忍不住了。
主动加载器优化
主动加载是 PHP 中一个整洁的、并不古老的性能,它能够说是援救了这门语言的末日。尽管如此,通过破译给定的命名空间字符串来寻找和加载相干类的过程是须要工夫的,在生产部署中,如果须要高性能,能够防止这个过程。再一次,Laravel 有一个繁多命令的解决方案来解决这个问题:
composer install --optimize-autoloader --no-dev
与队列交朋友
队列 是指当有很多事件时,你如何解决这些事件,而且每件事件都须要几毫秒能力实现。一个很好的例子是发送电子邮件——在网络应用中,一个宽泛的用例是当用户执行一些操作时,收回几封告诉邮件。
例如,在一个新推出的产品中,你可能心愿每当有人下单超过肯定值时,公司领导层(大概 6 - 7 个电子邮件地址)就会收到告诉。假如你的邮件网关能在 500ms 内响应你的 SMTP 申请,那么在订单确认启动之前,用户须要期待 3 - 4 秒。一个十分蹩脚的用户体验,我置信你会批准。
补救的方法是在工作进来的时候就把它们存储起来,通知用户所有都很顺利,而后再解决它们(几秒钟)。如果呈现谬误,在发表失败之前,排队的工作能够重试几次。
尽管队列零碎使设置复杂化了一些 (并减少了一些监控开销),但它在古代 Web 利用中是不可短少的。
资源优化 (Laravel Mix)
对于你的 Laravel 利用中的任何前端资源,请确保有一个管道能够编译和最小化所有的资源文件。那些对 Webpack,Gulp,Parcel 等打包器零碎很相熟的人不须要费神,但如果你还没有这样做,Laravel Mix 是一个牢靠的举荐。
Mix 是一个轻量级的 (诚实说, 很讨人喜欢!) 围绕 Webpack 的打包器,它能够解决你所有的 CSS,SASS,JS 等文件。一个典型的 .mix.js
文件能够像这样小,但依然能够施展出微小的作用。
const mix = require('laravel-mix').mix.js('resources/js/app.js', 'public/js');
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
当您筹备部署生产环境并运行 npm run production
时,它将主动解决导入,最小化,优化以及整个工作流程。Mix 不仅关怀传统的 JS 和 CSS 文件,而且还关怀您在应用程序工作流程中可能应用的 Vue 和 React 组件。
更多信息参考 这里!
论断
性能优化与其说是迷信,不如说是艺术 —— 晓得如何做以及做多少比做什么更重要。也就是说,在 Laravel 利用中能够优化的内容和数量是有限的。
但无论您做什么,我都心愿留给您一些临别的倡议 —— 优化应该在有充沛的理由时进行,而不是因为它听起来不错,也不是因为您对 超过 100,000 个用户的应用程序的性能抱有偏执,而实际上只有 10 个用户。
如果你不确定是否须要优化你的利用,那你就不要去捅这个马蜂窝。一个能失常运行的利用,尽管有时感觉很无趣,但却做了它必须做的事件,这比一个优化成突变体混合型超级机器却时不时会失败的利用要可取十倍。
探讨请返回业余的 Laravel 论坛:https://learnku.com/laravel/t…