使用 SSE 轮询数据库的最有效方法

Most Efficient Way To Poll Database Using SSE

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

对于流式音频播放器上的“正在播放”功能,每次歌曲更改时,我都需要用艺术家和歌曲标题数据更新

标签。我正在使用 JQuery + Ajax 来执行此操作,但这似乎非常低效,所以我切换到 server-sent 事件。我的 server-side 代码如下。

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

<?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();

?>

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

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

'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 秒发送一次数据,无论它是否发生变化。客户端的

标记使用相同的数据更新现有数据,直到发生更改。因此,对于一首 1 分钟的歌曲,艺术家+标题数据被发送 12 次。这似乎是对互联网资源的浪费,尽管我认为考虑到互联网流量的总量,这可能是微不足道的浪费。

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

您可以执行无限循环,只要连接打开就一直运行。每 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 秒处理一次投票。