О утечках памяти
Webman — это резидентный фреймворк в памяти, поэтому нужно обращать внимание на утечки памяти. Впрочем, разработчикам не стоит слишком беспокоиться: утечки происходят лишь в очень крайних случаях и их легко избежать. Разработка на webman в целом аналогична работе с традиционными фреймворками; дополнительных действий по управлению памятью не требуется.
Подсказка
Встроенный процесс мониторинга webman отслеживает использование памяти всеми процессами. Когда использование памяти процессом приближается к значениюmemory_limitиз php.ini, соответствующий процесс автоматически безопасно перезапускается для освобождения памяти, без влияния на приложение.
Определение утечки памяти
То, что использование памяти webman растёт с увеличением числа запросов, — нормально. Как правило, после достижения процесса определённого объёма запросов (обычно порядка миллионов) потребление памяти стабилизируется или лишь изредка слегка увеличивается.
В большинстве приложений потребление памяти одним процессом со временем стабилизируется в районе 10M–100M. Беспокоиться не нужно, пока потребление памяти одним процессом не превышает 100M.
Кроме того, при обработке больших файлов, крупных запросов или чтении больших объёмов данных из базы PHP выделяет много памяти. PHP может оставлять часть этой памяти для повторного использования вместо возврата её операционной системе, из‑за чего использование памяти может казаться высоким. Так как память переиспользуется, беспокоиться не нужно.
Подсказка
Для проектов, упакованных в phar или бинарник, при большом размере пакета нормально, если использование памяти превышает 100M.
Как подтвердить утечку памяти
Если процесс обработал более миллиона запросов, использование памяти превышает 100M и память продолжает расти после каждого запроса, возможно, происходит утечка памяти.
Как найти утечку памяти
Простой способ — провести нагрузочное тестирование каждого API и выяснить, какое продолжает увеличивать использование памяти после миллионов запросов.
Найдя проблемный API, можно применить метод деления пополам: каждый раз закомментировать половину бизнес-логики, пока не определится проблемный участок кода.
Как происходит утечка памяти
Утечка памяти возникает только при одновременном выполнении двух условий:
- Существует массив с длинным жизненным циклом (обычные массивы не являются проблемой)
- И этот массив с длинным жизненным циклом неограниченно разрастается (приложение постоянно добавляет в него данные и никогда их не удаляет)
Утечка возникает только при одновременном выполнении обоих условий. Если одно из условий не выполнено или выполняется только одно, утечки нет.
Массивы с длинным жизненным циклом
В webman к ним относятся:
- Массивы с ключевым словом
static - Свойства-массивы синглтонов
- Массивы с ключевым словом
global
Примечание
В webman допустимо использовать данные с длинным жизненным циклом, но нужно следить, чтобы объём данных оставался ограниченным и количество элементов не росло неограниченно.
Ниже приведены примеры по каждому случаю.
Статический массив, неограниченно растущий
class Foo
{
public static $data = [];
public function index(Request $request)
{
self::$data[] = time();
return response('hello');
}
}
Массив $data, определённый с ключевым словом static, имеет длинный жизненный цикл. В примере $data постоянно растёт с каждым запросом, что приводит к утечке памяти.
Свойство-массив синглтона, неограниченно растущее
class Cache
{
protected static $instance;
public $data = [];
public function instance()
{
if (!self::$instance) {
self::$instance = new self;
}
return self::$instance;
}
public function set($key, $value)
{
$this->data[$key] = $value;
}
}
Код вызова
class Foo
{
public function index(Request $request)
{
Cache::instance()->set(time(), time());
return response('hello');
}
}
Cache::instance() возвращает синглтон Cache с длинным жизненным циклом. Хотя свойство $data не использует static, из‑за длинного жизненного цикла класса сам $data тоже является массивом с длинным жизненным циклом. По мере добавления разных ключей в $data использование памяти растёт, возникает утечка.
Примечание
Если ключи, добавляемые черезCache::instance()->set(key, value), ограничены по количеству, утечки не будет, поскольку массив$dataне растёт неограниченно.
Глобальный массив, неограниченно растущий
class Index
{
public function index(Request $request)
{
global $data;
$data[] = time();
return response($foo->sayHello());
}
}
Массивы, определённые с ключевым словом global, не освобождаются после завершения функции или метода, поэтому имеют длинный жизненный цикл. Приведённый код вызывает утечку по мере роста числа запросов. Аналогично массивы, определённые с static внутри функции или метода, тоже имеют длинный жизненный цикл; если такой массив растёт неограниченно, возникает утечка, например:
class Index
{
public function index(Request $request)
{
static $data = [];
$data[] = time();
return response($foo->sayHello());
}
}
Рекомендации
Разработчикам не нужно придавать излишнее значение утечкам памяти, так как они редки. При возникновении утечки нагрузочные тесты помогут найти вызывающий её код. Даже если разработчик не найдёт место утечки, встроенный сервис мониторинга webman перезапустит затронутый процесс в нужный момент и освободит память.
Чтобы по возможности снизить риск утечек, можно следовать этим рекомендациям:
- По возможности не использовать массивы с ключевыми словами
globalиstatic; при использовании убедиться, что они не растут неограниченно. - Для малознакомых классов предпочитать инициализацию через
newвместо синглтонов. При использовании синглтона проверить, есть ли у него свойства-массивы, способные неограниченно расти.