本文目录导读:

- 方案一:Redis + Sorted Set(最推荐)
- 方案二:MySQL 联表查询 + 定时缓存
- 方案三:定时计算 + 静态文件缓存(极简方案)
- 方案四:WebSocket 推送(极致的实时性)
- 架构选型建议
- 核心优化要点(针对“实时”)
在PHP中实现排行榜的实时更新,有多种方案,从简单到复杂,各有优缺点,以下是几种主流且高效的实现方式,按推荐程度从高到低排序:
Redis + Sorted Set(最推荐)
这是目前互联网公司最常用的方案,性能极高,天然支持排序和范围查询。
核心原理: 利用 Redis 有序集合(ZADD)的分数自动排序功能。
实现步骤:
-
写入/更新分数: 当用户产生行为(如获得积分、击杀数)时,直接调用 Redis 命令:
$redis->zIncrBy('game:rank:2024', 10, 'user_id_123'); // 分数增加10,使用ZINCRBY自动累加 -
获取排行榜(前N名):
// 获取前10名(分数从高到低) $top10 = $redis->zRevRange('game:rank:2024', 0, 9, true); // 返回数组 [user_id_123 => 100, user_id_456 => 90, ...] -
获取某个用户排名:
$rank = $redis->zRevRank('game:rank:2024', 'user_id_123'); // 返回值从0开始,需要+1
优点: 毫秒级响应,内存操作,不占用数据库压力,支持分页和范围查询。 缺点: 需要引入Redis服务,需要处理数据持久化(定期刷入MySQL)。
MySQL 联表查询 + 定时缓存
适合中小型项目,数据量不大(万级以内)的场景。
实现步骤:
-
建表设计:
CREATE TABLE user_scores ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, score INT DEFAULT 0, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY idx_user (user_id) -- 唯一索引,防止重复 ); -
写入/更新:
INSERT INTO user_scores (user_id, score) VALUES (1, 10) ON DUPLICATE KEY UPDATE score = score + 10;
-
查询排行榜:
SELECT user_id, score, @rank := @rank + 1 AS rank FROM user_scores, (SELECT @rank := 0) r ORDER BY score DESC LIMIT 10;
“实时”处理技巧: 不要频繁查询数据库,而是使用 定时任务(Cron) 或 异步队列,每隔 1-2 分钟将最新数据写入 Redis 或静态文件缓存,前端直接读缓存。
优点: 简单,数据可靠,无需额外技术栈。 缺点: 当数据量大或并发高时,SQL性能瓶颈明显。
定时计算 + 静态文件缓存(极简方案)
如果排行榜允许几分钟延迟,可以使用纯 PHP 文件缓存。
实现步骤:
-
写一个生成脚本(cron.php):
// 查询数据库计算排名 $list = $db->query("SELECT user_id, score FROM user_scores ORDER BY score DESC LIMIT 100")->fetchAll(); // 存储为JSON文件 file_put_contents('/tmp/rank_cache.json', json_encode($list)); -
配置Cron任务:
*/1 * * * * php /path/to/cron.php
-
前端读取静态文件:
$rank_data = json_decode(file_get_contents('/tmp/rank_cache.json'), true); echo json_encode($rank_data); // 直接输出,不查数据库
优点: 最节省服务器资源,不占数据库连接。 缺点: 不是严格实时,有延迟。
WebSocket 推送(极致的实时性)
如果你希望排行榜变化时立即推送到所有在线用户页面(如直播打赏榜),则需使用 WebSocket。
实现原理: 用户操作 → PHP后端 → 更新Redis/MySQL → 通过消息队列(如 RabbitMQ)或 Redis Pub/Sub → 推送到 WebSocket 服务(如 Workerman、Swoole) → 浏览器JS更新页面。
PHP+Workerman 示例(伪代码):
// 业务代码中触发推送
$redis->zIncrBy('rank', 10, $uid);
$message = json_encode(['uid'=>$uid, 'score'=> $newScore]);
// 通过Redis发布消息
$redis->publish('rank_update', $message);
// Workerman Worker 监听订阅并推送
$worker = new Worker('websocket://0.0.0.0:2346');
$worker->onWorkerStart = function() {
$subscribe = new Redis();
$subscribe->subscribe(['rank_update'], function($redis, $channel, $msg) {
// 通过WebSocket推送给所有连接的客户端
foreach ($GLOBALS['clients'] as $client) {
$client->send($msg);
}
});
};
优点: 真正的实时,用户体验极佳。 缺点: 架构复杂,需要维护长连接,开发成本和运维成本高。
架构选型建议
| 应用场景 | 推荐方案 |
|---|---|
| 大型游戏、高并发、秒级更新 | 方案一(Redis) |
| 中型网站、分钟级更新可接受 | 方案二(定时缓存) |
| 个人项目、简单展示 | 方案三(静态文件) |
| 直播PK、打赏滚动、需主动推送给所有用户 | 方案四(WebSocket) |
核心优化要点(针对“实时”)
- 避免实时SQL排序: 永远不要用
ORDER BY直接查全表排序,一定要用缓存(Redis/内存表)。 - 合并请求: 用
MGET一次性获取多个用户数据,减少网络开销。 - 分页加载: 排行榜通常有“排行榜”和“我的排名”两个视图,只查询需要的数据,不要一次拉取全榜。
对于绝大多数 PHP 项目,“Redis + Sorted Set” 是平衡性能与开发成本的最佳选择,如果你还没有 Redis,建议优先考虑引入它。