使用 Redis 實現排行榜功能

排行榜功能是一個很廣泛的需求。使用 Redis 中有序集合的特性來實現排行榜是又好又快的選擇。php

通常排行榜都是有實效性的,好比「用戶積分榜」。若是沒有實效性一直按照總榜來排,可能榜首老是幾個老用戶,對於新用戶來講,那真是太使人沮喪了。git

首先,來個「今日積分榜」吧,排序規則是今日用戶新增積分從多到少。github

那麼用戶增長積分時,都操做一下記錄當天積分增長的有序集合。
假設今天是 2015 年 04 月 01 日,UID 爲 1 的用戶由於某個操做,增長了 5 個積分。
Redis 命令以下:redis

bashZINCRBY rank:20150401 5 1

假設還有其餘幾個用戶也增長了積分:bash

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

看看如今有序集合 rank:20150401 中的數據(withscores 參數能夠附帶獲取元素的 score):this

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

按照分數從高到低,獲取 top10:spa

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

由於只有三個元素,因此就查詢出了這些數據。code

若是天天記錄當天的積分排行榜,那麼其餘花樣百出的榜單也就簡單了。
好比「昨日積分榜」:orm

bashZREVRANGE rank:20150331 0 9 withscores

利用並集實現多天的積分總和,實現「上週積分榜」:排序

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

這樣就將 7 天的積分記錄合併到有序集合 rank:last_week 中了。權重因子 WEIGHTS 若是不給,默認就是 1。爲了避免隱藏細節,特地寫出。
那麼查詢上週積分榜 Top10 的信息就是:

bashZREVRANGE rank:last_week  0 9 withscores

「月度榜」、「季度榜」、「年度榜」等等就以此類推。

下面給出一個 PHP 版的簡單實現。使用 Redis 依賴於 PHP 擴展 PhpRedis,代碼還依賴於 Carbon 庫,用於處理時間。代碼量不多,因此就不敲註釋了。

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

}
相關文章
相關標籤/搜索