首页 > 解决方案 > 使用 SSE 轮询数据库的最有效方法

问题描述

我觉得这一定是一个被解决了一百万次的问题,但是经过三个小时的搜索,我找不到答案。我试图弄清楚如何仅在 Mysql 表中发生更改时将数据发送到客户端,并以最有效的方式执行此操作。

对于流式音频播放器上的“正在播放”功能,每次歌曲更改时,我都需要使用艺术家和歌曲标题数据更新 <DIV> 标签。我使用 JQuery + Ajax 来执行此操作,但这似乎非常低效,因此我切换到服务器发送事件。我的服务器端代码如下。

然而,这似乎只是稍微更有效率,因为除非我的理解不正确,否则我的代码每五秒发送一次相同的艺术家 - 歌曲数据,即使播放中的歌曲没有变化。

<?php

//include db connect file
if (!isset($pdo)) {
include '../../PDO_con.inc';
}

//run a query to get the most recent song for today
$statement = $pdo->query('[SELECT most recent record in table]');

while($row = $statement->fetch(PDO::FETCH_ASSOC)) {

    $artist = $row['artist_name'];
    $song   = $row['title'];
    $song_data = $artist . " - " . $song;

}

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Access-Control-Allow-Origin: *');

echo "retry: 5000\ndata: {$song_data}\n\n";
flush();

?>

似乎我应该每 5 秒一次又一次地运行选择查询(也许通过将其包装在 WHILE 循环中?),但仅在发生更改时发送 SSE 数据。我应该能够自己解决这个问题,但到目前为止我还没有找到答案。感谢您的任何建议。

编辑:作为对评论的回应,这里是选择最新记录的查询:

'SELECT artist.artist_name, song.title, plays.play_date FROM plays INNER JOIN song ON plays.song_id = song.id INNER JOIN artist ON song.artist_id = artist.id WHERE date(plays.play_date) = date(now()) ORDER BY plays.play_date DESC LIMIT 1';

我所说的效率是指只在发生变化的地方发送数据。现在,我的脚本每 5 秒发送一次数据,无论它是否已更改。客户端的 <DIV> 标记使用相同的数据更新现有数据,直到发生更改。因此,对于一分钟的歌曲,艺术家+标题数据被发送 12 次。这似乎是对互联网资源的浪费,尽管我认为考虑到互联网流量的总量,这可能是一种微不足道的浪费。

标签: phpmysqlserver-sent-events

解决方案


只要您有某种方法可以确定哪些记录需要从数据库推送到客户端,您就可以定期轮询数据库。根据您的数据库设计的复杂性,您可能需要考虑其他优化,例如缓存。

只要连接打开,您就可以执行无限循环。每 5 秒轮询一次数据库以查看是否有任何新条目。跟踪您最近进行的一项调查。

这是一个非常粗略的例子:

<?php

header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");

$pdo = new \PDO("mysql:host=localhost;dbname=test;charset=utf8mb4", 'user', 'password', [
    \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
    \PDO::ATTR_EMULATE_PREPARES => false
]);

$lastTime = new DateTime('1 minute ago');
$stmt = $pdo->prepare('SELECT message, created_at FROM messages WHERE created_at>? ORDER BY created_at');

while (!connection_aborted()) {
    $stmt->execute([$lastTime->format('Y-m-d H:i:s')]);

    foreach ($stmt as $message) {
        $lastTime = new DateTime($message['created_at']);
        echo 'data: This is a message at time ' . $lastTime->format('Y-m-d H:i:s') . "\n\n";
    }

    ob_flush();
    flush();

    sleep(5);
}

每 5 秒轮询一次数据库并没有那么糟糕。普通用户每秒可以刷新几次页面,因此您的服务器应该可以轻松地每 5 秒处理一次轮询。


推荐阅读