Redis實戰運用之SortedSet

今天星期一,滿血迴歸。天天開心的工做,愉快的生活。php

繼上一篇Redis實戰運用之SET更新有兩天了,週末浪去了。今天接着上一篇分享下SortedSet,上一篇分享了SET的一些基本操做,例如像集合中添加元素、刪除元素、取出元素,取交集,取並集等操做,(不瞭解的SET的,建議能夠先去看下Redis實戰運用之SET。^_^)。html

其實呢,今天分享的SortedSet,顧名思義就是一個有序的集合。既然是集合,那麼和上一篇分享的SET實際上是類似的,有不少相似的操做,取交、取並、添加、刪除等功能。主要的區別在於「有序」二字上,既然有序那必然得有一個排序的依據,在SortedSet裏面這個排序的依據名稱叫作:Score,每個有序集合的元素都有一個對應的Score值。mysql

那麼設置到添加有序集合時,添加元素的時候比以前SET添加元素的時候多一個Score參數就很能理解了。有了這個Score的值,那麼這個集合有有了序列,有了序列就又了排名。既然如此,SortedSet最主要的一些方法,例如:ZRANGEBYSCORE根據score範圍獲取元素,ZRANK根據排名獲取元素,ZREMRANGEBYRANK,ZREMRANGEBYSCORE根據分數、根據排名來刪除元素,ZSCORE獲取元素socre,ZREVRANK、ZRANK根據排名升序、倒序獲取元素,ZRANGE、ZREVRANGE根據Score升序、倒序獲取元素等,這些理所固然的方法是否是就很容易理解了,都是基於score這個值來肯定的。具體詳細的方法依然貼在本位最後的位置。git

至此,有了上面的理解,那麼上一篇些分享SET留下的問題,就迎刃而解了。上一篇說起的多個標籤關聯查詢能夠SET來實現交集,可是不能實現排序分頁等功能,因此是使用SortedSet來實現的。github

是時候上一波代碼了:redis

/**
     * @desc 有序集合獲取分頁數據
     * @param string $redisKey
     * @param int $currentPage
     * @param int $perPage
     * @param string $sort
     * @return mixed
     */
    public static function pageData($redisKey = '',$currentPage = 1, $perPage = 10,$sort = 'desc'){
        $pagination['currentPage'] = $currentPage;
        $pagination['perPage'] = $perPage;
        $pagination['totalPage'] = $pagination['totalCount'] = 0;

        $rs['data'] = [];
        $redis = Yii::$app->redis;

        $pagination['totalCount'] = $redis->ZCard($redisKey);
        $pagination['totalPage'] = ceil($pagination['totalCount'] / $perPage);
        
        //這裏的$start $end 就至關於mysql裏面:limit start end
        $start = ($currentPage - 1) * $perPage;
        $end = $currentPage * $perPage - 1;

        if(strtolower($sort) == 'desc'){
            $rs['data'] = $redis->ZRevrange($redisKey,$start,$end);
        }else{
            $rs['data'] = $redis->zRange($redisKey, $start, $end);
        }

        $rs['pagination'] = $pagination;

        return $rs;
    }
 //用的是https://github.com/yiisoft/yii2-redis這個擴展從新處理了哈zrange這個方法 withScores就是放回元素的時候同時返回其score
 public static function zRange($redisKey = '',$start = 0,$end = -1,$withScores = true,$sort = SORT_DESC){
        $data = [];
        if($withScores){
            $rs = [];
            $data = Yii::$app->redis->ZRange($redisKey,$start,$end,'WITHSCORES');
            if($data){
                $count = count($data);
                for($i = 0 ; $i < $count ; $i++){
                    if($i % 2 != 0){
                        $rs[$data[$i - 1]] = $data[$i];
                    }
                }
                array_multisort($rs,$sort);
            }
            return $rs;
        }else{
            $data = Yii::$app->redis->ZRange($redisKey,$start,$end);
        }
        return $data;
    }

 /**
     * @desc 獲取交集數據 獲取並集基本同樣的操做 名字換成zUnionStore便可
     * @param array $redisKeys 鍵值 必須大於一個
     * @param int $expire 緩存時間 默認2個小時
     * @return string
     */
    public static function zInterStore($redisKeys = [],$expire = 2 * 60 * 60){
        if(empty($redisKeys)) return '';

        $keyCount = count($redisKeys);
        //最少須要2個集合才能取交
        if($keyCount < 2) return $redisKeys[0];
        //最多隻能是5個集合取交
        $keyCount > 5 && $keyCount = 5;

        $destination = 'xjj:'.md5(json_encode($redisKeys)); //xjj=>小JJ 並集 djj=>大JJ
        if((int)Yii::$app->get('redis')->exists($destination) == 1) return $destination; //有緩存直接讀取緩存

        $rs = 0;
        switch($keyCount){
            case 2:
                $rs = Yii::$app->get('redis')->zinterstore($destination,$keyCount,$redisKeys[0],$redisKeys[1]);
                break;
            case 3:
                $rs = Yii::$app->get('redis')->zinterstore($destination,$keyCount,$redisKeys[0],$redisKeys[1],$redisKeys[2]);
                break;
            case 4:
                $rs = Yii::$app->get('redis')->zinterstore($destination,$keyCount,$redisKeys[0],$redisKeys[1],$redisKeys[2],$redisKeys[3]);
                break;
            case 5:
                $rs = Yii::$app->get('redis')->zinterstore($destination,$keyCount,$redisKeys[0],$redisKeys[1],$redisKeys[2],$redisKeys[3],$redisKeys[4]);
                break;
            default:
                break;
        }
        if(intval($rs) === 0) return ''; //交集直接就木有數據 TODO 標記取交集沒有結果的$destination 避免重複取交集運算 這個很好解決本身去搞吧
        Yii::$app->get('redis')->expire($destination,$expire);
        return $destination;
    }

有了上面的代碼那麼實現這個搜索具體的實現以下:sql

$page = (int)Yii::$app->request->get('page') ?: 1;
//從tag的hash中讀取的數據
$tagInfoList = [
    ['id' => 1,'name' => '韓國美女','alias' =>'hanguo'],
    ['id' => 2,'name' => '獅子座','alias' =>'sz']
    ['id' => 3,'name' => '平面模特','alias' =>'mote']
    ['id' => 4,'name' => '清純','alias' =>'qincun']
    ['id' => 5,'name' => '正妹','alias' =>'zhengmei']
];
//指定字段獲取明星列表數據
$needFields = ['id','name','nick_name','header','view','ranking']; //查詢的字段
//獲取數據就是這麼簡單,依然沒有查詢數據庫。
$result = Star::getStarByTags($tagInfoList,$page,20,$needFields); 


     /**
     * getStarByTags 具體實現
     * 邏輯說明 多個標籤的時候 取明星數據經過「所有交集」的方式獲取
     * @param array $tags
     * @param int $currentPage
     * @param int $perPage
     * @param array $fields
     * @return array|mixed
     */
    public static function getStarByTags($tags = [],$currentPage = 1, $perPage = 10,$fields = []){
        $tagCount = count($tags);
        //沒有查詢條件 直接讀取總的明星列表
        if($tagCount === 0) return static::getMaxViewStar($currentPage,$perPage,$fields);

        $redisKey = '';
        //只有一個標籤,直接讀取該標籤的數據便可
        if(count($tags) === 1){
            $redisKey = 'tagZset:'.trim($tags[0]['alias']);
        }else{
            $alias = array_column($tags,'alias');
            $alias = array_map(function($value){
                return 'tagZset:'.trim($value);
            },$alias);
            $redisKey = RedisService::zInterStore($alias); //上一段代碼中有
        }
        //上面代碼中有 分頁獲取SortedSet中的數據
        $rs = RedisService::pageData($redisKey,$currentPage,$perPage); 
        //根據ID從hash中獲取具體信息,前面寫hash的時候分享過。
        $rs['data'] = static::formatStars($rs['data'],$fields); 
        return $rs;

    }

    /**
     * @desc 獲取瀏覽量最高的明星數據 starZset也是一個有序結合,以view做爲分數。
     * @param int $currentPage
     * @param int $perPage
     * @param array $fields
     * @return array
     */
    public static function getMaxViewStar($currentPage = 1, $perPage = 10,$fields = []){
        $rs = RedisService::pageData('starZset',$currentPage,$perPage);

        $rs['data'] = static::formatStars($rs['data'],$fields);

        return $rs;
    }

以上代碼固然也是有序結合的基本運用。藉此就會發現sort設置用於作一個排行榜就很容易了。例如說就我本身的果圖網:http://www.guotuw.com/ranking/這個頁面包含了各類排行榜,例如大陸美女榜單韓國美女榜混血美女榜單等等,甚至之後擴展各類各樣的榜單設置都徹底不用該代碼就能徹底兼容。具體實現也很簡單,就只用有序集合就能實現了。例如:大陸美女榜單包含薛麗於欣彤韓國美女榜包含최별하차정아不管有哪些明星。就至關於一個分類的明星ID做爲元素,瀏覽量view做爲score,就能實現各類榜單。很簡單吧。^_^數據庫

在例如,不少網站都在作日榜單,月榜單這個功能,這裏在來貼一段代碼具體實現很是之簡單。json

/**
     * @desc 更新明星訪問量同時記錄明星天天每個月的訪問量
     * @param int $starId
     */
    public static function incrView($starId = 0){
        $redis = Yii::$app->get('redis');
        $starId = intval($starId);
        if($starId == 0) return;

        $increment = rand(1,3);
        $redis->hincrby('star:'.$starId,'view',$increment);//明星hash
        $redis->zincrby('starZset',$increment,$starId); //明星總榜

        //爲各類明星榜單作基礎數據 記錄每一個明星天天的訪問量
        $starDayView = 'starDayView:'.date('Ymd',time());
        //記錄每一個明星每個月的訪問量
        $starMonthView = 'starMonthView:'.date('Ym',time());
        /*
         * 例如:
         * starDayView:20171012 = [starA=>3,starB=>4,c=>5]
         * starDayView:20171013 = [starA=>5,starB=>4,c=>0]
         * 直接取並集 score默認求和 而後和各種型的榜單的明星ID取交集便可 明星的score默認爲0
         * 交集結果緩存到天天晚上的12點過時 便可實現榜單天天自動更新
         *
         * 月榜:直接從starMonthView取值便可
         */
        $redis->zincrby($starDayView,$increment,$starId);
        $redis->zincrby($starMonthView,$increment,$starId);
    }

這篇有點長,若是你都能看到這裏了,那就謝謝你了。最後在囉嗦下有序結合取交集和並集的時候關於score的問題,默認求和SUM,能夠將全部集合中某個成員的 score 值之  做爲結果集中該成員的 score 值;使用參數 MIN ,能夠將全部集合中某個成員的 最小 score 值做爲結果集中該成員的 score 值;而參數 MAX 則是將全部集合中某個成員的 最大 score 值做爲結果集中該成員的 score 值。這個看具體的需求運用便可。緩存

若是你看累了,能夠下個人果圖網,打哈望,美女多多喲。下一篇分享:Redis Pub/Sub(發佈/訂閱)

有序集合具體方法以下:

序號 命令及描述
1 ZADD key score1 member1 [score2 member2] 
向有序集合添加一個或多個成員,或者更新已存在成員的分數
2 ZCARD key 
獲取有序集合的成員數
3 ZCOUNT key min max 
計算在有序集合中指定區間分數的成員數
4 ZINCRBY key increment member 
有序集合中對指定成員的分數加上增量 increment
5 ZINTERSTORE destination numkeys key [key ...] 
計算給定的一個或多個有序集的交集並將結果集存儲在新的有序集合 key 中
6 ZLEXCOUNT key min max 
在有序集合中計算指定字典區間內成員數量
7 ZRANGE key start stop [WITHSCORES] 
經過索引區間返回有序集合成指定區間內的成員
8 ZRANGEBYLEX key min max [LIMIT offset count] 
經過字典區間返回有序集合的成員
9 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 
經過分數返回有序集合指定區間內的成員
10 ZRANK key member 
返回有序集合中指定成員的索引
11 ZREM key member [member ...] 
移除有序集合中的一個或多個成員
12 ZREMRANGEBYLEX key min max 
移除有序集合中給定的字典區間的全部成員
13 ZREMRANGEBYRANK key start stop 
移除有序集合中給定的排名區間的全部成員
14 ZREMRANGEBYSCORE key min max 
移除有序集合中給定的分數區間的全部成員
15 ZREVRANGE key start stop [WITHSCORES] 
返回有序集中指定區間內的成員,經過索引,分數從高到底
16 ZREVRANGEBYSCORE key max min [WITHSCORES] 
返回有序集中指定分數區間內的成員,分數從高到低排序
17 ZREVRANK key member 
返回有序集合中指定成員的排名,有序集成員按分數值遞減(從大到小)排序
18 ZSCORE key member 
返回有序集中,成員的分數值
19 ZUNIONSTORE destination numkeys key [key ...] 
計算給定的一個或多個有序集的並集,並存儲在新的 key 中
20 ZSCAN key cursor [MATCH pattern] [COUNT count]  迭代有序集合中的元素(包括元素成員和元素分值)
相關文章
相關標籤/搜索