【问题标题】:PHP, MySQL, Cron Job - Efficient method to maintain current/live data in large tables?PHP、MySQL、Cron Job - 在大型表中维护当前/实时数据的有效方法?
【发布时间】:2016-06-20 02:12:01
【问题描述】:

这主要是理论,所以如果它变得冗长,我很抱歉。

背景

我正在从事的项目从其他网站(外部网站,非我们托管)提取信息。我们希望尽可能提供最接近实时的信息,以便我们的用户能够立即获得相关信息。这意味着不断监控和更新表格。

很难展示我以前在这方面的工作,但在过去几周里,我一直在搜索“在数据库中维护实时数据”和“在发生外部更改时立即更新数据库”等类似内容.但一切都无济于事。我想维护最新记录的问题很常见,所以我不确定为什么针对它的彻底解决方案似乎如此罕见。

为了遵守 SO 的指导方针,我不是在寻找意见,而是在寻找当前的最佳实践和行业中最常用/接受的有效方法。

目前,使用cron job,我们能做的最好的事情就是每分钟运行一个进程。

* * * * * cd /home/.../public_html/.../ && /usr/bin/php .../robot.php >/dev/null 2>&1

问题是,我们正在从数千个其他站点提取数据(每一行都是一个站点),有时更新可能需要几分钟或更长时间。每分钟只调用一次函数是不够的。理想情况下,我们想要近乎即时的分辨率

检查是否需要更新行很快。基本上只是您的简单哈希比较:

if(hash(current) != hash(previous)){
    ... update row ...
}

使用仅由 cron 作业触发的进程意味着,如果一行最终得到更新,则该进程将被暂停,直到它完成,或者直到 cron 作业在一分钟后触发一个新进程。

没有布埃诺!呸!如果由于某种可怕的命运转折,每一行都需要更新,那么可能需要数小时(或更长时间)才能使所有记录成为最新。并且在那个时候,已经被传递的行将会过时。

注意:数据库的设置方式使得当前正在更新的行无法被新进程访问。该函数实质上是向下爬取表格,找到下一个尚未读取/更新的可用行,然后深入研究。更新完成后,它会继续下一个可用行。

每个进程在到达表的末尾或表中的所有行都标记为已读时被终止。此时,所有行都被重置为未读,流程重新开始。

随着收集的数据量越来越大,提高分辨率的唯一方法是同时运行多个进程。

但是多少才算太多?

可能的解决方案(方法)

到目前为止,我想出的最好的方法是,尽可能快地遍历所有行,是这样的:

  1. Cron Job 调用第一个进程 (P1)

  2. P1 浏览表,直到找到未读且需要更新的行,然后潜入

  3. 一旦 P1 进入该行,它就会调用第二个相同的进程 (P2) 从该点继续

  4. P2 浏览表,直到找到未读且需要更新的行,然后潜入

  5. 一旦 P2 进入该行,它就会调用第三个相同的进程 (P3) 从该点继续

...等等。

本质上,每当一个进程进入一行来更新它时,就会调用一个新进程来继续。

但是...父进程并没有死。这意味着一旦他们完成更新,他们就会再次开始爬取表格,寻找下一个可用行。

并且...除此之外,每分钟仍会触发一个新的 cron 作业。

这意味着可能有数千个相同的进程可能同时运行。进程数不能超过表中的记录数。最坏的情况是每一行都同时更新,并且在任何更新完成之前触发一两个 cron 作业。 cron 作业将立即终止,因为没有可更新的行。随着每个进程完成其更新,它也会因同样的原因立即死亡。

上述情况是最坏的情况。每次传递需要更新超过 5 或 10 行的可能性不大,但理论上可以同时更新每一行。

可能的改进(主要是资源,而不是速度或分辨率)

  1. 监控和限制允许的活动进程的数量,并杀死任何被触发的新进程。但这会引出诸如“多少才算太多?”和“达到特定分辨率所需的最小数量是多少?”之类的问题

  2. 让每个进程一次标记多行 (5-10),并且在处理完集合中的所有行之前不要继续。这会导致同时处理的最大数量减少一个因子,无论多少行一次被标记。

就像我一开始所说的,这对于数据库架构师来说肯定是一个常见的问题。有没有比我提出的更好/更快/更有效的方法来维护当前记录?

感谢您一直陪伴我!

【问题讨论】:

  • 为了解决此类问题,我们使用队列管理器、cron 和简单的手工 php-daemon(使用 symfony2 框架占用大约 60mb 内存)。 Ofc 守护进程可以是多线程的,但我们使用简单的单线程版本。此外,当脚本启动多次并在找到运行副本时退出时,守护程序会检查自身的运行状态。守护程序在 10 分钟后死亡(以解决可能的内存泄漏)或队列为空。像这样的......

标签: php mysql sql-server database cron


【解决方案1】:

首先,我阅读了所有内容!只是不得不为此拍拍自己的背:)

您可能正在寻找的是工作队列。排队基本上就像你在超市里看到的那样,工作人员是在柜台收钱并为每个顾客做所有事情的女人。没有顾客时,她不工作,有顾客时,她确实工作。

当商场里有很多顾客时,更多的工人在空荡荡的柜台上,买杂货的人被分散到所有的人身上。

我最近写了很多关于队列的文章,我最推荐的是Beanstalk。它使用简单,如果您打算在 php 中创建队列和工作线程(并从那里控制 MySQL 数据库中发生的事情),它会使用 Pheanstalk API。

队列脚本和工作脚本的外观示例类似于以下内容(显然,您可以添加自己的代码以适应您的特定需求,并且可以生成任意数量的工作人员。您甚至可以让您的工作人员根据您队列中的需求量而有所不同):

将作业添加到队列中

<?php
$pheanstalk = new Pheanstalk('127.0.0.1:11300');
$pheanstalk
  ->useTube("my_queue")
  ->put("UPDATE mytable SET price = price + 4 WHERE stock = GOOG");//sql query for instance
?>

根据您的描述,您似乎正在设置事务,这会禁止在执行其他更新时进行某些更新。这实际上是使用队列的一个重要原因,因为如果队列作业超时,它会被发送到队列的顶部(至少在我描述的 pheanstalk 队列中),这意味着它赢了'在超时的情况下不会丢失。

工作脚本:

<?php
    $pheanstalk = new Pheanstalk('127.0.0.1:11300');

    if ($job = $pheanstalk
    ->watch('my_queue')
    ->ignore('default')
    ->reserve())//retreives the job if there is one in the queue
    {
        echo $job->getData();//instead of echoing you would 
                             //have your query execute at this point

        $pheanstalk->delete($job);//deletes the job from the queue
    }
}
?>

您必须进行一些更改,例如设计您将拥有多少工人。您可以将 1 个 worker 放入 while 循环中,获取所有作业并一个一个地执行它们,然后调用其他 worker 脚本来帮助您,以防您看到您执行了 3 个或更多作业。管理队列的方法有很多种,但在您描述的这种情况下经常使用这种方法。

pheanstalk 推荐的库队列的另一个巨大好处是它非常通用。如果将来您决定以不同的方式组织您的员工,您可以轻松地做到这一点,并且有许多功能可以让您的工作更轻松。没有理由重新发明轮子。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-09
    • 2011-03-31
    • 2016-12-08
    • 2023-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-08
    相关资源
    最近更新 更多