Адрес перепечатки: https://mp.weixin.qq.com/s/98D_VtkFEM5bZsu9cazggg?
содержание
- Сценарий 1 Данные о работе программы слишком велики
- Сценарий 2. Копия создается, когда программа работает с большими данными.
- Сценарий 3 Необоснованное исчерпание системных ресурсов конфигурации
- Сценарий 4 Бесполезные данные не публикуются вовремя
- понять глубже
- php управление памятью
- проблема с утечкой памяти в php-fpm
- Проблема утечки памяти резидентного процесса
предисловие
Когда я начал писать эту статью, в проекте, которым я руководил, необходимо было использовать php для разработки программы резидентного процесса, которая непрерывно сообщает данные в режиме реального времени после установления длительного соединения с сервером через Socket.После разработки и совместной отладки бизнес-функции программы, она действительно была запущена и отправила большой объем данных и обнаружила, что память увеличилась.Очень быстро, за очень короткий промежуток времени, достигнут предел доступной памяти по умолчанию php 128M, и сообщается об ошибке:
Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes)
Моей первой реакцией была утечка памяти, но я не знаю, где. Вторая реакция заключается в том, что бесполезные переменные должны сбрасываться после того, как они израсходованы, а проблема остается после модификации. После нескольких поворотов проблема была наконец решена. Поэтому я решил разобраться в подобных ситуациях, поэтому у меня есть эта статья, чтобы поделиться с вами.
Наблюдайте за использованием памяти программы PHP
PHP предоставляет два метода для получения информации об использовании памяти текущей программой.
- memory_get_usage(), функция этой функции состоит в том, чтобы получить размер памяти, используемый в настоящее время PHP-скриптом.
- memory_get_peak_usage(), функция этой функции возвращает пиковую память, занимаемую текущим скриптом, в текущую позицию, чтобы можно было получить потребность в памяти для текущего скрипта.
int memory_get_usage ([ bool $real_usage = false ] )
int memory_get_peak_usage ([ bool $real_usage = false ] )
Сценарий 1: данные о работе программы слишком велики
Восстановление сценария: исчерпание памяти, вызванное однократным чтением данных, которые превышают верхний предел доступной памяти PHP
<?php
ini_set('memory_limit', '128M');
$string = str_pad('1', 128 * 1024 * 1024);
?>
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 134217729 bytes) in /Users/zouyi/php-oom/bigfile.php on line 3
Это говорит нам о том, что когда программа работает, когда она пытается выделить новую память, она выдает фатальную ошибку, потому что достигает верхнего предела памяти, разрешенного PHP, и не может продолжать выполнение.Это обычно называется OOM (Out Of Память) в разработке Java.
PHP настраивает предел памяти, устанавливая memory_limit в php.ini.До PHP 5.2 значение по умолчанию было 8M, а значение по умолчанию в PHP 5.2 было 16M.Значение по умолчанию в более поздних версиях составляет 128M.
Явление проблемы: оно может воспроизводиться при обработке конкретных данных, и любая операция ввода-вывода может столкнуться с такими проблемами, как: запрос mysql возвращает большое количество данных, в программу за один раз считывается большой файл и т. д.
Решение:
能用钱解决的问题都不是问题,如果程序要读大文件的机会不是很多,且上限可预期,那么通过 ini_set('memory_limit', '1G'); 来设置一个更大的值或者 memory_limit=-1。内存管够的话让程序一直跑也可以。
如果程序需要考虑在小内存机器上也能正常使用,那就需要优化程序了。如下,代码复杂了很多。
<?php
//php7 以下版本通过 composer
//引入 paragonie/random_compat ,为了方便来生成一个随机名称的临时文件
require "vendor/autoload.php";
ini_set('memory_limit', '128M');
//生成临时文件存放大字符串
$fileName = 'tmp'.bin2hex(random_bytes(5)).'.txt';
touch($fileName);
for ( $i = 0; $i < 128; $i++ ) {
$string = str_pad('1', 1 * 1024 * 1024);
file_put_contents($fileName, $string, FILE_APPEND);
}
$handle = fopen($fileName, "r");
for ( $i = 0; $i <= filesize($fileName) / 1 * 1024 * 1024; $i++ )
{
//do something
$string = fread($handle, 1 * 1024 * 1024);
}
fclose($handle);
unlink($fileName);
Сценарий 2. Копия создается, когда программа работает с большими данными.
Восстановление сценария: во время выполнения копируются большие переменные, что приводит к нехватке памяти.
<?php
ini_set("memory_limit",'1M');
$string = str_pad('1', 1* 750 *1024);
$string2 = $string;
$string2 .= '1';
Fatal error: Allowed memory size of 1048576 bytes exhausted (tried to allocate 768001 bytes) in /Users/zouyi/php-oom/unset.php on line 8
Call Stack:
0.0004 235440 1. {main}() /Users/zouyi/php-oom/unset.php:0
zend_mm_heap corrupted
проблемное явление: удваивает объем памяти, занимаемый во время выполнения локального кода.
анализ проблемы:
PHP — это копирование при записи, то есть память не изменяется, когда присваивается новая переменная, и копирование не произойдет до тех пор, пока не будет произведено манипулирование содержимым новой переменной.
Решение:
Заблаговременно освободите неиспользуемые переменные или манипулируйте примитивными данными как ссылками.
<?php
ini_set("memory_limit",'1M');
$string = str_pad('1', 1* 750 *1024);
$string2 = $string;
unset($string);
$string2 .= '1';
<?php
ini_set("memory_limit",'1M');
$string = str_pad('1', 1* 750 *1024);
$string2 = &$string;
$string2 .= '1';
unset($string2, $string);
Сценарий 3: необоснованное исчерпание системных ресурсов конфигурации
Восстановление сценария: Недостаточно памяти из-за неразумной настройки.На машине с 2Г памяти может быть запущено максимум 100 дочерних процессов php-fpm, но после фактического запуска 50 дочерних процессов php-fpm больше запускать процессы нельзя.
проблемное явление: нет проблем, когда количество бизнес-запросов в Интернете невелико.Если количество запросов велико, некоторые запросы не могут быть выполнены.
анализ проблемы:
Как правило, из соображений безопасности php ограничивает максимальное количество и размер запросов форм, которые могут быть отправлены, например, post_max_size, max_file_uploads, upload_max_filesize, max_input_vars, max_input_nesting_level.
Если предположить, что пропускной способности достаточно, пользователь часто отправляет на сервер данные post_max_size = 8M, а nginx пересылает их в php-fpm для обработки, то каждый подпроцесс php-fpm может занимать дополнительные 8M памяти, даже если он ничего не делает, кроме памяти занята собой..
Решение:
Разумно установите такие параметры, как post_max_size, max_file_uploads, upload_max_filesize, max_input_vars, max_input_nesting_level и настройте параметры, связанные с php-fpm.
php.ini
$ php -i |grep memory
memory_limit => 1024M => 1024M //php脚本执行最大可使用内存
$php -i |grep max
max_execution_time => 0 => 0 //最大执行时间,脚本默认为0不限制,web请求默认30s
max_file_uploads => 20 => 20 //一个表单里最大上传文件数量
max_input_nesting_level => 64 => 64 //一个表单里数据最大数组深度层数
max_input_time => -1 => -1 //php从接收请求开始处理数据后的超时时间
max_input_vars => 1000 => 1000 //一个表单(包括get、post、cookie的所有数据)最多提交1000个字段
post_max_size => 8M => 8M //一次post请求最多提交8M数据
upload_max_filesize => 2M => 2M //一个可上传的文件最大不超过2M
При необоснованных настройках выгрузки неудивительно, что занимает большой объем памяти, например, в некоторых интранет-сценариях требуется сверхбольшая строка сообщения post_max_size=200M, тогда при отправке 200M данных из формы серверу, php выделит 200M памяти. Дайте эти данные, пока запрос не будет обработан и не освободит память.
php-fpm.conf
pm = dynamic //仅dynamic模式下以下参数生效
pm.max_children = 10 //最大子进程数
pm.start_servers = 3 //启动时启动子进程数
pm.min_spare_servers = 2 //最小空闲进程数,不够了启动更多进程
pm.max_spare_servers = 5 //最大空闲进程数,超过了结束一些进程
pm.max_requests = 500 //最大请求数,注意这个参数是一个php-fpm如果处理了500个请求后会自己重启一下,可以避免一些三方扩展的内存泄露问题
Процесс php-fpm рассчитывается исходя из 30 Мб памяти, а 50 процессов php-fpm требуют 1500 Мб памяти, необходимо кратко оценить, будет ли исчерпана системная память после запуска всех процессов php-fpm под максимальной нагрузкой.
Сценарий 4: Бесполезные данные не публикуются вовремя
Восстановление сценария: проблема такого рода не является проблемой логики программы, но бесполезные данные занимают большой объем памяти, что приводит к нехватке ресурсов, поэтому оптимизация кода должна выполняться целенаправленно.
Следующий код используется для мониторинга операций базы данных при разработке Laravel:
DB::listen(function ($query) {
// $query->sql
// $query->bindings
// $query->time
});
После того, как мониторинг базы данных включен, новый объект QueryExecuted будет создаваться всякий раз, когда выполняется SQL, и анонимная функция будет передаваться для последующих операций.Это не проблема для php-программы, которая завершает процесс и освобождает ресурсы после выполнения, но если это является резидентным процессом Program, объект QueryExecuted будет добавляться в память каждый раз, когда программа выполняет SQL, и память всегда будет увеличиваться, пока программа не завершится.
проблемное явление: Память постепенно увеличивается во время работы программы, и память обычно освобождается после завершения программы.
анализ проблемы:
Такие проблемы трудно обнаружить и трудно определить, особенно для некоторых методов, инкапсулированных в инфраструктуру, и их применимые сценарии должны быть прояснены.
Решение:
В этом примере метод DB::listen используется для получения записей всех выполненных операторов SQL и записи их в журнал, но у этого метода есть проблема с утечкой памяти, которая не имеет значения в среде разработки, но должна быть отключена в производственной среде.Выполните оператор SQL и запишите журнал.
понять глубже
- Глоссарий
-
Утечка памяти (Memory Leak): проблема заключается в том, что программа не может должным образом освободить память, которая больше не используется в процессе управления распределением памяти, что приводит к занятию большого количества ресурсов. В объектно-ориентированном программировании причиной утечек памяти часто является то, что объекты хранятся в памяти, но недоступны для запуска кода. Поскольку случаев подобных проблем много, мы можем анализировать, локализовать и решать их только из исходного кода.
-
Сборка мусора (сокращенно GC): форма автоматического управления памятью, при которой программа GC проверяет и обрабатывает память, которая была выделена в программе, но больше не используется объектами. Самый ранний GC был изобретен Джоном Маккарти примерно в 1959 году, чтобы упростить ручное управление памятью в Lisp. Ядро PHP имеет встроенные функции управления памятью, поэтому в обычных сценариях приложений утечки памяти возникают не так просто.
-
Трассировка: Начинайте трассировку с корневого объекта, проверяйте, какие объекты доступны, а остальные (недоступные) — мусор.
-
Счетчик ссылок (счетчик ссылок): Каждый объект имеет число, используемое для обозначения количества ссылок на него. Счетчик ссылок, равный 0, может быть переработан. Счетчик ссылок объекта увеличивается, когда ссылка на объект создается, и уменьшается, когда ссылка уничтожается. Метод подсчета ссылок может гарантировать, что объект будет уничтожен, как только на него не будет ссылки. Однако у подсчета ссылок есть некоторые недостатки: 1. Циклическая ссылка, 2. Подсчет ссылок должен применяться для увеличения объема памяти, 3. Он влияет на скорость, 4. Он должен обеспечивать атомарность, 5. Он не работает в режиме реального времени.
2. управление памятью php
在 PHP 5.2 以前, PHP 使用引用计数(Reference count)来做资源管理, 当一个 zval 的引用计数为 0 的时候, 它就会被释放.。
虽然存在循环引用(Cycle reference), 但这样的设计对于开发 Web 脚本来说, 没什么问题, 因为 Web 脚本的特点和它追求的目标就是执行时间短, 不会长期运行。
对于循环引用造成的资源泄露, 会在请求结束时释放掉. 也就是说, 请求结束时释放资源, 是一种部补救措施( backup ).
然而, 随着 PHP 被越来越多的人使用, 就有很多人在一些后台脚本使用 PHP , 这些脚本的特点是长期运行, 如果存在循环引用, 导致引用计数无法及时释放不用的资源, 则这个脚本最终会内存耗尽退出.
所以在 PHP 5.3 以后, 我们引入了 GC .
—— Выдержка из сообщения в блоге Brother Bird «Пожалуйста, освободите свои ресурсы вручную».
После PHP 5.3 был введен алгоритм синхронного сбора циклов (Concurrent Cycle Collection) для решения проблемы утечек памяти за счет определенного влияния на производительность, но в целом влияние приложений веб-скриптов невелико.
Механизм сборки мусора PHP включен по умолчанию, а php.ini можно отключить, установив zend.enable_gc=0. Сборку мусора также можно включать и выключать, вызывая функции gc_enable() и gc_disable() соответственно.
Хотя сборка мусора освобождает PHP-разработчиков от забот об управлении памятью, есть крайние противоположные примеры: Composer, известный инструмент управления пакетами в мире PHP, однажды добавил строку gc_disable(); производительность значительно улучшилась.
Основы подсчета ссылок (http://php.net/manual/zh/features.gc.refcounting-basics.php
)
Сбор циклов (http://docs.php.net/manual/zh/features.gc.collecting-cycles.php
)
Две приведенные выше ссылки представляют собой объяснения по управлению памятью и сведениям, связанным с сборщиком мусора, в официальном руководстве по PHP с изображениями и текстом, поэтому я не буду повторять их здесь.
3. проблема утечки памяти php-fpm
На обычном сервере nginx + php-fpm:
nginx 服务器 fork 出 n 个子进程(worker), php-fpm 管理器 fork 出 n 个子进程。
当有用户请求, nginx 的一个 worker 接收请求,并将请求抛到 socket 中。
php-fpm 空闲的子进程监听到 socket 中有请求,接收并处理请求。
Жизненный цикл php-fpm примерно такой:
模块初始化(MINIT)-> 请求初始化(RINIT)-> 请求处理 ->
请求结束(RSHUTDOWN) -> 请求初始化(RINIT)-> 请求处理 ->
请求结束(RSHUTDOWN)……. 请求初始化(RINIT)-> 请求处理 ->
请求结束(RSHUTDOWN)-> 模块关闭(MSHUTDOWN)。
В инициализации запроса (RINIT) -> обработка запроса -> завершение запроса (RSHUTDOWN) процесс "обработки запроса" таков: php читает соответствующий файл php, выполняет над ним лексический анализ, генерирует опкод, и виртуальная машина zend выполняет опкод.
PHP автоматически освобождает память после каждого запроса, эффективно избегая проблемы утечек памяти в распространенных сценариях, однако в реальной среде из-за сбоя некоторых расширенных методов управления памятью или циклических ссылок в коде PHP неиспользуемая память не может быть освобождена обычным образом. .ресурс.
В файле конфигурации php-fpm установите для параметра pm.max_requests меньшее значение. Значение этого параметра: подпроцесс php-fpm будет уничтожен после обработки не более pm.max_requests пользовательских запросов. Когда процесс php-fpm уничтожается, вся занимаемая им память освобождается.
Суммировать
Столкнувшись с утечкой памяти, сначала посмотрите, вызвана ли она недостатком памяти самой программы или внешних ресурсов, а затем выясните, какие ресурсы используются при работе программы: запись журналов диска, подключение к SQL-запросам базы данных, отправка Curl запросы, связь через сокет и т.д., операции ввода/вывода Память обязательно будет использоваться.Если нет явных утечек памяти в этих местах, проверьте, где обрабатывается большой объем данных и ресурсы не освобождаются вовремя.Если есть PHP 5.3 и ниже необходимо учитывать проблему циклических ссылок.
Узнайте больше о некоторых средствах анализа в Linux, и вы сможете делать больше с меньшими затратами при решении проблем.
Наконец, давайте опубликуем новейший инструмент отслеживания прозрачных ссылок приложений с открытым исходным кодом Molten от команды по преобразованию облачных вычислений в этом году:https://github.com/chuan-yun/Molten
.