zoukankan      html  css  js  c++  java
  • 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缓存出现问题导致系统瘫痪。

  • 相关阅读:
    sklearn.neighbors.KNeighborsClassifier的k-近邻算法使用介绍
    修改或定义sudo下的PATH环境变量
    Linux安装deb后缀包
    Linux截图软件shutter安装
    Python变量前'*'和'**'的作用
    线段树入门
    机器学习实战源码&数据集
    Spring Boot 自动配置原理(精髓)
    Spring Boot 注解配置 day03
    Spring Boot 配置_yaml语法介绍 day02
  • 原文地址:https://www.cnblogs.com/owenma/p/11211580.html
Copyright © 2011-2022 走看看