'safe'json_decode(,,,)以防止耗尽内存

Sim*_*man 6 php json

在我的应用程序中,我经常调用一个返回json字符串的外部api.

$url = 'api.example.com/xyz';
$blah = json_decode( file_get_contents( $url ) );
Run Code Online (Sandbox Code Playgroud)

但在某些情况下,我得到了

PHP致命错误:允许的内存大小xxx字节耗尽(试图分配32个字节)...

我无法控制外部API,当然我可以增加php的内存,但这有一些缺点.

1-无论我设定的尺寸如何,仍然可能太少.2-如果我将内存大小设置为"无限",那么我可能会冒着杀死我的服务器的风险.

理想情况下,我想在调用json_decode(...)之前"检查"该字符串会导致内存耗尽.

那可能吗?

Mar*_* AO 6

如果他们设法耗尽服务器的内存,你必须得到一些大规模的JSON响应.以下是一些包含多维关联数组的1 MB文件的度量标准(包含为进入具有不同数据类型的三个MySQL表而准备的数据).

当我include和文件作为数组加载到内存中时,我的内存使用量变为9 MB.如果我获得原始数据file_get_contents(),则需要1 MB内存.然后,PHP数组strlen()与数据的比率大约为1:9 (最初输出为var_export()).

当我运行时json_encode(),峰值内存使用量不会增加.(PHP以块的形式分配内存,因此通常会有一些开销,在这种情况下足以包含JSON的字符串数据;但它可能会使您更多地阻塞一个块.)生成的JSON数据作为字符串需要670 KB.

当我将JSON数据加载file_get_contents到字符串中时,它需要0.75 MB的内存.当我运行json_decode()它时,它需要7 MB的内存.然后我会因子为1:10的最小比率JSON-数据bytesize解码以天然PHP阵列或对象为RAM的要求.

要在解码之前对JSON数据运行测试,您可以执行以下操作:

if (strlen($my_json) * 10 > ($my_mb_memory * 1024 * 1024)) {
    die ('Decoding this would exhaust the server memory. Sorry!');
}
Run Code Online (Sandbox Code Playgroud)

... $my_json原始JSON响应在哪里,$my_mb_memory是您分配的RAM,它被转换为字节以与传入数据进行比较.(当然,您也可以使用intval(ini_get('memory_limit'))内存限制作为整数.)

如下所述,RAM的使用还取决于您的数据结构.相比之下,一些更快的测试用例因为我很好奇自己:

    1. 如果我使用整数1-60000创建一个单维数组,则保存的PHP数组大小为1 MB,但峰值RAM使用量介于10.5和12.5 MB(奇怪的振荡)之间,或者比例为1:12-ish.
    1. 如果我将1 MB文件的数据作为12000个随机字符串创建为基本关联数组,则加载时内存使用量仅为5 MB; 比例为1:5.
    1. 如果我创建1 MB文件值得作为类似的关联数组,其中一半条目是数组作为字符串与数字索引,内存使用量是7 MB,比例1:7.

所以你的实际内存里程可能会有很大差异.另外要注意的是,如果你在圈子中传递大量数据并做一些这样的事情,你的内存使用量可能会比json_decode()单独产生的更多(或指数级,取决于你的代码经济性).

要调试内存使用情况,您可以在代码中使用memory_get_usage()和/或memory_get_peak_usage()以主要间隔记录或输出代码不同部分中使用的内存.


hum*_*ads 6

如果 JSON 文件太大,您可以使用基于事件的 JSON 解析器(如https://github.com/salsify/jsonstreamingparser )处理任意大小的 JSON 文件,而不是简单地退出。一次只会将一小部分对象/数组加载到内存中。

如果您对 JSON 文件有任何影响,请请求或更改它以JSON Lines格式重新格式化,以便可以使用任何普通 JSON 解析器逐行处理。