Рубрики
Без рубрики

Практическое руководство по утечкам памяти в Node.js

Введение Утечки памяти похожи на паразиты приложения, они незаметны в ваших системах, и не вызывают никакого вреда изначально …

Автор оригинала: Arbaz Siddiqui.

Ссылка на сайт к оригинальной статье.

Вступление

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

Утечки памяти могут быть широко определены как кусок памяти, который больше не требуется вашим приложением, но не доступен для вашей ОС для дальнейшего использования. Другими словами, блок памяти, который принимает свое приложение без намерения использовать его в будущем.

Управление памятью

Memory Management – это способ назначения памяти с вашей памяти машины в ваше приложение, а затем выпустить эту память обратно на ваш компьютер, когда больше не используется. Есть несколько способов управления памятью, и это зависит от языка программирования, который вы используете. Ниже приведены несколько способов управления памятью:

  • Ручное управление памятью : В этой парадигме управления памятью программист отвечает за оба назначения, а затем выпустить память. Язык по умолчанию не предоставит какие-либо автоматизированные инструменты для вас. Пока это дает вам чрезвычайную гибкость, его тоже добавляют накладные расходы. C и C ++ Используйте этот подход к управлению памятью и предоставить способы, такие как malloc и Бесплатно координировать с памятью машины.
  • Коллекция мусора : Сборки мусора языки делает управление памятью для вас из коробки. Программист не нужно беспокоиться о том, чтобы освободить память как встроенный сборщик мусора сделает это для вас. Как это работает, и когда он будет спусковым для освобождения неиспользованной памяти, в основном будет черным ящиком для разработчиков. Большинство современных языков программирования, такие как JavaScript , Языки на базе JVM (Java, Scala, Kotlin) , Голанг , Python , Ruby и т. Д. Языки сбора мусора.
  • Владение : При таком подходе управления памятью каждая переменная должна иметь своего владельца, и как только владелец выйдет из приспособления, значение в переменной будет отброшена, выделяющая память. Ржавчина Использует этот подход управления памятью.

Есть много других способов управления памятью, которые используют такие языки, как Райи используется C ++ 11 и Дуга используется Свифт Но это не имеет возможности этой статьи. Плюсы, минусы и сравнение между каждым из этих методов требуют собственной статьи. Когда-нибудь.

С момента любимого языка веб-разработчиков и языком и языка в объеме этой статьи собрано мусор, мы будем выглядеть глубже в том, как работает сборка мусора в JavaScript.

Коллекция мусора в JavaScript

Как уже упоминалось в вышеуказанном разделе, JavaScript – это сборщик мусора, и, следовательно, двигатель, называемый коллектором мусора в периодически и проверяет, какие выделенные память все еще могут быть достигнуты по вашему приложению код, то есть, какие переменные тоже есть ссылку. Если он найдет некоторую память, не ссылается на приложение, она отпустит его. Существует два основных алгоритма для приведенного выше подхода. Во-первых, Марка и подметать который используется JavaScript а второй – Ссылка подсчет который используется Python и PHP Отказ

Отметки и подметать

ALGORTHM MARK и SWIELMED впервые создает список корней, которые являются глобальными переменными в окружающей среде ( окно объект в браузере), а затем переходит дерево из корней на узлы листьев и отмечает все объекты, которые он сталкивается. Любая память, не проводимая отмеченными объектами в куче, помечена как бесплатная.

Утечки памяти в приложении узла

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

Представляя протекающий код

Ради демонстрации, у меня есть построенный сервер экспресс-сервера, который имеет в ней утечный маршрут. Мы будем использовать этот сервер API для отладки.

const express = require('express')

const app = express();
const port = 3000;

const leaks = [];

app.get('/bloatMyServer', (req, res) => {
  const redundantObj = {
    memory: "leaked",
    joke: "meta"
  };

  [...Array(10000)].map(i => leaks.push(redundantObj));

  res.status(200).send({size: leaks.length})
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

Здесь у нас есть Утечки Массив, который находится за пределами объема нашего API, и, следовательно, каждый раз, когда это называется, он будет продолжать толкать данные на этот массив, не убирая его. Так как он всегда будет ссылаться, GC никогда не выпустит воспоминания.

Раздум на нашем сервере

Вот где все интересны. В Интернете много статей, сообщающих, как отлаживать утечки памяти на своем сервере, сначала ударяя его несколько времен с такими инструментами, как артиллерия а затем отладка, используя Узел --inspect Но есть серьезная проблема с этим подходом. Представьте себе, что у вас есть сервер API с сотнями API с каждым API, принимающим несколько параметров, которые вызывают разные пути кода. Таким образом, в реальных сценариях мира, где вы не знаете, где находится утечка, чтобы отложить свою память, чтобы отладить утечку, вы будете ударить каждый API с помощью всех возможных параметров несколько раз. Что для меня звучит как очень сложная вещь, чтобы сделать, если у вас нет инструментов, таких как Гореплей которые позволяют вам записывать и воспроизводить реальный трафик на вашем тестовом сервере.

Чтобы исправить эту проблему, мы собираемся отладить это в производстве, то есть мы позволим нашему серверу подумать в производстве (поскольку он получит все виды запроса API) и после того, как мы найдуте использование памяти, мы начнем отладовать его.

Heapdump.

Чтобы понять, что есть HeaPDumm, мы сначала должны понимать, что такое куча. Чтобы поставить в чрезвычайно простые термины, куча – это место, в котором все бросается, и он остается там до тех пор, пока GC не удаляет, что должно быть нежелательным. Dump Heap – это снимок вашей нынешней кучи. Он будет содержать все внутренние и пользовательские переменные и выделения, которые в настоящее время присутствуют в куче.

Так что, если мы сможем каким-то образом сравнивать Heapdump из свежих сервера VS Heapdump от длинного бегущего раздутого сервера, мы должны иметь возможность идентифицировать объекты, которые не собираются GC, глядя на дифференциал.

Но сначала давайте посмотрим, как взять HeaPDump. Мы будем использовать библиотеку NPM Heapdump что позволяет нам проводить HeaPdump сервера программно. Для установки сделать:

npm i heapdump

Мы собираемся внести несколько изменений на нашем Express Server для использования этого пакета.

const express = require('express');
const heapdump = require("heapdump");

const app = express();
const port = 3000;

const leaks = [];

app.get('/bloatMyServer', (req, res) => {
  const redundantObj = {
    memory: "leaked",
    joke: "meta"
  };

  [...Array(10000)].map(i => leaks.push(redundantObj));

  res.status(200).send({size: leaks.length})
});

app.get('/heapdump', (req, res) => {
  heapdump.writeSnapshot(`heapDump-${Date.now()}.heapsnapshot`, (err, filename) => {
    console.log("Heap dump of a bloated server written to", filename);

    res.status(200).send({msg: "successfully took a heap dump"})
  });
});

app.listen(port, () => {
  heapdump.writeSnapshot(`heapDumpAtServerStart.heapsnapshot`, (err, filename) => {
    console.log("Heap dump of a fresh server written to", filename);
  });
});

Мы использовали пакет, чтобы взять HeaPDump, как только сервер запускается и написал API, чтобы взять HeaPDump, когда мы называем API /heapdump Отказ Мы назовем эту API, когда мы понимаем, что наша память пошло наверх.

Если вы используете свое приложение внутри кластера Kube, вы не сможете ударить желаемое высокое потребление POD. Сделать, чтобы вы могли использовать Переадресация порта Чтобы ударить этот специфический стручок в кластере. Также поскольку вы не сможете получить доступ к файловой системе, чтобы загрузить эти HEAMPDUMS, вы можете загрузить эти HeaPdumps для облака (S3).

Определение утечки

Так что теперь наш сервер развернут и работает в течение нескольких дней. Его пострадают от ряд запросов (только один в нашем случае), и мы заметили, что потребление памяти нашего сервера Spiked (вы можете сделать это, используя инструменты мониторинга, такие как Экспресс-монитор статуса , клиника , Prometheus ). Теперь мы сделаем вызов API, чтобы взять HeaPDump. Этот HeapDump содержит все объекты, которые GC не смог собирать.

curl --location --request GET 'http://localhost:3000/heapdump'

Принимая усилия Heapdump GC для запуска и, следовательно, нам не нужно беспокоиться о распределении памяти, которые могут быть собраны GC в будущем, но в настоящее время входят внутри Heap I.e. Неизвестные объекты.

Как только вы получите руки как на Heapdumps (свежий и длинный запуска), мы можем начать сравнивать.

Взятие HeaPDump – это память интенсивной и блокирующей операцией и должна быть сделана с уходом. Прочитайте это предостережение для получения дополнительной информации.

Откройте Chrome и нажмите F12 Отказ Это откроет Chrome Console, перейдите в Память вкладка и Нагрузка обе снимки.

Загрузить снимки

После загрузки обе снимки меняют Перспектива к Сравнение и нажмите на снимка длинного запуска сервера

Сравнение снимков

Мы можем пройти через Конструктор И посмотрите на все объекты GC не подметали. Большинство из них были бы внутренняя ссылка на какие узлы используют один аккуратный трюк, чтобы сортировать их по Alloc. Размер Чтобы проверить наиболее тяжелые распределения памяти, у нас есть. Если мы расширим массив а затем расширить (объектные элементы) Мы сможем увидеть наших Утечки Массив, содержащий безумный объем объектов в нем, который не поднят GC.

Определение объектов

Теперь мы можем пинчить точку на Утечки массив как причина высокого потребления памяти.

Исправлять утечку

Теперь, когда мы знаем массив Утечки Это вызывает проблему, мы можем посмотреть код и довольно легко отладки, что ее, поскольку массив находится за пределами цикла запроса, и, следовательно, его ссылка никогда не удаляется. Мы можем исправить это довольно легко, делая:

app.get('/bloatMyServer', (req, res) => {
  const redundantObj = {
    memory: "leaked",
    joke: "meta"
  };

  const leaks = []; //highlight-line

  [...Array(10000)].map(i => leaks.push(redundantObj));

  res.status(200).send({size: leaks.length})
});

Мы можем проверить это исправление, повторяя вышеупомянутые шаги и снова сравнивая снимки.

Выводы

Утечки памяти обязаны случиться на собранных мусорах, таких как JavaScript. Исправление утечки памяти – это легкая, хотя идентификация их является настоящей болью. В этой статье мы узнали о основах Memory Management и как ее проводится различными языками. Мы издевались с реальным сценарием и пытались отладить утечку памяти и в конечном итоге исправили его.

Как этот пост? Вы можете найти больше в Twitter: @Arbazsiddiqui_ Или посетить мой Сайт Или присоединиться к рассылка Спасибо за прочтение!