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缓存出现问题导致系统瘫痪。

Redis的Sorted-Sets排行榜功能实现的更多相关文章

  1. Redis 命令 - Sorted Sets

    ZADD key score member [score member ...] Add one or more members to a sorted set, or update its scor ...

  2. Redis 有序聚合实现排行榜功能

    排行榜功能是一个很普遍的需求.使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择.Redis有序集合非常适用于有序不重复数据的存储 一般排行榜都是有实效性的,比如“用户积分榜”.如果没有实 ...

  3. Redis实现排行榜功能(实战)

    需求前段时间,做了一个世界杯竞猜积分排行榜.对世界杯64场球赛胜负平进行猜测,猜对+1分,错误+0分,一人一场只能猜一次.1.展示前一百名列表.2.展示个人排名(如:张三,您当前的排名106579). ...

  4. Redis实现世界杯排行榜功能(实战)

    转载请注明出处:https://www.cnblogs.com/wenjunwei/p/9754346.html 需求 前段时间,做了一个世界杯竞猜积分排行榜.对世界杯64场球赛胜负平进行猜测,猜对+ ...

  5. 使用 Redis 的 sorted set 实现用户排行榜

    要求:实现一个用户排行榜,用户数量有很多,排行榜存储的是用户玩游戏的分数,对排行榜的读取压力比较大,如何实现? 思路分析: 实现排行榜,可以考虑使用 Redis 的 zset 结构: 用户数量很多的话 ...

  6. 使用 Redis 实现排行榜功能

    排行榜功能是一个很普遍的需求.使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择. 一般排行榜都是有实效性的,比如“用户积分榜”.如果没有实效性一直按照总榜来排,可能榜首总是几个老用户,对 ...

  7. 使用 Redis 实现排行榜功能 (转载 https://segmentfault.com/a/1190000002694239)

    排行榜功能是一个很普遍的需求.使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择. 一般排行榜都是有实效性的,比如"用户积分榜".如果没有实效性一直按照总榜来排,可能榜 ...

  8. redis的有序集合(Sorted Sets)数据类型

    和Sets相比,Sorted Sets增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的Sorted Sets,其集合value可以是同学的学号,而 ...

  9. redis实现排行榜功能

    目录 加入排行榜 操作排行榜 redis的zset可以很方便地用来实现排行榜功能,下面简单介绍python如何使用redis实现排行榜功能 加入排行榜 获取redis实例 import redis m ...

随机推荐

  1. django--通过jwt获取用户信息的两种方式

    HTTP请求是无状态的,我们通常会使用cookie或session对其进行状态保持,cookie存储在客户端,容易被用户误删,安全性不高,session存储在服务端,在服务器集群情况下需要解决sess ...

  2. 使用Runtime的objc_msgSend实现模型和字典的互转

    一.介绍 模型转字典,字典转模型,这是开发中最基本的功能.系统类中提供了一个setValuesForKeysWithDictionary方法来实现字典转模型,至于模型转字典,这个就需要使用runtim ...

  3. Spring案例--打印机

    目录: 1.applicationContext.xml配置文件 <?xml version="1.0" encoding="UTF-8"?> &l ...

  4. Java 程序员应在2019年学习的10条面向对象(OOP)设计原则

    面向对象的设计原则 是 OOP 编程的核心,但是我看到大多数 Java 程序员都在追求诸如 Singleton 模式,Decorator 模式或 O​​bserver 模式之类的设计模式,而对学习面向 ...

  5. IT兄弟连 Java语法教程 流程控制语句 分支结构语句1

    不论哪一种编程语言,都会提供两种基本的流程控制结构:分支结构和循环结构.其中分支结构用于实现根据条件来选择性地执行某段代码,循环结构则用于实现根据循环条件重复执行某段代码.Java同样提供了这两种流程 ...

  6. Python - 部分PEP8规范

    写代码就像写字一样,为什么有的人写的字十分漂亮,而有的人写的字过后连自己都不认识,最主要还是从一开始是否对自己严格要求.从现在开始就当自己是个初学者,把代码写漂亮点.以下截取了部分PEP8代码规范,里 ...

  7. node.js的async和await

    一.async和await是什么 ES2017 标准引入了 async 函数,使得异步操作变得更加方便,async其实本质是Generator函数的语法糖 async表示函数里有异步操作 await表 ...

  8. Java开发桌面程序学习(12)——Javafx 悬浮窗提示 tooptip

    Javafx 悬浮窗提示 tooptip 鼠标悬浮在某个控件,弹出提示,效果如下: 代码: //control是某个控件 Tooltip.install(control, new Tooltip(&q ...

  9. WPF——如何为项目设置全局样式。

    在项目中,需要为所有的Button.TextBox设置一个默认的全局样式,一个个的为多个控件设置相同的样式显然是不明智的.在WPF中可以通过资源设置全局样式,主要有俩种方法: 1.第一种就是先写好按钮 ...

  10. 解决sublime快捷键回车换行问题

    鼠标右键sublime 以管理员身份运行 打开首选项里面的按键绑定用户 将下面的代码粘贴复制 { "keys": ["enter"], "comman ...