本文概述
- 注意n + 1个数据库查询
- 缓存配置!
- 减少自动加载的服务
- 明智地使用中间件堆栈
- 避免使用ORM(有时)
- 尽可能使用缓存
- 首选内存缓存
- 缓存路由
- 图像优化和CDN
- 自动装带器优化
- 使用队列
- 资源优化(Laravel Mix)
Laravel有很多东西。但是快不是其中之一。让我们学习一些交易技巧, 以加快交易速度!
如今, Laravel并没有碰过任何PHP开发人员。他们是喜欢Laravel提供的快速开发的初级或中级开发人员, 或者是由于市场压力而被迫学习Laravel的高级开发人员。
无论哪种方式, 都不能否认Laravel复兴了PHP生态系统(如果没有Laravel, 我肯定会早就离开了PHP世界)。
Laravel自称赞的片段
但是, 由于Laravel向后弯腰为你提供便利, 这意味着在其下方进行了大量工作, 以确保你作为开发人员过上舒适的生活。 Laravel似乎所有起作用的”神奇”功能都有一层又一层的代码, 每次功能运行时都需要将其搅动。即使是一个简单的Exception跟踪, 兔子洞有多深(注意错误从哪里开始, 一直到主内核):
对于其中一个视图中似乎是编译错误的情况, 有18个函数调用要跟踪。我个人有40个, 如果你使用的是其他库和插件, 则可能会更多。
要点是, 默认情况下, 这层代码层使Laravel变慢。
Laravel有多慢?
老实说, 出于几个原因, 根本不可能回答这个问题。
首先, 没有衡量Web应用程序速度的公认, 客观, 明智的标准。相比之下, 更快或更慢?在什么条件下?
其次, Web应用程序依赖于很多东西(数据库, 文件系统, 网络, 缓存等), 以至于谈论速度是愚蠢的。具有非常慢的数据库的非常快的Web应用程序就是非常慢的Web应用程序。 ????
但是这种不确定性正是基准受欢迎的原因。即使它们毫无意义(请参阅此内容), 它们也提供了一定的参考框架并可以帮助我们免于生气。因此, 在准备好几笔盐之后, 让我们对PHP框架的速度有一个错误的粗略了解。
根据这个颇受人尊敬的GitHub资料, 下面是比较PHP框架的方式:
你甚至可能不会在这里注意到Laravel(即使你用力地斜视了一下), 除非你将箱子放到尾巴的尽头。是的, 亲爱的朋友, Laravel排名倒数第二!现在, 理所当然的是, 这些”框架”中的大多数都不是很实用, 甚至没有用, 但它确实告诉我们, 与其他较流行的Laravel相比, Laravel有多呆滞。
通常, 这种”缓慢”不会出现在应用程序中, 因为我们的日常网络应用程序很少会达到很高的数量。但是一旦完成(例如, 并发200-500以上), 服务器就会开始窒息而死。现在是时候甚至不投入更多硬件解决问题了, 基础架构账单攀升的速度如此之快, 以至于你对云计算的崇高理想逐渐崩溃。
但是, 嘿, 振作起来!本文不是关于不能做什么, 而是关于可以做什么。 ????
好消息是, 你可以做很多事情来使Laravel应用更快地运行。快几倍。是的, 没有开玩笑。你可以使同一个代码库成为弹道导弹, 并每月节省数百美元的基础架构/托管费用。怎么样?让我们开始吧。
四种优化
我认为, 可以在四个不同的级别上进行优化(当涉及到PHP应用程序时):
- 语言级别:这意味着你使用该语言的较快版本, 并避免使用该语言的特定编码功能/样式, 这会使你的代码变慢。
- 框架级别:这些是我们将在本文中介绍的内容。
- 基础架构级别:调整你的PHP流程管理器, Web服务器, 数据库等。
- 硬件级别:转向更好, 更快, 功能更强大的硬件托管提供商。
所有这些类型的优化都有自己的位置(例如, php-fpm优化非常关键且功能强大)。但是本文的重点将仅是类型2的优化:与框架有关的优化。
顺便说一句, 编号背后没有任何依据, 也不是公认的标准。我只是编造的。请不要引用我的话说:”我们需要在服务器上进行3型优化”, 否则你的团队负责人会杀死你, 找到我, 然后也杀死我。 ????
现在, 最后, 我们到达了应许之地。
注意n + 1个数据库查询
使用ORM时, n + 1查询问题很常见。 Laravel具有强大的ORM(称为Eloquent), 它非常漂亮, 非常方便, 以至于我们经常忘记看看发生了什么。
考虑一个非常常见的场景:显示给定客户列表下的所有订单的列表。这在电子商务系统和通常需要显示所有与某些实体相关的所有实体的任何报告界面中非常普遍。
在Laravel中, 我们可以想象一个控制器功能可以完成以下工作:
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, . . .);
当然, 单个查询比一千个额外查询好。想象一下, 如果有10, 000个客户要处理, 那会发生什么!如果我们也想展示每个订单中包含的物品, 那就上帝禁止!请记住, 这项技术的名称是渴望加载, 并且几乎总是一个好主意。
缓存配置!
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文件中作为”提供程序”数组键的一部分使用。让我们看看我的情况:
/*
|--------------------------------------------------------------------------
| 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, 这意味着我不需要会话服务提供者, 视图服务提供者等。而且由于我以自己的方式做一些事情, 并且没有遵循框架默认值, 我还可以禁用身份验证服务提供者, 分页服务提供者, 翻译服务提供者等等。总而言之, 对于我的用例而言, 其中几乎有一半是不必要的。
仔细研究一下你的应用程序。是否需要所有这些服务提供商?但是, 为了上帝的缘故, 请不要盲目评论这些服务并投入生产!运行所有测试, 在开发人员和登台计算机上手动检查所有内容, 并在拉动触发器之前非常偏执。 ????
明智地使用中间件堆栈
当你需要对传入的Web请求进行一些自定义处理时, 创建新的中间件便是答案。现在, 很想打开app / Http / Kernel.php并将中间件粘贴在Web或api堆栈中;这样, 它就可以在整个应用程序中使用, 并且如果它没有做侵入性的操作(例如, 记录或通知)。
但是, 随着应用程序的增长, 即使每个请求中都包含所有(或大多数)中间件, 这种全球中间件集合也可能成为应用程序的一个沉重负担。
换句话说, 请注意在何处添加/应用新的中间件。全局添加某些内容可能更方便, 但是从长远来看, 性能损失会很高。我知道, 如果每次有新更改时都选择有选择地应用中间件, 那么你将遭受痛苦, 但是我愿意并推荐它!
避免使用ORM(有时)
尽管Eloquent使数据库交互的许多方面变得令人愉悦, 但它是以速度为代价的。作为映射器, ORM不仅必须从数据库中获取记录, 而且还必须实例化模型对象并使用列数据对它们进行水合(填充)。
因此, 如果你执行简单的$ users = User :: all()并且有10, 000个用户, 则该框架将从数据库中获取10, 000行, 并在内部进行10, 000个新的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
图像是大多数Web应用程序的核心。巧合的是, 他们还是带宽的最大消耗者, 也是应用程序/网站运行缓慢的最大原因之一。如果你只是简单地将上传的图像天真地存储在服务器上, 然后以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
使用队列
队列是当有很多事物时你如何处理事物的方法, 而每个事物都需要几毫秒的时间才能完成。一个很好的例子是发送电子邮件-Web应用程序中一个广泛使用的案例是当用户执行某些操作时发出一些通知电子邮件。
例如, 在新推出的产品中, 你可能希望每当有人下达高于某个特定值的订单时通知公司领导(大约6-7个电子邮件地址)。假设你的电子邮件网关可以在500毫秒内响应SMTP请求, 那么我们说的是等待3-4秒等待用户, 然后再执行订单确认。一个非常糟糕的UX, 我相信你会同意。
补救措施是在作业进入时存储它们, 告诉用户一切顺利, 然后在几秒钟后处理它们。如果有错误, 可以先对已排队的作业重试几次, 然后再声明它们已失败。
积分:Microsoft.com
排队系统使设置有些复杂(并增加了一些监视开销), 但它在现代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')
.sass('resources/sass/app.scss', 'public/css');
当你准备开始生产并运行npm run production时, 它将自动处理导入, 最小化, 优化和整个工作。 Mix不仅照顾传统的JS和CSS文件, 而且照顾你在应用程序工作流程中可能拥有的Vue和React组件。
更多信息在这里!
总结
性能优化比艺术更是一门艺术-知道如何做和要做多少比要做的重要。也就是说, 在Laravel应用程序中可以优化的数量和功能是无限的。
但是无论你做什么, 我都想向你提供一些建议–应该在有充分理由的情况下进行优化, 而不是因为听起来不错, 或者因为你对现实中超过100, 000个用户的应用程序性能抱有偏执只有10个
如果你不确定是否需要优化应用程序, 则无需踢黄蜂的巢穴。一个工作的应用程序感觉很无聊, 但它确实需要做的事情比被优化为突变型混合超级机器但偶尔会变平的应用程序要高十倍。
而且, 要让newbiew成为Laravel大师, 请查看此在线课程。
希望你的应用运行速度更快! ????
评论前必须登录!
注册