本文目录导读:

在PHP项目中实现排行榜,通常需要结合数据存储(如数据库、Redis)和排序逻辑,以下是几种常见的实现方案,从简单到复杂:
基于数据库的排行榜(最基础)
表结构设计
CREATE TABLE user_rank (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL UNIQUE,
username VARCHAR(50),
score INT NOT NULL DEFAULT 0,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
查询排行榜
// 获取排名前10的玩家
$sql = "SELECT user_id, username, score,
FIND_IN_SET(score, (SELECT GROUP_CONCAT(score ORDER BY score DESC) FROM user_rank)) AS rank
FROM user_rank
ORDER BY score DESC
LIMIT 10";
// 如果要获取指定用户的排名
function getUserRank($userId) {
global $db;
// 先获取该用户的分数
$user = $db->query("SELECT score FROM user_rank WHERE user_id = ?", [$userId]);
if (!$user) return false;
// 统计分数高于该用户的人数
$rank = $db->query("SELECT COUNT(*) + 1 as rank FROM user_rank WHERE score > ?", [$user['score']]);
return $rank['rank'];
}
优点:简单,无需额外组件
缺点:数据量大时性能差,实时更新压力大
Redis Sorted Set 排行榜(高性能)
Redis的有序集合(Sorted Set)是排行榜的最佳选择。
安装Redis扩展
# 使用 phpredis 或 predis 扩展 composer require predis/predis
核心代码实现
<?php
class RankService {
private $redis;
private $rankKey = 'game:rank'; // 排行榜key
public function __construct() {
$this->redis = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
}
// 更新用户分数(加分)
public function updateScore($userId, $increment) {
$this->redis->zincrby($this->rankKey, $increment, $userId);
}
// 设置用户分数(直接设置)
public function setScore($userId, $score) {
$this->redis->zadd($this->rankKey, $score, $userId);
}
// 获取排行榜(从高到低)
public function getLeaderboard($start = 0, $count = 10) {
$results = $this->redis->zrevrange($this->rankKey, $start, $start + $count - 1, 'WITHSCORES');
$leaderboard = [];
$rank = $start + 1;
foreach ($results as $userId => $score) {
$leaderboard[] = [
'user_id' => $userId,
'score' => $score,
'rank' => $rank++
];
}
return $leaderboard;
}
// 获取用户排名(排名从0开始,加1为实际排名)
public function getUserRank($userId) {
$rank = $this->redis->zrevrank($this->rankKey, $userId);
if ($rank === false) return false;
return $rank + 1; // 转为从1开始的排名
}
// 获取用户分数
public function getUserScore($userId) {
return $this->redis->zscore($this->rankKey, $userId) ?: 0;
}
// 获取排行榜总人数
public function getTotalPlayers() {
return $this->redis->zcard($this->rankKey);
}
// 获取指定分数区间的用户(如:同分选手)
public function getUsersByScoreRange($minScore, $maxScore) {
return $this->redis->zrangebyscore($this->rankKey, $minScore, $maxScore);
}
}
// 使用示例
$rankService = new RankService();
// 用户完成游戏,加10分
$rankService->updateScore(1001, 10);
// 获取前10名
$top10 = $rankService->getLeaderboard(0, 10);
// 获取用户1001的排名
$myRank = $rankService->getUserRank(1001);
echo "我的排名: 第{$myRank}名";
优点:毫秒级排序,支持大量数据,实时性强
缺点:需要维护Redis服务,数据可能丢失(可持久化)
数据库 + Redis 混合方案(推荐生产环境)
策略
- Redis:处理实时查询和更新
- MySQL:做数据持久化,定期同步
<?php
class HybridRankService {
private $redis;
private $db;
private $rankKey = 'game:rank';
// 用户完成游戏时
public function onGameComplete($userId, $scoreIncrement) {
// 1. 更新Redis(实时排行榜)
$this->redis->zincrby($this->rankKey, $scoreIncrement, $userId);
// 2. 异步更新数据库(通过消息队列或定时任务)
$this->queueDatabaseUpdate($userId, $scoreIncrement);
}
// 定时同步任务(每5分钟执行)
public function syncToDatabase() {
// 获取所有用户的分数
$allUsers = $this->redis->zrange($this->rankKey, 0, -1, 'WITHSCORES');
// 批量更新数据库
$sql = "INSERT INTO user_rank (user_id, score) VALUES (?, ?)
ON DUPLICATE KEY UPDATE score = VALUES(score)";
foreach ($allUsers as $userId => $score) {
$this->db->execute($sql, [$userId, $score]);
}
}
// 数据库初始化Redis(应用启动时)
public function initFromDatabase() {
$users = $this->db->query("SELECT user_id, score FROM user_rank");
// 清空并重新加载
$this->redis->del($this->rankKey);
foreach ($users as $user) {
$this->redis->zadd($this->rankKey, $user['score'], $user['user_id']);
}
}
}
特殊场景排行榜
1 多维度排行榜(按周/月/总榜)
// 使用不同key区分时间维度
$redis->zadd('rank:daily:2024-01-15', $score, $userId);
$redis->zadd('rank:weekly:2024-w03', $score, $userId);
$redis->zadd('rank:monthly:2024-01', $score, $userId);
$redis->zadd('rank:total', $score, $userId);
2 好友排行榜
// 用户A的好友列表
$friendIds = [1001, 1002, 1003, 1004];
// 获取好友在排行榜中的信息
$scores = [];
foreach ($friendIds as $friendId) {
$score = $redis->zscore($this->rankKey, $friendId);
$rank = $redis->zrevrank($this->rankKey, $friendId);
if ($score !== false) {
$scores[$friendId] = [
'score' => $score,
'rank' => $rank + 1
];
}
}
// 按分数排序
arsort($scores);
性能优化建议
- Redis持久化:开启RDB + AOF混合持久化
- 数据库索引:
score字段建立索引 - 缓存排名:热数据(前100名)缓存到APCu或Redis中
- 异步更新:使用消息队列(RabbitMQ/Kafka)避免阻塞
- 限流:防止恶意刷分
不同场景选择
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 小型项目(<1万用户) | 纯数据库 | 简单够用 |
| 中型游戏/应用 | Redis Sorted Set | 性能好,易扩展 |
| 大型项目 | Redis + 数据库混合 | 兼顾性能与数据安全 |
| 好友排行榜 | Redis + ZINTERSTORE | 支持集合运算 |
| 实时性要求极高 | Redis + 内存缓存 | 毫秒级响应 |
对于大多数PHP项目,Redis Sorted Set 是最佳选择,平衡了性能、开发成本和维护复杂度。