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

时间:2015-06-27 06:36:15

标签: php json

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

$url = 'api.example.com/xyz';
$blah = json_decode( file_get_contents( $url ) );

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

  

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

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

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

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

这可能吗?

4 个答案:

答案 0 :(得分: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。

当我将带有file_get_contents的JSON数据加载到字符串中时,它需要0.75 MB的内存。当我在其上运行json_decode()时,它需要7 MB的内存。然后,我将因 JSON-data-bytesize 解码为本地PHP数组或对象的RAM要求的最小比率为1:10。

要在解码之前对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是您分配的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()来记录或输出代码不同部分中使用的内存。

答案 1 :(得分:2)

我上面的第一个答案纯粹是关于避免内存限制。 现在,如果你不想放弃某些,你如何处理数据,但是如果它偶尔会被丢弃笨重超出你的记忆限制?

假设您不需要在一次性和绝对实时中解析响应。然后,您可以简单地将响应拆分为适当大小的块,例如使用explode()preg_split(),并将它们保存到临时目录中,稍后在批处理操作中处理。

我假设大型API响应一次返回多个数据集;如果没有,你还可以将一个多维条目拼接成更易于管理的块,这些块稍后会重新加入,尽管这需要更多的手术精度来制作你的JSON字符串分割器函数。

如果需要在以后的处理中关联多个数据集(例如数据库条目),您还需要一个包含批处理操作的元数据的聚合器文件。 (或者将其全部粘贴到数据库中。)您当然必须确保分块数据格式正确。这不是理想的,但没有记忆的演出也不理想。批处理是处理它的一种方式。

答案 2 :(得分:2)

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

答案 3 :(得分:0)

如果您唯一需要做的就是遍历大小无法确定的json中的项目,请尝试halaxa/json-machine。解析任何大小的json时,它都不会耗尽内存,仅使用foreach即可做到这一点,没有火箭科学。无需事先检查大小“安全性”,也无需增加php内存限制。它是这样的:

<?php
foreach(JsonMachine::fromFile('users.json') as $user) {
    echo $user['name'];
}