';安全';json_decode(,,)以防止耗尽内存


'safe' json_decode( ,,, ) to prevent exhausting memory

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

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

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

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

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

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

理想情况下,我想在调用json_decode(…)之前"检查"字符串是否导致内存耗尽。

这可能吗?

如果它们耗尽了服务器的内存,那么您一定会得到一些巨大的JSON响应。以下是一些指标,其中有一个1MB的文件,其中包含一个多维关联数组(包含准备进入三个具有不同数据类型的MySQL表的数据)。

当我include并且文件作为数组加载到内存中时,我的内存使用量将达到9 MB。如果我使用file_get_contents()获取原始数据,那么它需要1MB的内存。然后,PHP数组与数据的strlen()(最初用var_export()输出)的比例大约为1:9。

当我运行json_encode()时,峰值内存使用率不会增加。(PHP以块为单位分配内存,因此通常会有一点开销,在这种情况下,开销足以包括JSON的字符串数据;但它可能会使您多增加一个块。)生成的JSON数据作为字符串需要670KB。

当我将带有file_get_contents的JSON数据加载到字符串中时,它需要0.75 MB的内存。当我在它上运行json_decode()时,它占用了7MB的内存。然后,对于解码为原生PHP数组或对象JSON数据字节大小,我将考虑最小比例1:10,以满足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是您分配的RAM,它被转换为字节以与传入数据进行比较。(当然,您也可以使用intval(ini_get('memory_limit'))将内存限制作为整数。)

如下所述,RAM的使用也将取决于您的数据结构。相比之下,我自己也很好奇,所以有几个更快速的测试案例:

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

因此,您的实际RAM里程可能会有很大差异还要注意,如果你在圆圈里传递大量数据,并做一些这样或那样的事情,你的内存使用率可能会比单独的json_decode()高出很多(或指数级,取决于你的代码经济性)。

要调试内存使用情况,可以在代码中每隔一段时间使用memory_get_usage()和/或memory_get_peak_usage()来记录或输出代码不同部分中使用的内存。

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

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

我上面的第一个答案纯粹是为了避免内存限制 如果你不想丢弃一些,但如果它偶尔会超出你的内存限制,你该如何处理这些数据?

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

我推测大型API响应一次返回多个数据集;如果没有,您还可以将单个多维条目拼接到更易于管理的块中,这些块稍后会重新加入,尽管这需要更精确的JSON字符串拆分器函数。

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

如果你唯一需要的是迭代大小不可预测的json中的项目,请尝试halaxa/json机器。在解析任何大小的json时,它都不会耗尽内存,并且只使用foreach来实现这一点,而不是火箭科学。无需事先检查大小"安全性",也无需增加php内存限制。它是这样工作的:

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