Позвольте мне поделиться своим опытом оптимизации приложений Angular в реальном времени и тем, как мы сэкономили 80% времени при рендеринге компонентов.

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

Начнем сейчас ...

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

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

  1. Клиент хочет, чтобы каждая страница приложения отображалась за 90 мс.
  2. Это веб-приложение будет оставаться активным и доступным в течение 24x7 дней.

Давайте разберемся с технологической экосистемой приложения, оно было построено с использованием Angular 8 и библиотеки материалов. Что касается сторонней библиотеки, loadash использовался для манипулирования данными.

Когда я присоединился к этому проекту, они уже разработали 80% приложения, большинство страниц рендерилось более чем за 300 мс. Приложение также перестает отвечать после длительного использования.

Позвольте мне поделиться подходом, который я использовал в этом проекте, чтобы преодолеть все эти проблемы; вы также можете использовать этот подход для точной настройки своих клиентских приложений.

Начнем с оптимизации памяти. Первое, что я сделал после присоединения к проекту, это познакомился с системой.

Я задал несколько вопросов, например, сколько модулей / компонентов у нас есть в приложении? Используем ли мы данные в реальном времени с помощью веб-сокета? Загружаем ли мы приложение сразу целиком или по частям? Каков уровень иерархии компонентов? Я имею в виду, создаются ли компоненты глубоко внутри других родительских компонентов?

Основываясь на этих вопросах, я обнаружил, что приложение представляет собой приложение среднего размера, имеющее 100 компонентов в 5 модулях, использующее WebSocket для потребления данных в реальном времени, оно имеет высокое время загрузки, а максимальное время загрузки страниц занимает более 300 мсек. . Приложение перестало отвечать после 3 часов непрерывного использования. Итак, признаки были очень ясны, что это требует капитального ремонта всего приложения.

Сначала я начал с оптимизации памяти

Лучшее место для проверки использования памяти вашим веб-приложением - не более чем инструмент разработчика Chrome. Это помогает вам записывать потребление памяти конкретной страницей или полным функциональным сценарием. Мы также можем использовать вкладку производительности, чтобы увидеть объем памяти кучи, потребляемой в конкретном сценарии.

На картинке ниже вы можете увидеть несколько вещей: движение вверх по строке предполагает, что мы загружаем / рендерим новую страницу или компонент, нижняя строка указывает, что мы освобождаем некоторую память, это может быть из-за запуска сборки мусора.

Здесь следует отметить, что линия не касается оси x, что означает, что некоторая часть памяти не была восстановлена ​​должным образом. Это говорит о том, что есть утечка памяти.

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

Когда я проанализировал этот шаблон, я сразу же перешел к коду, чтобы выяснить, какой фрагмент кода имеет root-права, из-за которого происходит утечка памяти. Я обнаружил, что приложение активно использует Observables для получения внешних данных. Этот паттерн Observer очень удобен в приложениях Angular. Но если мы не будем следовать лучшим практикам, мы попадем в ловушку памяти.

Первая большая проблема. Я обнаружил, что мы не отказываемся от подписки на Observable, когда нам не нужны данные, что делает Observable активным все время.

Есть несколько способов отказаться от подписки на наблюдаемые в компонентах Angular. Один из подходов - использовать оператор takeUntil. Таким образом мы можем отказаться от подписки на службу данных более функциональным способом.

Когда вы работаете в большой команде, наличие всех передовых практик, написанных на листе бумаги, не заставит разработчиков их использовать. Но с помощью правил линтинга вы можете заставить разработчиков принять правила кодирования. Я нашел одно правило линтинга, которое помогло мне придерживаться этого правила с командой Принудительное использование с помощью линтинга - https://github.com/angular-extensions/lint-rules

Вторая проблема. Я обнаружил, что мы используем прослушиватели событий в нашем приложении, нам много времени нужно добавлять события в элементы HTML. Поскольку это явно добавленные слушатели событий, angular не удалит их из системы, поэтому вам нужно быть осторожным при его использовании.

Если вы заметили приведенный выше код, мы добавляем событие прокрутки в тело документа. Похоже, что всякий раз, когда пользователь выполняет прокрутку, нам нужно вызывать метод this.updatePosition (). Событие Код выглядит нормально, но мы не отменяем регистрацию события при выходе из этого компонента. Лучшим способом написать тот же код может быть:

Теперь у нас есть прослушиватель событий с незарегистрированным блоком кода, который освобождает память в конце жизненного цикла компонента.

Одно из практических правил, которое я бы порекомендовал здесь, - это написать код отказа от подписки перед написанием метода подписки.

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

После внесения всех вышеуказанных изменений я обнаружил, что график потребления памяти приложением снова начал касаться оси x, это хороший знак для любого веб-приложения. Во второй части я расскажу о своем опыте оптимизации производительности во время выполнения.