Implementing Real-Time Leaderboards with Redis in PHP
This article explains how to design and implement a real-time ranking system for a mobile tank game using Redis sorted sets, covering leaderboard categories, composite scoring formulas, dynamic updates, data retrieval with pipelines, and provides a complete PHP class example.
Recently a real-time leaderboard was built for a mobile tank game, featuring global rankings, individual player queries, and dual‑dimensional sorting, handling data volumes from 10,000 to 500,000 players.
The leaderboards are divided into three main subjects: characters, guilds, and tanks, each with specific sub‑categories such as combat power, arena rank, tower floor and time, prestige, guild level, and tank types.
To meet real‑time requirements, Redis is chosen, primarily using its ZADD command to add member‑score pairs to a sorted set. When scores are equal, members are ordered lexicographically.
Composite scoring for level leaderboard : score = level * 10^10 + combatPower, allowing up to 13‑digit scores well within Redis's 64‑bit integer range.
Composite scoring for tower leaderboard : score = floor * 10^N + (baseTimestamp - clearTimestamp), with N=10 to keep a 10‑digit relative time; a far future base time (e.g., 2050‑01‑01) is used.
Tank leaderboard uses a compound member ID ( uid_tankId ) to distinguish different tanks belonging to the same player.
Dynamic updates store the sorted‑set member as the player UID and the score as the composite value, while mutable player data (name, avatar, guild, VIP level, etc.) is kept in a Redis hash as a JSON string. When a player's level or combat power changes, the sorted set score and the hash entry are updated via a pipeline.
Fetching the top N players (e.g., top 100) uses ZRANGE / ZREVRANGE to obtain UIDs, then HGET to retrieve their JSON data. Using Redis pipelines reduces the round‑trip count to two, dramatically improving performance.
The following PHP class demonstrates a generic leaderboard implementation with methods for adding/updating scores, retrieving a single rank, deleting entries, getting the top list (optionally with item data), and flushing the leaderboard. All Redis operations are performed with pipelines for efficiency.
<?php
class RankList
{
protected $rankKey;
protected $rankItemKey;
protected $sortFlag;
protected $redis;
public function __construct($redis, $rankKey, $rankItemKey, $sortFlag = SORT_DESC)
{
$this->redis = $redis;
$this->rankKey = $rankKey;
$this->rankItemKey = $rankItemKey;
$this->sortFlag = SORT_DESC;
}
public function getRedis()
{
return $this->redis;
}
public function setRedis($redis)
{
$this->redis = $redis;
}
public function updateScore($uid, $score = null, $rankItem = null)
{
if (is_null($score) && is_null($rankItem)) {
return;
}
$redis = $this->getRedis()->multi(Redis::PIPELINE);
if (!is_null($score)) {
$redis->zAdd($this->rankKey, $score, $uid);
}
if (!is_null($rankItem)) {
$redis->hSet($this->rankItemKey, $uid, $rankItem);
}
$redis->exec();
}
public function getRank($uid)
{
$redis = $this->getRedis()->multi(Redis::PIPELINE);
if ($this->sortFlag == SORT_DESC) {
$redis->zRevRank($this->rankKey, $uid);
} else {
$redis->zRank($this->rankKey, $uid);
}
$redis->hGet($this->rankItemKey, $uid);
list($rank, $rankItem) = $redis->exec();
return [$rank === false ? -1 : $rank + 1, $rankItem];
}
public function del($uid)
{
$redis = $this->getRedis()->multi(Redis::PIPELINE);
$redis->zRem($this->rankKey, $uid);
$redis->hDel($this->rankItemKey, $uid);
$redis->exec();
}
public function getList($topN, $withRankItem = false)
{
$redis = $this->getRedis();
if ($this->sortFlag === SORT_DESC) {
$list = $redis->zRevRange($this->rankKey, 0, $topN);
} else {
$list = $redis->zRange($this->rankKey, 0, $topN);
}
$rankItems = [];
if (!empty($list) && $withRankItem) {
$redis->multi(Redis::PIPELINE);
foreach ($list as $uid) {
$redis->hGet($this->rankItemKey, $uid);
}
$rankItems = $redis->exec();
}
return [$list, $rankItems];
}
public function flush()
{
$redis = $this->getRedis();
$redis->del($this->rankKey, $this->rankItemKey);
}
}
?>Overall, the article provides a concise yet complete guide to building a scalable, real‑time leaderboard using Redis sorted sets and PHP, with clear formulas for composite scores and performance‑optimised data access patterns.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.