How to Build a Real‑Time Game Leaderboard with Redis Sorted Sets

This article explains how to implement a real‑time ranking system for a mobile tank game using Redis sorted sets, covering ranking categories, composite scoring formulas, dynamic updates, efficient data retrieval with pipelines, and a complete PHP class example.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
How to Build a Real‑Time Game Leaderboard with Redis Sorted Sets

1. Introduction

Recently a real‑time leaderboard was implemented for a mobile game. Main features include real‑time global ranking, individual player rank lookup, and support for two‑dimensional sorting. Data volume ranges from 10,000 to 500,000 records.

2. Ranking Categories

Rankings are divided by entity type:

Character

Guild (Clan)

Tank

Examples of specific leaderboards:

- Combat Power Leaderboard (1. Combat 2. Level)
- Personal Arena Leaderboard (1. Arena Rank)
- Tower Leaderboard (1. Floor 2. Completion Time)
- Prestige Leaderboard (1. Prestige 2. Level)

Guild Level Leaderboard (1. Guild Level 2. Total Combat Power)

- Medium Tank
- Heavy Tank
- Anti‑Tank Gun
- Self‑Propelled Artillery

3. Design Idea

Because of the real‑time requirement, Redis is chosen to implement the leaderboard. The article references the Redis online manual for command details.

Key problems to solve:

Composite sorting (2‑dimensional)

Dynamic updates of ranking data

How to retrieve the leaderboard

4. Implementing Composite Sorting

Redis Sorted Sets are used. Adding members with scores is done via ZADD key score member. By default, equal scores are ordered by member lexical order.

4.1 Level Leaderboard

Score formula: score = level * 10000000000 + combatPower . Player level ranges 1‑100, combat power up to 100,000,000, giving a 13‑digit score, well within Redis 64‑bit integer limits.

4.2 Tower Leaderboard

Requires higher floor first, then earlier completion time. Score is calculated as score = floor * 10^N + (baseTime - completionTimestamp) , where baseTime is a far future timestamp (e.g., 2050‑01‑01 00:00:00 = 2524579200). N is set to 10 to keep 10 digits for the relative time.

4.3 Tank Leaderboard

Member IDs are composite keys formed by uid_tankId , which must be handled carefully.

5. Dynamic Updates of Ranking Data

Using the level leaderboard as an example, the sorted set stores uid as member and the composite score as score. Player details (name, avatar, guild, VIP level, etc.) are stored separately in a Redis hash as JSON strings.

-- s1:rank:user:lv ---------- zset --
| playerId1 | score1
| ...       | ...
| playerIdN | scoreN
-------------------------------------

The hash key s1:rank:user:lv:item holds the JSON data for each player.

When a player's level or combat power changes, the composite score in the sorted set is updated. When other display data changes, the JSON in the hash is updated.

6. Retrieving the Leaderboard

To get the top 100 players from the level leaderboard: ZRANGE key start stop [WITHSCORES] Steps: zRange("s1:rank:user:lv", 0, 99) – obtain the top 100 uids. hGet("s1:rank:user:lv:item", $uid) – fetch each player's JSON data.

Executing 100 individual hGet commands can be costly. Using Redis pipelines reduces the round‑trip to two commands.

// PHP example using pipeline
$redis->multi(Redis::PIPELINE);
foreach ($uids as $uid) {
    $redis->hGet($userDataKey, $uid);
}
$resp = $redis->exec(); // results returned as an array

Pipeline batches commands without guaranteeing atomicity, whereas MULTI provides atomic transactions.

7. Show The Code

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

This provides a simple yet complete implementation of a leaderboard where the score calculation is handled externally.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Real-TimeredisPHPPipelineSorted Setleaderboard
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.