Using redis to realize ranking function

Time:2021-1-2

Ranking function is a very common requirement. It’s a good and fast choice to use the ordered set feature of redis to realize the ranking.

General rankings are effective, such as “user scoreboard”. If there is no effectiveness, it will always be ranked according to the general list. Maybe the number one user on the list is always a few old users. For new users, it’s really frustrating.

First of all, let’s have a “today’s scoreboard”. The ranking rule is that the number of new points added by today’s users is from more to less.

Then, when users add points, they all operate to record the ordered set of points added on that day.
Suppose today is April 1, 2015, and the user with uid 1 has increased 5 points due to a certain operation.
The redis command is as follows:

bashZINCRBY rank:20150401 5 1

Suppose there are several other users who have also increased their points:

bashZINCRBY rank:20150401 1 2
ZINCRBY rank:20150401 10 3

Look at the ordered set now rank:20150401 The WithCores parameter can be attached with the score of the element:

bashZRANGE rank:20150401 0 -1 withscores
bash1) "2"
2) "1"
3) "1"
4) "5"
5) "3"
6) "10"

According to the score from high to low, get TOP10:

bashZREVRANGE rank:20150401 0 9 withscores
bash1) "3"
2) "10"
3) "1"
4) "5"
5) "2"
6) "1"

Since there are only three elements, the data is queried.

If the daily record of the day’s ranking, then the other variety of the list will be simple.
For example, “yesterday’s scoreboard”:

bashZREVRANGE rank:20150331 0 9 withscores

Using union to achieve the sum of points for many days, to achieve “last week’s scoreboard”:

bashZUNIONSTORE rank:last_week 7 rank:20150323 rank:20150324 rank:20150325 rank:20150326 rank:20150327 rank:20150328 rank:20150329 WEIGHTS 1 1 1 1 1 1 1

Merge the points into an ordered set of 7 days rank:last_ Week is in. If weights is not given, it is 1 by default. In order not to hide the details, specially write.
So the information of the top 10 of last week’s scoreboard is as follows:

bashZREVRANGE rank:last_week  0 9 withscores

“Monthly list”, “quarterly list”, “annual list” and so on.

Here is a simple implementation of PHP version. Using redis depends on PHP extensionPhpRedis, the code also depends onCarbonLibrary for processing time. The amount of code is small, so don’t type comments.

php<?php

namespace Blog\Redis;

use \Redis;
use Carbon\Carbon;


class Ranks {

    const PREFIX = 'rank:';

    protected $redis = null;


    public function __construct(Redis $redis) {
        $this->redis = $redis;
    }


    public function addScores($member, $scores) {
        $key = self::PREFIX . date('Ymd');
        return $this->redis->zIncrBy($key, $scores, $member);
    }


    protected function getOneDayRankings($date, $start, $stop) {
        $key = self::PREFIX . $date;
        return $this->redis->zRevRange($key, $start, $stop, true);
    }


    protected function getMultiDaysRankings($dates, $outKey, $start, $stop) {
        $keys = array_map(function($date) {
            return self::PREFIX . $date;
        }, $dates);

        $weights = array_fill(0, count($keys), 1);
        $this->redis->zUnion($outKey, $keys, $weights);
        return $this->redis->zRevRange($outKey, $start, $stop, true);
    }


    public function getYesterdayTop10() {
        $date = Carbon::now()->subDays(1)->format('Ymd');
        return $this->getOneDayRankings($date, 0, 9);
    }


    public static function getCurrentMonthDates() {
        $dt = Carbon::now();
        $days = $dt->daysInMonth;

        $dates = array();
        for ($day = 1; $day <= $days; $day++) {
            $dt->day = $day;
            $dates[] = $dt->format('Ymd');
        }
        return $dates;
    }


    public function getCurrentMonthTop10() {
        $dates = self::getCurrentMonthDates();
        return $this->getMultiDaysRankings($dates, 'rank:current_month', 0, 9);
    }

}