一、Redis 有序集合简介

        Redis 有序集合(sorted set)和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。正是因为有分值,所以很适合用在排行榜业务中。

二、具体业务实现举例

下面举例对redis有序集合实现排行榜功能进行说明。比如,业务需要某用户近30日工具使用率的排行榜。

(1)为每个用户建立有序集合记录每日各个工具的使用频次,keyPrefix+userId+day作为有序集合的key。例如toolRank:11223344:2022-10-14,该key的有效期为30天,即最多只保存用户一个月的使用情况;

(2)用户每次打开工具即将有序集合中该工具的score+1;

(3)取当前日期前30天的全部集合,取集合间的并集,其中相同元素的score累加,形成的新集合即为用户近30日的使用率排行榜,即将日榜合并为月榜;

三、代码实现

springboot代码实现如下:

RedisServiceImpl.java

    //=============================== sort set =================================
    /**
     * 添加指定元素到有序集合中
     * @param key
     * @param score
     * @param value
     * @return
     */
    @Override
    public boolean sortSetAdd(String key, String value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 有序集合中对指定成员的分数加上增量 increment
     * @param key
     * @param value
     * @param i
     * @return
     */
    @Override
    public double sortSetZincrby(String key, String value, double i) {
        //返回新增元素后的分数
        return redisTemplate.opsForZSet().incrementScore(key, value, i);
    }

    /**
     * 向zset结构中指定元素的分数加上增量 i,新增key设置有效期
     * @param key
     * @param value
     * @param i
     * @param time
     * @param unit
     * @return
     */
    @Override
    public double sortSetZincrby(String key, String value, double i, Long time, TimeUnit unit) {
        long members = redisTemplate.opsForZSet().zCard(key);
        //返回新增元素后的分数
        double sorce = redisTemplate.opsForZSet().incrementScore(key, value, i);
        //新增key则设置有效期
        if (0 == members && null != time) {
            redisTemplate.expire(key, time, unit);
        }
        return sorce;
    }

    /**
     * 获得有序集合指定范围元素 (从大到小)
     * @param key
     * @param start
     * @param end
     * @return
     */
    @Override
    public Set<Object> sortSetReverseRange(String key, int start, int end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 将有序集合oZset和oZsetList的交集合并为nZset
     *
     * @param oZset
     * @param oZsetList
     * @param nZset
     */
    @Override
    public Long sortSetIntersection(String oZset, List<String> oZsetList,
                                    String nZset) {
        Long size = redisTemplate.opsForZSet().intersectAndStore(oZset, oZsetList, nZset,
                Aggregate.SUM, null);
        return size;
    }

    /**
    * 将有序集合oZset和oZsetList的并集合并为nZset,每个集合的权重相同
    *
    * @param oZset
    * @param oZsetList
    * @param nZset
    */
    @Override
    public Long sortSetUnionAndStore(String oZset, List<String> oZsetList,
                                     String nZset) {
        int[] weights = new int[oZsetList.size() + 1];
        for (int i = 0; i <= oZsetList.size(); ++i) {
            weights[i] = 1;
        }
        Long size = redisTemplate.opsForZSet().unionAndStore(oZset, oZsetList, nZset,
                Aggregate.SUM, Weights.of(weights));
        return size;
    }

    /**
     * 从zset结构中移除元素
     */
    @Override
    public Long sortSetRemove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }

RedisController.java

    @Autowired
    private RedisService redisService;
    
    private static final String keyPrefix = "toolRang:";

    @ApiOperation(value = "用户访问工具")
    @RequestMapping(value = "/openTool", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult openTool(@RequestParam("userId") String userId,
                                 @RequestParam(value = "toolId") String toolId,
                                 @RequestParam(value = "day", required = false) String day) {
        if (Objects.isNull(day)) {
            day = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
        }
        String key = keyPrefix.concat(userId).concat(":") + day;
        //搜索次数 + 1
        redisService.sortSetZincrby(key, toolId, 1, 30L, TimeUnit.DAYS);
        return CommonResult.success(null);
    }

    @ApiOperation(value = "获取排行榜")
    @RequestMapping(value = "/getRang", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult<Set<Object>> getToolRang(@RequestParam("userId") String userId,
                                                 @RequestParam(value = "day", required = false) String day) {
        String key = keyPrefix.concat(userId).concat(":");
        List<String> otherKeys = new ArrayList<>();
        LocalDate currentDay = LocalDate.now();
        if (!Objects.isNull(day)) {
            currentDay = LocalDate.parse(day, DateTimeFormatter.ISO_DATE);
        }
        for (int i = -29; i < 0; i++) {
            LocalDate otherDay = currentDay.plusDays(i);
            otherKeys.add(key + otherDay.format(DateTimeFormatter.ISO_DATE));
        }
        String currentKey = key + currentDay.format(DateTimeFormatter.ISO_DATE);
        String unionKey = keyPrefix.concat(userId).concat(":") + System.currentTimeMillis();
        redisService.sortSetUnionAndStore(currentKey, otherKeys, unionKey);
        Set<Object> res = redisService.sortSetReverseRange(unionKey, 0, -1);
        redisService.del(unionKey);
        return CommonResult.success(res);
    }

    @ApiOperation(value = "删除排行榜")
    @RequestMapping(value = "/delRang", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult<Set<Object>> delToolRang(@RequestParam("userId") String userId,
                                                 @RequestParam(value = "day", required = false) String day) {
        String key = keyPrefix.concat(userId);
        if (!Objects.isNull(day)) {
            key = key.concat(":").concat(day);
        }
        redisService.delByPattern(key.concat("*"));
        return CommonResult.success(null);
    }

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐