PHP中持续检测数据库变化的最佳实践是什么?

6
我正在编写一个后端模块(用PHP编写),用于监控私人聊天室,如果[ 正好 ] 300秒(5分钟)没有活动,则需要更新数据库(设置最大用户数和其他内容)。我通过now()和上一条消息发送时间的时间差来监控闲置时间段。
我的做法是:设置一个cron作业,每分钟或60秒运行一次我的监控脚本(通过php-cli)。在监控脚本内部:
$expire_time = time() + 60;

//this loop will run for 60 seconds
while(time() < $expire_time)
{
  $idle_time = get_all_chatrooms_idle_time();
  foreach($idle_time as $s_time)
  {
    if($s_time >= 300)
    {
      update_changes();
    }
  }
  usleep(500000);
}

在300秒空闲时间后立即设置最大用户数量的条件是不可商议的。因此,我不能真正按照“除非有实际要求,否则避免做任何事情”的建议行事,尽管这听起来很有道理。

为什么呢?活跃和非活跃聊天室数据需要实时更新,因为它将显示在仪表板上。聊天室管理员的薪水取决于此。


为什么不在每次仪表板加载时检查它们?对不起,仍然不可能。

检查需要在服务器端进行,并且仪表板使用ajax自动更新,每秒轮询一次。

当我将监控代码附加到由我的ajax调用请求的页面时,我认为它比我的当前实现更占用资源(如果我错了,请纠正我)

让我给你一些用户数量的粗略估计,这样您可以想象出我们正在承受的负载/流量:

  • 包括管理员在内的聊天者数量:约800人
  • 聊天室数量:约250个
  • (x)聊天室管理员数量:约50人
  • (x)我的老板和他的员工:

(x)- 可查看仪表板


有更好的方法吗?我的做法正确吗?


似乎你做错了。你听说过CRON作业吗?我认为最好的方法是设置每1分钟(或更短)运行一次简单的MySQL脚本来执行CRON作业。 - Stranger
可能是 https://dev59.com/UFPTa4cB1Zd3GeqPmtXd 的重复问题。 - Benjamin Paap
听起来PHP可能不是最适合这项工作的工具。PHP非常擅长处理无状态系统的服务器端脚本,但一旦引入实时复杂性,你就不得不使用像Unix cron-job、伪cron(就像你正在做的那样)和其他绕过解决方案来让脚本按计时器执行的方法,而PHP则显得力不从心。 - SamT
@BenjaminPaap 我认为这是另一种情况,因为 "我正在通过现在()和上次发送消息的时间差来监控空闲时间跨度",所以这不是你仅仅可以从数据库中推断出来的事件。或者如果我错了,请纠正我。 - yowmamasita
@SamT你有什么建议?顺便说一下,你可以把它作为答案发布,因为这不仅是算法问题,还涉及技术栈是否合适。谢谢! - yowmamasita
@陌生人 在第二部分中,“我做了什么:设置一个cron作业,每分钟或60秒运行我的监控脚本(通过php-cli)…” - yowmamasita
3个回答

2
这个循环过度了。即使在一个中等的服务器上,它可能每分钟运行几千次,并且即使对于实时应用程序,它也会产生高CPU使用率。添加一个计数器,并查看迭代次数。我认为这比每个AJAX请求都处理更多的负载。
首先,确定您需要的信息的粒度。假设您选择3秒的粒度(例如,每3秒扫描一次数据库)-这个数字可能对您来说太高,但它说明您不会失去太多。使用每秒轮询AJAX,您可能会看到一些计数器应该持续爬升的计数器爬升一两次后回落。(您是否真的会看到这种情况取决于计数器的性质。)
如果您的计数器基于秒级数据(例如,显示经过的秒数的总和或以每秒$为基础的金额),则每秒的AJAX轮询将无法提供连续的计数器。(由于网络原因,它有时会错过一秒钟或更新到该秒两次)。
无论选择什么样的粒度,最终的统计数据都是正确的,因为它们基于绝对时间戳-无论何时评估它们都可以。
如果使用每秒的AJAX轮询来实现平滑计数器,则可以做得比这更好:计数应在客户端运行(例如,使用JS发送带有每秒递增的值:revenue: <span data-inc="25">14432</span>并进行计数)。然后仅实现AJAX以监视停止/重置计数器的条件。然后,您只需要确定通知可能会延迟多长时间(例如10秒),然后计数器将超出最大值。 10s回到预期值。在这种情况下,您不应该经常运行DB清理(例如,间隔的一半)。这允许例如在您的周期中进行3秒的睡眠,从而大大降低负载。
如果您可以轻松选择向数据库添加每个聊天室的过期时间戳(记录内或固定)并带有索引,那么这将加速读取(并额外允许每个房间的过期规则)。

1
如果您想使用已经编写好的BL代码并且需要轻松访问,PHP可以胜任这项工作。但是它在作为实时跟踪服务(用于实时跟踪)时会遇到问题。在一个系统上,您可以解除至少此脚本的运行限制,然后创建一个逻辑,使其sleep()直到下一个可能的事件[例如下一个过期时间或最小可能的过期时间]。如果您必须使用运行时限制,则仍然可以使用现在使用的方案(每分钟cron一个在一分钟后退出的脚本),只需添加一些sleep()即可! - Levente Pánczél
非常抱歉,但是我忘记添加了usleep(500000)的代码。500毫秒的休眠基于我们服务器与大多数客户端之间的平均延迟。因此...循环:每秒最多2次调用,ajax轮询:如果有60个观众,则每秒60次调用。 - yowmamasita
1
好的,你在正确的方向上。有关客户端计数以允许降低检查延迟的建议仍然适用。另外,休眠直到下一个可能的事件也适用:如果您的选择表明下一个过期时间在 X 秒后(如果当前没有打开的聊天室,请取一个大值),并且系统中最小的过期时间(即使计算可能创建的房间)为 Y 秒,则可以睡眠 min(X,Y) 而无需重新运行检查;然后进行检查,可能处理,并确定新的 X。 - Levente Pánczél

1
#!/usr/bin/php
<?php

if( file_exists('/tmp/chatrooms_cron.lock') ) {
   die( 'There is already a script running.' );
}

file_put_contents( '/tmp/chatrooms_cron.lock', 1 ); // Storing pid would be better 

// Run loop forever
while( true )
{
  $idle_time = get_all_chatrooms_idle_time();

  foreach($idle_time as $s_time)
  {
    if($s_time >= 300)
    {
      update_changes();
    }
  }

  sleep( 60 );
}

关于node.js(带有socket.io)的提示:

我在工作中一直在使用node.js,它非常棒!它可以实时与浏览器交互,支持ie5/ie6及以下版本。这可以在node.js内部完成。

setInterval( function() {
   // Fetch and update peers
}, 300000 );

1
根据评论,我建议使用像node.js这样的技术来构建应用程序,它是一个基于事件驱动的I/O异步平台。如果聊天室是一个外部应用程序,您可以轻松地打开一个套接字并监听聊天室,记录它,检查用户的活动,并侦听特定事件。

当聊天消息输入时,清除函数update_max_users()的超时计时器,将settimeout update_max_users()设置为300秒。 - yowmamasita

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接