• Время чтения ~3 мин
  • 19.06.2023

В прошлый раз я анализировал производительность контейнера Laravel. Я столкнулся с тем, что приложение тратит много времени на создание зависимостей, особенно для тяжелых конечных точек. Это было странно, потому что я предпочел бы ожидать, что соответствующая логика должна быть самой тяжелой частью запроса.

Проблема

Оказывается, что по умолчанию все зависимости в Laravel не являются общими. Поэтому, когда приложению требуется определенная зависимость, контейнер создаст и внедрит новый экземпляр этой зависимости. Например, в Symfony по умолчанию все зависимости являются общими:

В сервисном контейнере все сервисы являются общими по умолчанию. Это означает, что каждый раз, когда вы извлекаете службу, вы будете получать один и тот же экземпляр.

https://symfony.com/doc/current/service_container/shared.htmlТаким образом, реализация контейнера в Laravel странна, потому что это означает, что каждое крупное приложение, использующее Laravel, будет иметь проблему с внедрением большого количества экземпляров зависимостей. Это тратит много ресурсов, потому что приложение должно распознавать параметры определенного класса с помощью Reflection (магия 😄 автоматической проводки), а затем создавать все зависимости и делать то же самое во время создания зависимостей этого конкретного класса и так далее. 🤯

\Illuminate\Container\Container::resolve

// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
    return $this->instances[$abstract];
}

Как решить эту проблему?

Используйте область действия или синглтон вместо привязки в поставщиках:

$this->app->scoped(ServiceInterface::class, Service::class);
$this->app->singleton(ServiceInterface::class, Service::class);

Есть одно различие между областью действия и синглтоном.

Метод с заданной областью связывает класс или интерфейс с контейнером, который должен быть разрешен только один раз в течение заданного жизненного цикла запроса или задания Laravel. Хотя этот метод аналогичен методу синглтона, экземпляры, зарегистрированные с помощью метода с заданной областью, будут сбрасываться всякий раз, когда приложение Laravel начинает новый «жизненный цикл», например, когда рабочий процесс Laravel Octane обрабатывает новый запрос или когда рабочий процесс очереди Laravel обрабатывает новое задание:Вы также можете вручную очистить зависимости с заданной областью:

Вы также можете вручную очистить зависимости с заданной областью:

$this->container->forgetScopedInstances();

В большинстве случаев это изменение должно быть безопасным, но будьте осторожны и убедитесь, что ваши службы не имеют состояния.

Автоматические проводные услуги

К сожалению, по умолчанию используются необщие зависимости. Таким образом, это означает, что когда у нас есть автоматические проводные сервисы без какого-либо интерфейса, нам не нужно объявлять их ни у одного провайдера, тогда мы не можем сделать их общими.

Чтобы решить эту проблему, я придумал только следующее неприятное решение для этого:

$this->app->scoped(Service:class, Service:class)

Таким образом, мы можем просто создать поставщика и определить службу, используя метод с заданной областью, чтобы сделать этот сервис общим.

Сравнение

до

и после Эти результаты приведеныпосле

некоторых изменений от привязки к области, но все еще не все, поэтому все еще есть возможности для улучшения. Время в Blackfire не имеет значения, но мы по-прежнему видим прирост производительности на ~60% при сборке зависимостей.

Резюме

: Мне любопытно, что послужило основной причиной реализации контейнера таким образом. Это означает, что Laravel может быть не лучшим вариантом для больших монолитных приложений. Несколько лет назад я работал с огромным монолитом, основанным на фреймворке Symfony, разработанным около 50 бэкенд-разработчиками, и я даже не могу представить себе использование Laravel в таком проекте с такой конструкцией контейнера. Наверное, это был бы кошмар.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297