【问题标题】:How to query, then process a large quantity in somewhat short time frame如何查询,然后在较短的时间内处理大量
【发布时间】:2014-10-17 12:46:44
【问题描述】:

我有一大张订单。在设定的时间,我需要向他们中的大部分人发送一条 SMS 消息(不幸的是,每个消息都有不同的内容)(基于他们是否选择了该消息以及是否包含电话号码)。在相当短的时间内可能有 200,000 多个数字。 (现在并没有那么高,但理论上可以并且想要这样构建)。

他们不必都立即发送,但是 - 在设定时间的 1-3 小时内是理想的。

我正在使用带有 API 的服务来发送它们,所以这不是问题 - 问题是:

1) 量大如何处理

2) 如何知道它们都已处理或重新处理未处理的那些

我不认为最好执行 MySQL 查询以获取所有 200,000 多个电话号码,然后循环遍历 - 我不得不假设这会吸收很多内存(?)。

所以 - 我想尝试一个 cron 作业,并让它每分钟(左右)运行一次。在该脚本中,我可以提取大约 5,000 条记录,标记为“处理中”,然后重复处理每条记录,然后将其所在行更新为“已发送”。

但这有很多潜在的问题......如果 SMS 服务速度变慢而我无法将它们全部发布怎么办。如果出现不可预见的错误并且脚本在中途停止......等等。如果有任何数量的事情导致该脚本无法发送所有 5000 条记录,我怎么知道要返回并重新处理哪些记录?

不仅是这个过程,还有其他一些过程 - 总的问题不断出现如何处理我们需要以某种方式处理的大量行并知道每个行都已完成。

我希望我只是把这件事弄得太复杂了,并且有一些更简单的方法来处理。

如果我不清楚,请发表评论,我很乐意进一步解释任何方面。

【问题讨论】:

  • 我希望我只是把这件事弄得太复杂了......你是。哇没想到我的回答会被接受...谢谢!我还是个新手,还在学习礼仪之类的。

标签: php mysql repeat large-data-volumes


【解决方案1】:

短版:

  1. 不用担心内存消耗。只是不要尝试一次获取整个结果集。

  2. 您的想法是使用单独的表格列出每条短信,然后在您知道它是否成功时更新该行通常是正确的方法(无论您是否在 cron 中执行此操作并不重要)。

  3. 如果您担心您的 SMS 提供商可能会丢弃您的一些请求,那么您可以使用 a ActiveMQ 或类似的东西来实现您自己的排队机制。但是,这在很大程度上违背了使用提供程序的目的。他们应该使用自己的队列,这样您就不必担心了。

详情:

短信服务应该会通知您成功或失败。 大多数高容量 SMS 服务将您的消息排队,并一次将它们以 n 条消息的块形式发送出去。然后,他们将通过某种回调或网络挂钩通知您,哪些消息已成功,哪些消息已失败。 它们中的大多数还提供 API,可让您检查是否已发送特定消息。您需要利用这些功能。

我认为你的 cron 方法走在正确的轨道上。

一种选择是永远不要“拉”记录。相反,在现有表上有一列指定它是否正在等待消息发送。这样,您无需执行 SELECT 并处理数十万行,而是执行简单的 UPDATE,然后当每个回调来自 API 时,您可以使用成功/失败重新更新行。

如果您可能会为每行数据一次发送多条消息,那么显然这不起作用。您必须有一个单独的表格,其中包含您要跟踪的每条消息的一行。

就您的记忆力而言,我认为这不是问题。只是不要获取整个结果集。相反,单独获取每一行。这将阻止 mysql 返回整个数据集,因此您不需要将其保存在内存中。

来自 php.net

由于 mysqli_fetch_all() 在单个步骤中将所有行作为数组返回,它可能比一些类似的函数(例如 mysqli_fetch_array() 一次只从结果集中返回一行)消耗更多的内存。此外,如果您需要对结果集进行迭代,您将需要一个循环结构来进一步影响性能。由于这些原因,mysqli_fetch_all() 应该只用于那些将获取的结果集发送到另一层进行处理的情况。

编辑/修正

解决评论/问题:

我不能在每个 chron 中只提取一个条目 - 这将花费很长时间......我 明白我也不应该一次获取整个结果集,那就是 是什么让我问“那我还能怎么做呢?

在 PHP(使用 mysqli 和 mysqlnd)中,当您进行查询时,它实际上并不返回数据。它会根据您的查询准备要返回的数据,但不会返回。

当您使用 fetch_all 时,您要求的是整个结果。 当你使用 fetch_array 时,你是在询问 next 结果,你是在告诉 mysql 移动结果光标,这样你就可以得到之后的下一个结果。只要您不将每个结果都存储在内存中(在单独的变量中),那么就不存在内存问题。只需根据需要使用该行,然后获取下一个。 是否是 cron 作业并不重要。
您不需要为每一行一遍又一遍地调用脚本。该脚本在一次调用中处理每一行。它只是一次读取一行,以节省内存。

这是一个脚本示例:

$mysqli = new mysqli("host", "user", "pass", "db");
$query = "SELECT * from TextMessages";
$result = $mysqli->query($query);
while ($row = $mysqli->fetch_array($result))
{
    //this is the only thing you store in memory, one single row at a time
    $row = $result->fetch_array(MYSQLI_ASSOC);

    //go send the text message and do whatever else you need to do
    if ($row["SomeSmsToken"] == null && $row["TextHasAlreadyBeenSentOrDateSentOrWhatever"] == false)
    {
        //$someSmsToken = $myTwilioObject->SendByRow($row);
        //$this->UpdateRowToTellItThatItHasBeenSentToProviderAndIsWaitingForResponse($row,$someSmsToken);
        //..etc...
        //then go to the next row.
    }
}
$result->free();

然后在一些回调脚本中你会做这样的事情。

$mysqli = new mysqli("host", "user", "pass", "db");
$query = "SELECT * from TextMessages where SomeSmsToken = '".$_POST["SomeTokenSentFromProviderInCallback"]."'";
$result = $mysqli->query($query);
while ($row = $mysqli->fetch_array($result))
{
  $someObject->UpdateRowToSayThatTheTextWasSentOrItFailed($row,$_POST["SomeStatusSentFromProviderInCallback"]);
}

$result->free();

您也可以在完成后使用mysqli_free_result,以释放 php 的 mysql 驱动程序消耗的所有内存。

来自 php.net:

当您的结果对象不再需要时,您应该始终使用 mysqli_free_result() 释放结果。

编辑: 如果您想要某种聪明的方法来处理“如果脚本超时怎么办”,我建议每分钟运行一次 cron。 当它运行时,它应该检查它是否已经在运行,如果它还没有运行,那么你运行它。 该脚本会一直工作,直到超时。
然后在一分钟内,cron 将再次启动它,并且由于它没有运行,它会再次运行并从它停止的地方继续。

【讨论】:

  • SMS 提供商不发送没有问题,我只是举例说明我是否因任何原因无法连接到它们。我不明白你的其余答案。如果不拉(即 mysql 选择)行,我怎么知道要发送哪些行然后更新?每行不发送多个 - 只发送 1 个。我很欣赏这个答案,也许有一些东西,但我根本不关注。我不能每个 chron 只提取一个条目 - 这将永远花费......我知道我也不应该一次获取整个结果集,这就是让我问“那我还能怎么做呢?”
  • 我已经重读了大约 10 次,虽然它措辞得当,但 我认为它没有解决问题中的问题。如果我只是不明白你的意思,请澄清或让我知道。谢谢。
  • 我认为这个问题很模糊,所以答案可能也是如此。如果我理解,您问:我正在使用带有 API 的服务来发送它们,所以这不是问题 - 问题是:1)如何处理大量 2)如何知道它们都被处理或重新处理那些没有的。我将更新我的答案以回答您现在提出的更具体的问题。
  • 我已经编辑/修改了我的答案,以提供有关如何在不使用大量内存的情况下获取数据的详细信息。
  • 抱歉所有的编辑。我添加了一些伪代码来保存短信状态并通过回调/webhook 更新它。
猜你喜欢
  • 2014-12-20
  • 1970-01-01
  • 2015-11-26
  • 1970-01-01
  • 2021-12-17
  • 1970-01-01
  • 1970-01-01
  • 2020-02-24
  • 2021-01-28
相关资源
最近更新 更多