事件源的性能表现

3

我目前正在进行一个大型项目,需要实现服务器发送事件。我决定使用事件源传输来完成,从简单的聊天开始。目前客户端只监听新聊天消息事件,但未来该项目将有大量其他事件。首先,我非常关注服务器端脚本和其中的循环,其次,我不确定在此情况下是否实际上使用MySQL数据库作为存储(例如,用于聊天消息)是一个好的做法。当前循环根据出现在数据库中的新消息提供新消息:

$statement = $connect->prepare("SELECT id, event, user, message FROM chat WHERE id > :last_event_id");
while(TRUE) {
    try {
        $statement->execute(array(':last_event_id' => $lastEventId));
        $result = $statement->fetchAll();
        foreach($result as $row) {
            echo "id: " . $row['id'] . "\n";
            echo "event: " . $row['event'] . "\n";
            echo "data: |" . $row['user'] . "| >>> \n";
            echo "data: " . $row['message'] . "\n\n";
            $lastEventId++;
        }
    } catch(PDOException $PDOEX) {
        echo $PDOEX->getMessage();
    }
    ob_flush();
    flush();
    usleep(10000);
}

据我所了解,这种循环是不可避免的,我的任务是优化它的性能。目前,我在while()之外使用准备好的语句和合理的usleep()

因此,对于那些有服务器端事件经验的人,有以下问题:

  1. 在中等负载的网站(在线1000-5000用户)中使用这种技术是否合理?
  2. 如果是,有什么方法可以提高性能?
  3. MySQL数据库在这种情况下可能成为瓶颈吗?

非常感谢任何帮助,因为这个问题相当复杂,搜索信息也无法给我任何提示或测试方法。

3个回答

4
所有的1000多个用户都会同时连接吗?您是否在使用Apache和PHP?如果是这样,我认为您真正应该关注的是内存:每个用户都保持一个套接字、一个Apache进程和一个PHP实例。您需要自己测量,针对您自己的设置,但如果我们说每个用户20MB,那么1000个用户就需要20GB的内存。如果您将每个进程限制为12MB,那么每1000个用户仍需要12GB。(m2.xlarge EC2实例具有17GB的内存,因此如果您为每500-1000个用户预算一个这样的实例,我认为您会没问题。)
相比之下,您的10秒轮询时间,CPU使用率非常低。出于同样的原因,我不认为轮询MySQL数据库会成为瓶颈,但在这个使用级别上,我会考虑让每个DB写入也写入到memcached中。基本上,如果您不介意投入一些硬件,您的方法看起来是可行的。虽然这不是内存最有效的使用方式,但如果您熟悉PHP,它可能是程序员时间最有效的使用方式。

更新:刚看到OP的评论,意识到usleep(10000)是0.01秒,而不是10秒。糟糕!这改变了一切:

  • 你的CPU使用率现在很高!
  • 你需要在脚本顶部加上set_time_limit(0):由于时间限制太紧,你很快就会达到默认的30秒CPU使用量。
  • 你应该使用通知队列服务,而不是轮询数据库。

我会使用队列服务,而不是memcached,你可以找到一个现成的解决方案,或者用PHP编写一个自定义的解决方案。你仍然可以将MySQL作为主要数据库,并让队列服务轮询MySQL;这里的区别是你只有一个进程密集轮询它,而不是一千个。队列服务是一个简单的套接字服务器,它接受每个前端PHP脚本的连接。每当它的轮询发现一个新消息时,它会将其广播给所有已连接到它的客户端。(有不同的架构方式,但我希望这能给你一个大致的想法。)

在面向前端的PHP脚本中,您使用带有15秒超时的socket_select()调用。它仅在没有数据时唤醒,因此在其余时间不使用CPU。(15秒超时是为了发送SSE保持活动信号。)

(20MB和12MB数字的来源)


感谢您的回复。正如Dave所建议的那样,我目前正在尝试让memcache为我的目的工作,并且它作为临时存储,例如用于传入的聊天消息。'poll time'以微秒为单位,但这只是一个延迟,以便循环不会提交太多迭代。当php脚本达到max_execution_time时,事件源会打开新连接,每30秒钟一次。我认为事件源的特性是它建立了保持活动连接并刷新服务器回显的所有内容,或者我错了吗?网络显示每30秒钟有一个请求。 - Damaged Organic
1
你的回答非常...启发性,可以这么说,真的给了我一个想法。我目前正在尝试设置单个绑定套接字,它可以监听多个对等方,并轮询类型为MEMORYmySQL数据库表 - 所有这些都应通过event-source传递给客户端。这是目前对我来说的答案。谢谢!:) - Damaged Organic

2
  • 这种技术在中等负载的网站(1000-5000个在线用户)中使用是否合理?

除非您将刷新计时器放在客户端并仅使用服务器端作为Web服务,否则几乎是唯一的方法。当有那么多用户时,负载会很高,但您受到仅使用纯PHP解决方案的限制,我宁愿查看服务器上的C / C ++守护程序和原始套接字。

  • 如果是,是否有任何提高性能的方法?

使用memcached作为临时存储,然后使用后端进程每小时/每分钟提交归档到mysql数据库。

  • 在这种情况下,MySQL数据库可能成为瓶颈吗?

是的,但这取决于您愿意为解决方案投入多少硬件或者您设置类似于使用一个读取和一个写入数据库的主从复制的东西的信心有多大。

希望这可以帮助到您。


1

我的2分钱

@Darren Cook的解决方案是一种方法。然而,在做同样的事情后,我很快发现了这种方法的严重缺陷:

  1. 在新部署之后,循环中运行的代码将永远不会被刷新。
  2. 如果您的网站变得受欢迎,您很快就会遇到数据库“最大连接数”错误。
  3. 它不允许基础架构中的其他服务加入并向连接的客户端发送相关数据。您必须不断获取所需内容并将其作为SSE事件发送给客户端。非常痛苦。

我找到了Mercure(https://mercure.rocks/),它也提供开源版本。它可以用作发布/订阅系统,向连接的客户端发送SSE事件。这些事件可以使用浏览器的EventSource对象消费。这种方法的美妙和简单之处在于Mercure服务器不需要(也不需要)了解您的业务逻辑、PHP或任何编程语言。客户端需要订阅一个频道,发布者可以向这些频道推送任何内容。如果需要,消息可以从您的PHP或基础架构中运行的任何服务推送。


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