【问题标题】:Unserialize With Memory/Size Constraints使用内存/大小约束反序列化
【发布时间】:2020-10-06 05:19:34
【问题描述】:

有没有办法在内存/大小限制的情况下使用unserialize?

目前我们有:

$data = unserialize($_SESSION['visits']);

我们偶尔会得到:

PHP 致命错误:第 0 行 Unknown 中允许的内存大小为 134217728 字节已用尽(尝试分配 17645568 字节)

当访问者在短时间内进行了多次访问时(会话值存储有关访问的每个页面的信息)。

如果$_SESSION['visits'] 的长度超过一百万个字符,则会导致问题,因此我可以对此进行简单检查,但是否有比这更好的解决方案:

 if(strlen($_SESSION['visits']) <= 1000000) {
    $data = unserialize($_SESSION['visits']);
} else {
    $data = array();
}

我认为try catch 可能会表现得更好,但它没有被抓住:

try{
    $data = unserialize($_SESSION['vists']);
} catch(\Exception $exception){
    error_log('Caught memory limit');
}

这个问题的答案不是增加内存大小。

【问题讨论】:

  • unserialize 没有限制,可用内存除外。这是RFC for PHP 7,它介绍了从错误到异常的变化。如果您向下滚动到并非所有错误都已转换,您会看到一条说明,即与内存相关的错误不会转换为异常。 “有些是不可能的,比如内存限制”。尽管您可以使用register_shutdown_function,但您所能做的就是吐出一条快速死亡的消息并记录一些内容。您可以在序列化时处理大小限制,而不是捕获反序列化吗?
  • 内存限制错误是致命错误而不是异常,这就是您无法捕获它的原因。此外,没有办法将内存限制为仅用于一项功能。 memory_limit 是一个全局变量
  • 不完全正确:您可以捕获和后处理“致命”错误,但不能在 try/catch 块中,而且您也无法返回到原来的位置。您所能做的就是记录并...在新分支上做其他事情。但内存不足错误实际上是最难处理的错误之一,因为您无法在处理程序中执行任何使用更多内存的操作。
  • 会话数据在存储在文件系统中时已经在后台序列化/反序列化。您是否有“双重序列化”会话变量的特殊原因?另外,我想知道你到底在那儿存储了什么(以及为什么),以获得每个用户 100 万个字符的值得访问数据?我可以想到一些在块中实现反序列化的肮脏方法(当内存最大化时会导致部分数据),但似乎改变策略(和问题)是一种(更好)的前进方式。
  • @user3783243 如果您需要 14 年,请将 SessionID 保留在 Session 对象中,并为所有数据使用数据库。每次调用服务器时,您是否需要 PHP 真正解析所有这些数据?放入数据库意味着您只在需要时获得所需的数据,而无需处理更多数据。您也可以进行其他查询,例如访问类型。 (我们所有的会话处理都是这样处理的,除非项目没有附加数据库。在顶部使用内存缓存,它非常有效。)

标签: php memory serialization


【解决方案1】:

有两种选择:

a) 在另一个 phpprocess 中反序列化

这可能会因内存限制错误而失败,并且可能只返回您感兴趣的数据。

怎么做?

  1. 创建自定义 php 脚本/文件:
    1. 调用自动加载器/初始化应用程序
    2. 反序列化通过文件或$argv 传递的值
  2. 使用exec()调用该脚本

要解决问题要求,您可以:

  • 要么从自定义脚本返回内存使用情况(并检查该内存在父脚本中是否可用)
  • 或仅通过文件或标准输出返回您感兴趣的数据

b) 使用自定义解析器

像 https://github.com/xKerman/restricted-unserialize 一样,它允许:

  • 按单个值反序列化并检查您之前是否有足够的内存(同样的问题,但使用小数据可以更好地检查)
  • 遍历数据而不完全反序列化它们(因此不使用额外的内存)

c) 使用数据库

以上两个选项是您要求的解决方案。但是,我强烈建议将会话/访问数据存储在数据库中,然后只为它们存储一个唯一的 ID。

【讨论】:

  • 在另一个进程中反序列化但是如何?
【解决方案2】:

您将获得的最高效的内存可能是将所有内容存储在一个字符串中,以二进制形式打包,并使用手动索引。为此,您可以使用pack() 方法。

内存使用差异

$a = memory_get_usage();
$data = serialize(array(1 => 1, 0 => 2, 3 => 3));
$b = memory_get_usage();
$c = $b - $a;
echo $c; //Outputs 296

当相同的数据以二进制字符串的形式打包时。

$a = memory_get_usage();
$data = pack("C*",array(1 => 1, 0 => 2, 3 => 3));
$b = memory_get_usage();
$c = $b - $a;
echo $c; //Outputs 72

比预期节省更多内存,而且非常高效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-17
    • 1970-01-01
    • 2012-10-15
    • 1970-01-01
    相关资源
    最近更新 更多