В моем приложении я часто вызываю внешний api, который возвращает строку json.
$url = 'api.example.com/xyz'; $blah = json_decode( file_get_contents( $url ) );
Но в некоторых случаях я получаю
PHP Неустранимая ошибка: допустимый размер памяти xxx байт исчерпан (попытался выделить 32 байта) в …
Я не могу контролировать внешний API, и, конечно, я мог бы увеличить память для php, но это имеет некоторые недостатки.
1- Какой бы размер я ни устанавливал, все равно было бы слишком мало. 2- Если я установил размер памяти «бесконечно», я мог бы рискнуть убить мой сервер.
В идеале я хотел бы «проверить», прежде чем я вызову json_decode (…), что строка приведет к исчерпанию памяти.
Это возможно?
Вы должны получать некоторые массивные ответы JSON, если им удастся исчерпать память вашего сервера. Вот некоторые показатели с файлом 1 МБ, содержащим многомерный связанный массив (содержащий данные, подготовленные для ввода в три таблицы MySQL с различными типами данных).
Когда я include
и файл загружается в память в виде массива, использование моей памяти составляет 9 МБ. Если я получаю необработанные данные с помощью file_get_contents()
, он занимает 1 МБ памяти, как ожидалось. Затем массив PHP имеет приблизительное отношение 1: 9 к strlen()
данных (первоначально выводится с помощью var_export()
).
Когда я запускаю json_encode()
, пиковое использование памяти не увеличивается. (PHP выделяет память в блоках, поэтому часто приходится немного накладных расходов, в этом случае достаточно включить строковые данные JSON, но это может увеличить число вас на один блок.) Полученные данные JSON в виде строки занимают 670 КБ.
Когда я загружаю данные JSON с помощью file_get_contents
в строку, она занимает ожидаемую 0,75 МБ памяти. Когда я запускаю json_decode()
на нем, он занимает 7 МБ памяти. Тогда я бы определил минимальное соотношение 1:10 для JSON-data-bytesize, декодированное для собственного массива PHP-объекта для требований к RAM.
Чтобы выполнить проверку данных JSON перед ее расшифровкой, вы можете сделать что-то вроде этого:
if (strlen($my_json) * 10 > ($my_mb_memory * 1024 * 1024)) { die ('Decoding this would exhaust the server memory. Sorry!'); }
… где $my_json
– это сырой ответ JSON, а $my_mb_memory
– ваша выделенная ОЗУ, которая преобразуется в байты для сравнения с входящими данными. (Конечно, вы также можете использовать intval(ini_get('memory_limit'))
чтобы получить ограничение на память как целое.)
Как указано ниже, использование ОЗУ также будет зависеть от вашей структуры данных. Для сравнения, несколько более быстрых тестов, потому что мне любопытно:
Таким образом, ваш фактический пробег RAM может сильно отличаться. Также имейте в виду, что если вы передаете эту большую часть данных по кругу и немного json_decode()
то использование вашей памяти может значительно (или экспоненциально, в зависимости от вашей экономики кода) выше, чем только вызывает json_decode()
.
Чтобы отлаживать использование памяти, вы можете использовать memory_get_usage()
и / или memory_get_peak_usage()
с большими интервалами в вашем коде для регистрации или вывода памяти, используемой в разных частях вашего кода.
Мой первый ответ выше – это чистое ограничение памяти. Теперь, как вы можете справиться с данными, если вы ненавидите их отбрасывать , но если они постоянно становятся громоздкими за пределы вашей памяти?
Предполагая, что вам не нужно, чтобы ответ обрабатывался одним выстрелом и абсолютным в реальном времени. Затем вы можете просто разделить ответ на куски подходящего размера, например, с помощью explode()
или preg_split()
и сохранить их во временном каталоге и обработать позже в пакетной операции.
Я полагаю, что большие ответы API возвращают сразу несколько наборов данных; если нет, вы можете также объединить одну многомерную запись в более управляемые куски, которые позже воссоединились, хотя для этого потребуется гораздо более оперативная точность в создании функции разметки JSON-строки.
Если несколько наборов данных необходимо связать в последующей обработке (например, запись в базу данных), вы также захотите иметь файл агрегатора, содержащий метаданные для пакетной операции. (Или иначе вставьте все это в базу данных.) Разумеется, вы должны убедиться, что упорядоченные данные хорошо сформированы. Это не идеальный вариант, но не наличие концертов с памятью также не является идеальным. Пакетирование – это один из способов борьбы с ним.
Вместо того, чтобы просто выйти, если файл JSON слишком велик, вы можете обрабатывать файлы JSON произвольного размера, используя парсер JSON на основе событий, такой как https://github.com/salsify/jsonstreamingparser . За один раз в память будет загружен только небольшой фрагмент объекта / массива.