Redis的Sorted-Sets排行榜功能實現

Redis的ZSet排行榜功能實現

1. 功能需求

  類似給用戶n張圖片, 用戶左滑不喜歡右滑喜歡。所以每個用戶就會有一些喜歡的圖片集合和不喜歡的圖片集合。現在我們要做一個將按照一個算法將喜歡的排到前面。算法 ctr = (喜歡數+20)/ (喜歡數+不喜歡數+20),所有的內容按照這個算法的結果進行排行榜排序。

2. Redis sorts sets簡介

   Sorted-Sets和Sets類型極爲相似,它們都是字符串的集合,都不允許重複的成員出現在一個Set中。它們之間的主要差別是Sorted-Sets中的每一個成員都會有一個分數(score)與之關聯,Redis正是通過分數來爲集合中的成員進行從小到大的排序。然而需要額外指出的是,儘管Sorted-Sets中的成員必須是唯一的,但是分數(score)卻是可以重複的。

   Sorted Sets是通過Skip List(跳躍表)和hash Table(哈希表)的雙端口數據結構實現的,因此每次添加元素時,Redis都會執行O(log(N))操作。所以當我們要求排序的時候,Redis根本不需要做任何工作了,早已經全部排好序了。元素的分數可以隨時更新。

3. 代碼實現

本文主要通過redisTemplate來操作redis,當然也可以使用redis-client,看個人喜好。

首先寫兩個要用到的兩個方法, 一個批量插入數據,一個獲取排行榜Top n。

  /**
    * @Description: 批量添加zset數據
    * @author mazhq
    */
	public Long setBatchZSet(String key, Set<ZSetOperations.TypedTuple<Object>> typedTuples) {
		try {
			return redisTemplate.opsForZSet().add(key, typedTuples);
		} catch (Exception e) {
			logger.error("redis setZSet failed, key = " + key + "| error:" + e.getMessage(), e);
			return 0L;
		}
	}

	/**
	* @Description: 獲取排行前面的數據
	* @author mazhq
	*/
	public List<Object> getTopRankZSet(String key, int topCount) {
		try {
			Set<Object> range = redisTemplate.opsForZSet().reverseRange(key, 0, topCount);
			return Arrays.asList(range.toArray());
		} catch (Exception e) {
			logger.error("redis getTopRankZSet failed, key = " + key + "| error:" + e.getMessage(), e);
			return new ArrayList<>();
		}
	}

  

插入排行榜數據

/** 
* @Description: 批量添加圖片排行榜數據
* @author mazhq
*/
public void batchAddImageData(){
    //獲取喜歡和不喜歡的map數據 key是圖片ID
    Map<String, Integer> map = userBehaviorRecordManager.getQuickImageStatistic();
    //獲取所有圖片列表
    List<ImageConfigBean> imageConfigBeanList = quickImageConfigManager.getRealAllList();
    Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
    for (ImageConfigBean imageConfigResp : imageConfigBeanList) {
        String likeKey = imageConfigResp.getGuid() + QuickConstant.LIKE;
        String unLikeKey = imageConfigResp.getGuid() + QuickConstant.UNLIKE;
        //ctr算法 以1000爲統計精確維度 即精確到小數點後三位
        double ctr = 1000d;
        if (map.containsKey(likeKey) && map.containsKey(unLikeKey)) {
            double total = (map.get(likeKey)).doubleValue() + map.get(unLikeKey).doubleValue() + 20d;
            double ctrStatistic = (map.get(likeKey).doubleValue() + 20d) / total;
            ctr = (double) Math.round(ctrStatistic * 1000);
        }else if(!map.containsKey(likeKey) && map.containsKey(unLikeKey)){
            double total =  map.get(unLikeKey).doubleValue() + 20d;
            double ctrStatistic =  20d / total;
            ctr = (double) Math.round(ctrStatistic * 1000);
        }

        DefaultTypedTuple<Object> tuple = new DefaultTypedTuple<>(imageConfigResp.getGuid() + "", ctr);
        tuples.add(tuple);
    }

    redisClient.setBatchZSet(RedisKeysManager.getMiniProgramSlideRankingKey(), tuples);
}

  

獲取Top50排行榜數

 /** 
    * @Description: 獲取排行榜top50條記錄
    * @author mazhq
    */
    @RequestMapping("/getTop50")
    public String getTop50() {
        List<Object> stringList = redisClient.getTopRankWithScoresZSet(RedisKeysManager.getMiniProgramSlideRankingKey(), 50);
        return JSONObject.toJSONString(stringList);
    }

其它集合操作方法

//單個增加集合內容
public boolean setSortedSet(String key, double score, Object value) {
        try {
		    return redisTemplate.opsForZSet().add(key, value, score);
        } catch (Exception e) {
            logger.error("redis setSortedSet failed, key = " + key + "| error:" + e.getMessage(), e);
            return false;
        }
	}
//單個增加分數
public double incrementScore(String key, double score, Object value) {
        try {
            return redisTemplate.opsForZSet().incrementScore(key, value, score);
        } catch (Exception e) {
            logger.error("redis incrementScore failed, key = " + key + "| error:" + e.getMessage(), e);
            return 0.0;
        }
    }
//單個刪除
 public boolean delSortedSet(String key, Object... values) {
        try {
            long count = redisTemplate.opsForZSet().remove(key, values);
            return count > 0;
        } catch (Exception e) {
            logger.error("redis delSortedSet failed, key = " + key + "| error:" + e.getMessage(), e);
            return false;
        }
    }

 

 4. 總結

新增or更新

//單個新增or更新
Boolean add(K key, V value, double score);
//批量新增or更新
Long add(K key, Set<TypedTuple<V>> tuples);
//使用加法操作分數
Double incrementScore(K key, V value, double delta);

刪除

//通過key/value刪除
Long remove(K key, Object... values);

//通過排名區間刪除
Long removeRange(K key, long start, long end);

//通過分數區間刪除
Long removeRangeByScore(K key, double min, double max);

查尋

//通過排名區間獲取列表值集合
Set<V> range(K key, long start, long end);

//通過排名區間獲取列表值和分數集合
Set<TypedTuple<V>> rangeWithScores(K key, long start, long end);

//通過分數區間獲取列表值集合
Set<V> rangeByScore(K key, double min, double max);

//通過分數區間獲取列表值和分數集合
Set<TypedTuple<V>> rangeByScoreWithScores(K key, double min, double max);

//通過Range對象刪選再獲取集合排行
Set<V> rangeByLex(K key, Range range);

//通過Range對象刪選再獲取limit數量的集合排行
Set<V> rangeByLex(K key, Range range, Limit limit);
//獲取個人排行
Long rank(K key, Object o);

//獲取個人分數
Double score(K key, Object o);

統計

//統計分數區間的人數
Long count(K key, double min, double max);

//統計集合基數
Long zCard(K key);

  

基本整理了排行榜用到的所有方法,排行榜有這一篇文章夠用了。同時大家注意當redis緩存被清空,如何重新計算排行榜相關數據,或者安排定時排行榜數據定時落地邏輯。

避免redis緩存出現問題導致系統癱瘓。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章